"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Routing Conventions in ASP.NET Web API 2 Odata

by Mike Wasson

This article describes the routing conventions that Web API uses for OData endpoints.

When Web API gets an OData request, it maps the request to a controller name and an action name. The mapping is based on the HTTP method and the URI. For example, GET /odata/Products(1) maps to ProductsController.GetProduct.

In part 1 of this article, I describe the built-in OData routing conventions. These conventions are designed specifically for OData endpoints, and they replace the default Web API routing system. (The replacement happens when you call MapODataRoute.)

In part 2, I show how to add custom routing conventions. Currently the built-in conventions do not cover the entire range of OData URIs, but you can extend them to handle additional cases.

## Built-in Routing Conventions

Before I describe the OData routing conventions in Web API, it’s helpful to understand OData URIs. An OData URI consists of:

For routing, the important part is the resource path. The resource path is divided into segments. For example, /Products(1)/Supplier has three segments:

So this path picks out the supplier of product 1.

[!NOTE] OData path segments do not always correspond to URI segments. For example, “1” is considered a path segment.

Controller Names. The controller name is always derived from the entity set at the root of the resource path. For example, if the resource path is /Products(1)/Supplier, Web API looks for a controller named ProductsController.

Action Names. Action names are derived from the path segments plus the entity data model (EDM), as listed in the following tables. In some cases, you have two choices for the action name. For example, “Get” or “GetProducts”.

Querying Entities

Request Example URI Action Name Example Action
GET /entityset /Products GetEntitySet or Get GetProducts
GET /entityset(key) /Products(1) GetEntityType or Get GetProduct
GET /entityset(key)/cast /Products(1)/Models.Book GetEntityType or Get GetBook

For more information, see Create a Read-Only OData Endpoint.

Creating, Updating, and Deleting Entities

Request Example URI Action Name Example Action
POST /entityset /Products PostEntityType or Post PostProduct
PUT /entityset(key) /Products(1) PutEntityType or Put PutProduct
PUT /entityset(key)/cast /Products(1)/Models.Book PutEntityType or Put PutBook
PATCH /entityset(key) /Products(1) PatchEntityType or Patch PatchProduct
PATCH /entityset(key)/cast /Products(1)/Models.Book PatchEntityType or Patch PatchBook
DELETE /entityset(key) /Products(1) DeleteEntityType or Delete DeleteProduct
DELETE /entityset(key)/cast /Products(1)/Models.Book DeleteEntityType or Delete DeleteBook

Querying a Navigation Property

Request Example URI Action Name Example Action
GET /entityset(key)/navigation /Products(1)/Supplier GetNavigationFromEntityType or GetNavigation GetSupplierFromProduct
GET /entityset(key)/cast/navigation /Products(1)/Models.Book/Author GetNavigationFromEntityType or GetNavigation GetAuthorFromBook

For more information, see Working with Entity Relations.

Creating and Deleting Links

Request Example URI Action Name
POST /entityset(key)/links/navigation|/Products(1)/links/Supplier CreateLink
PUT /entityset(key)/links/navigation|/Products(1)/links/Supplier CreateLink
DELETE /entityset(key)/links/navigation|/Products(1)/links/Supplier DeleteLink
DELETE /entityset(key)/links/navigation(relatedKey)|/Products/(1)/links/Suppliers(1) DeleteLink

For more information, see Working with Entity Relations.

Properties

Requires Web API 2

Request Example URI Action Name Example Action
GET /entityset(key)/property /Products(1)/Name GetPropertyFromEntityType or GetProperty GetNameFromProduct
GET /entityset(key)/cast/property /Products(1)/Models.Book/Author GetPropertyFromEntityType or GetProperty GetTitleFromBook

Actions

Request Example URI Action Name Example Action
POST /entityset(key)/action /Products(1)/Rate ActionNameOnEntityType or ActionName RateOnProduct
POST /entityset(key)/cast/action /Products(1)/Models.Book/CheckOut ActionNameOnEntityType or ActionName CheckOutOnBook

For more information, see OData Actions.

Method Signatures

Here are some rules for the method signatures:

For reference, here is an example that shows method signatures for every built-in OData routing convention.

