Routing Conventions In OData v4 Using Web API 2



PSVDEVELOPERS













Introduction

Developing OData Web API service is pretty simple but mostly we get  a "404 - Not Found" error due to routing issue and it makes debugging difficult. When user is requesting OData URL, the request is mapped with controller name and action name. This request mapping is based on HTTP methods (GET, POST, PUT, DELETE, and PATCH) and URI.

There are two types of routing conventions supported by OData using Web API.

  • Built-in Routing Conventions
  • Custom Routing Conventions

Built-in Routing Conventions

OData URI mainly contains three parts:

  • Service Root
  • Resource path
  • Query option



Resource path is a very important part in URI. The Resource path may have many parts like Entity set name (Controller name), entity key, related entity (navigation property) etc. As we know, the controller name is always derived from the entity set and it is root of the resource path. In above mention example, web API looks for "TestDataController". The Action name is decided from the path segment along with EDM (entity data model).

We have two choices for action name: HTTP method name (example- GET, POST, etc.) and HTTP method name +Entity set name (example: GetTestData,PostTestData, etc.)

Following table contains the List of action name based URI supported by OData.



Following are rules to define method signatures:

  • Action should have parameter name as "key" if path contains key.
  • Action should have parameter name as "relatedKey" if path contains key of navigation property.
  • FromODataUri attribute used to get key and relatedKey parameter from URI.
  • Parameter type must be entity type for PUT and POST request.
  • Parameter type must be Delta<T> where T is entity type for PATCH request.
Custom Routing Conventions
Built-in routing convention does not cover all OData URIs. Interface IODataRoutingConvention helps us to add new conventions. This interface has two methods.
  • SelectController: It returns the name of the controller.
  • SelectAction: It returns the name of the action.
Both the methods should return null if the convention does not apply to that request. ODataPathparameter of these methods is responsible for parsed OData resource path. It has list of ODataPathSegment instances (one per each segment of resource path). PathTemplate property of the ODataPath is a string type and it contains full path of the resource path (i.e. concatenation all of the path segments).
There is built-in class named EntitySetRoutingConvention which is inheriting from NavigationSourceRoutingConvention and NavigationSourceRoutingConvention class inherit from IODataRoutingConvention. This class is useful to specify the custom route for addition of existing route. This class does not have SelectController method which is required for define the new routing convention.
Let take an example, consider following URI there is no build-in support for supplling index for navigation property.
/Department(1)/Employees(1)

To handle custom routing, first step is to create routing convention. Following code can help us to create routing convention.
  1. namespaceWebAPITest  
  2. {  
  3.     usingSystem.Linq;  
  4.     usingSystem.Net.Http;  
  5.     usingSystem.Web.Http.Controllers;  
  6.     usingSystem.Web.OData.Routing;  
  7.     usingSystem.Web.OData.Routing.Conventions;  
  8.     publicclassCustomConvention: EntitySetRoutingConvention  
  9.     {  
  10.         publicoverridestringSelectAction(ODataPathodataPath, HttpControllerContext context, ILookup < string, HttpActionDescriptor > actionMap)  
  11.         {  
  12.             if (context.Request.Method == HttpMethod.Get && odataPath.PathTemplate == "~/entityset/key/navigation/key")  
  13.             {  
  14.                 NavigationPathSegmentnavigationSegment = odataPath.Segments[2] asNavigationPathSegment;  
  15.                 varnavigationProperty = navigationSegment.NavigationProperty;  
  16.                 //Create Action name  
  17.                 stringactionName = "Get" + navigationProperty.Name;  
  18.                 if (actionMap.Contains(actionName))  
  19.                 {  
  20.                     // Add keys to route data, so they will bind to action parameters.  
  21.                     KeyValuePathSegmentkeyValueSegment = odataPath.Segments[1] asKeyValuePathSegment;  
  22.                     context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;  
  23.                     KeyValuePathSegmentrelatedKeySegment = odataPath.Segments[3] asKeyValuePathSegment;  
  24.                     context.RouteData.Values[ODataRouteConstants.RelatedKey] = relatedKeySegment.Value;  
  25.                     returnactionName;  
  26.                 }  
  27.             }  
  28.             // Not a match.  
  29.             returnnull;  
  30.         }  
  31.     }  
  32. }  
In the above example, I have derived CustomConvention class from EntitySetRoutingConvention because we need not required method to SelectController. Built-in routing is already support URI : "/Department(key)". Here I want this convention is work for GET method and when the path template is "~/entityset/key/navigation/key", so I have put this condition at beginning. At the bottom I have create action name. To create action name, Iam using method type i.e. "GET" and navigation property nameand concat them i.e. Get{Navigation property name}. This action takes two parameters: key and relatedKey.
Next step is to add this convention to routing conventions list.
  1. namespaceWebAPITest  
  2. {  
  3.     usingMicrosoft.OData.Edm;  
  4.     usingSystem.Web.Http;  
  5.     usingSystem.Web.OData.Batch;  
  6.     usingSystem.Web.OData.Builder;  
  7.     usingSystem.Web.OData.Extensions;  
  8.     usingSystem.Web.OData.Routing;  
  9.     usingSystem.Web.OData.Routing.Conventions;  
  10.     publicstaticclassWebApiConfig  
  11.     {  
  12.         publicstaticvoid Register(HttpConfigurationconfig)  
  13.         {  
  14.             var conventions = ODataRoutingConventions.CreateDefault();  
  15.             // Insert the custom convention at the start of the collection.  
  16.             conventions.Insert(0, newCustomConvention());  
  17.             config.MapODataServiceRoute("odata"null,  
  18.                 GetEdmModel(), newDefaultODataPathHandler(), conventions,  
  19.                 newDefaultODataBatchHandler(GlobalConfiguration.DefaultServer));  
  20.             config.EnsureInitialized();  
  21.         }  
  22.         privatestaticIEdmModelGetEdmModel()  
  23.         {  
  24.             ODataConventionModelBuilder builder = newODataConventionModelBuilder();  
  25.             builder.Namespace = "WebAPITest";  
  26.             builder.ContainerName = "DefaultContainer";  
  27.             builder.EntitySet < Department > ("Department");  
  28.             //  
  29.             builder.EntitySet < Employee > ("Employee");  
  30.             varedmModel = builder.GetEdmModel();  
  31.             returnedmModel;  
  32.         }  
  33.     }  
  34. }  
Now we need to add method GetEmployees to department controller. This method simply retrieves employee data from department collection.
  1. publicIHttpActionResultGetEmployees(int key, intrelatedKey)  
  2. {  
  3.     var result = DAL.GetDepartments().Where(p => p.Id == key).FirstOrDefault().Employees.Where(q => q.Id == relatedKey).FirstOrDefault();  
  4.     return Ok(result);  
  5. }  
Output



Summary

OData v4 provides many built-in routing conventions and we can also create our custom routing conventions which are not supported by built-in routing.