[!code-csharpMain]

   1:  public class ProductsController : ODataController
   2:  {
   3:      // GET /odata/Products
   4:      public IQueryable<Product> Get()
   5:   
   6:      // GET /odata/Products(1)
   7:      public Product Get([FromODataUri] int key)
   8:   
   9:      // GET /odata/Products(1)/ODataRouting.Models.Book
  10:      public Book GetBook([FromODataUri] int key)
  11:   
  12:      // POST /odata/Products 
  13:      public HttpResponseMessage Post(Product item)
  14:   
  15:      // PUT /odata/Products(1)
  16:      public HttpResponseMessage Put([FromODataUri] int key, Product item)
  17:   
  18:      // PATCH /odata/Products(1)
  19:      public HttpResponseMessage Patch([FromODataUri] int key, Delta<Product> item)
  20:   
  21:      // DELETE /odata/Products(1)
  22:      public HttpResponseMessage Delete([FromODataUri] int key)
  23:   
  24:      // PUT /odata/Products(1)/ODataRouting.Models.Book
  25:      public HttpResponseMessage PutBook([FromODataUri] int key, Book item)
  26:   
  27:      // PATCH /odata/Products(1)/ODataRouting.Models.Book
  28:      public HttpResponseMessage PatchBook([FromODataUri] int key, Delta<Book> item)
  29:   
  30:      // DELETE /odata/Products(1)/ODataRouting.Models.Book
  31:      public HttpResponseMessage DeleteBook([FromODataUri] int key)
  32:   
  33:      //  GET /odata/Products(1)/Supplier
  34:      public Supplier GetSupplierFromProduct([FromODataUri] int key)
  35:   
  36:      // GET /odata/Products(1)/ODataRouting.Models.Book/Author
  37:      public Author GetAuthorFromBook([FromODataUri] int key)
  38:   
  39:      // POST /odata/Products(1)/$links/Supplier
  40:      public HttpResponseMessage CreateLink([FromODataUri] int key, 
  41:          string navigationProperty, [FromBody] Uri link)
  42:   
  43:      // DELETE /odata/Products(1)/$links/Supplier
  44:      public HttpResponseMessage DeleteLink([FromODataUri] int key, 
  45:          string navigationProperty, [FromBody] Uri link)
  46:   
  47:      // DELETE /odata/Products(1)/$links/Parts(1)
  48:      public HttpResponseMessage DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty)
  49:   
  50:      // GET odata/Products(1)/Name
  51:      // GET odata/Products(1)/Name/$value
  52:      public HttpResponseMessage GetNameFromProduct([FromODataUri] int key)
  53:   
  54:      // GET /odata/Products(1)/ODataRouting.Models.Book/Title
  55:      // GET /odata/Products(1)/ODataRouting.Models.Book/Title/$value
  56:      public HttpResponseMessage GetTitleFromBook([FromODataUri] int key)
  57:  }

## Custom Routing Conventions

Currently the built-in conventions do not cover all possible OData URIs. You can add new conventions by implementing the IODataRoutingConvention interface. This interface has two methods:

[!code-csharpMain]

   1:  string SelectController(ODataPath odataPath, HttpRequestMessage request);
   2:  string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
   3:      ILookup<string, HttpActionDescriptor> actionMap);

For both methods, if the convention does not apply to that request, the method should return null.

The ODataPath parameter represents the parsed OData resource path. It contains a list of ODataPathSegment instances, one for each segment of the resource path. ODataPathSegment is an abstract class; each segment type is represented by a class that derives from ODataPathSegment.

The ODataPath.TemplatePath property is a string that represents the concatenation all of the path segments. For example, if the URI is /Products(1)/Supplier, the path template is “~/entityset/key/navigation”. Notice that the segments don’t correspond directly to URI segments. For example, the entity key (1) is represented as its own ODataPathSegment.

Typically, an implementation of IODataRoutingConvention does the following:

  1. Compare the path template to see if this convention applies to the current request. If it does not apply, return null.
  2. If the convention applies, use properties of the ODataPathSegment instances to derive controller and action names.
  3. For actions, add any values to the route dictionary that should bind to the action parameters (typically entity keys).

Let’s look at a specific example. The built-in routing conventions do not support indexing into a navigation collection. In other words, there is no convention for URIs like the following:

[!code-javascriptMain]

   1:  /odata/Products(1)/Suppliers(1)

Here is a custom routing convention to handle this type of query.

[!code-csharpMain]

   1:  using Microsoft.Data.Edm;
   2:  using System.Linq;
   3:  using System.Net.Http;
   4:  using System.Web.Http.Controllers;
   5:  using System.Web.Http.OData.Routing;
   6:  using System.Web.Http.OData.Routing.Conventions;
   7:   
   8:  namespace ODataRouting
   9:  {
  10:      public class NavigationIndexRoutingConvention : EntitySetRoutingConvention
  11:      {
  12:          public override string SelectAction(ODataPath odataPath, HttpControllerContext context, 
  13:              ILookup<string, HttpActionDescriptor> actionMap)
  14:          {
  15:              if (context.Request.Method == HttpMethod.Get && 
  16:                  odataPath.PathTemplate == "~/entityset/key/navigation/key")
  17:              {
  18:                  NavigationPathSegment navigationSegment = odataPath.Segments[2] as NavigationPathSegment;
  19:                  IEdmNavigationProperty navigationProperty = navigationSegment.NavigationProperty.Partner;
  20:                  IEdmEntityType declaringType = navigationProperty.DeclaringType as IEdmEntityType;
  21:   
  22:                  string actionName = "Get" + declaringType.Name;
  23:                  if (actionMap.Contains(actionName))
  24:                  {
  25:                      // Add keys to route data, so they will bind to action parameters.
  26:                      KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
  27:                      context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;
  28:   
  29:                      KeyValuePathSegment relatedKeySegment = odataPath.Segments[3] as KeyValuePathSegment;
  30:                      context.RouteData.Values[ODataRouteConstants.RelatedKey] = relatedKeySegment.Value;
  31:   
  32:                      return actionName;
  33:                  }
  34:              }
  35:              // Not a match.
  36:              return null;
  37:          }
  38:      }
  39:  }

Notes:

  1. I derive from EntitySetRoutingConvention, because the SelectController method in that class is appropriate for this new routing convention. That means I don’t need to re-implement SelectController.
  2. The convention applies only to GET requests, and only when the path template is “~/entityset/key/navigation/key”.
  3. The action name is “Get{EntityType}”, where {EntityType} is the type of the navigation collection. For example, “GetSupplier”. You can use any naming convention that you like — just make sure your controller actions match.
  4. The action takes two parameters named key and relatedKey. (For a list of some predefined parameter names, see ODataRouteConstants.)

The next step is adding the new convention to the list of routing conventions. This happens during configuration, as shown in the following code:

[!code-csharpMain]

   1:  using ODataRouting.Models;
   2:  using System.Web.Http;
   3:  using System.Web.Http.OData.Builder;
   4:  using System.Web.Http.OData.Routing;
   5:  using System.Web.Http.OData.Routing.Conventions;
   6:   
   7:  namespace ODataRouting
   8:  {
   9:      public static class WebApiConfig
  10:      {
  11:          public static void Register(HttpConfiguration config)
  12:          {
  13:              ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
  14:              // Create EDM (not shown).
  15:   
  16:              // Create the default collection of built-in conventions.
  17:              var conventions = ODataRoutingConventions.CreateDefault();
  18:              // Insert the custom convention at the start of the collection.
  19:              conventions.Insert(0, new NavigationIndexRoutingConvention());
  20:   
  21:              config.Routes.MapODataRoute(routeName: "ODataRoute",
  22:                  routePrefix: "odata",
  23:                  model: modelBuilder.GetEdmModel(),
  24:                  pathHandler: new DefaultODataPathHandler(),
  25:                  routingConventions: conventions);
  26:   
  27:          }
  28:      }
  29:  }

Here are some other sample routing conventions that be useful to study:

And of course Web API itself is open-source, so you can see the source code for the built-in routing conventions. These are defined in the System.Web.Http.OData.Routing.Conventions namespace.



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-routing-conventions.htm
< THANKS ME>