Part 11 – OData v4 in ASP.NET WebApi – Route Attributes

This is a series of blogs detailing my introduction to using OData v4 with C# and ASP.NET WebApi.

The source code for the project can be found here.

Conventional Routes

I mentioned in previous blogs that if we name our controllers and actions in a specific way, an OData request will locate the right action.

The conventions used can be read here.

What happens if we want to create our own naming convention that breaks these rules? In that case we need to tell the framework how to route our requests and one way is using attributes.

Attribute Routing

Before I start I need to point out something very important. Make sure the OData routes make sense to the client. Controller and Action names only make sense to the developer.

Up to now we have defined a Cars entity. Cars is plural and that makes sense because we have a collection of cars. Our Cars controller is called CarsController. This follows the convention Controller

In our cars controller we have an action called Get which returns a collection of cars. These two map nicely onto the Url http://Cars

What if we want to change the name of our controller and action? Well we need to keep http://Cars because that makes sense to the client.

Creating the Project

The Project is called Part11

Enabling Attribute Routing

The first configuration we need to perform is registering the service that enables attribute routing. In the App_Start\WebApiConfig.cs we need to add the IODataRoutingConvention service as follows.

public static void Register(HttpConfiguration config)
   // OData Routing
   var model = GetEdmModel();

   config.MapODataServiceRoute("OData", "OData", b => b
      .AddService(ServiceLifetime.Singleton, s => model)
      .AddService(ServiceLifetime.Singleton, s => ODataRoutingConventions.CreateDefaultWithAttributeRouting("OData", config))
      .AddService(ServiceLifetime.Singleton, s => new AlternateKeysODataUriResolver(model))


Renaming an Action

Let’s change our first Get action to GetAllCars.

public IQueryable GetAllCars()
   return _repo.GetCars();

From Postman issue a GET request with http://localhost:40000/OData/Cars to fetch all cars.

The request fails and the error actually tells you what the issue is. So let’s add the ODataRoute attribute.

public IQueryable GetAllCars()
   return _repo.GetCars();

Issue the same GET request.

The ODataRoute attribute just takes a path template which is appended to the service root.

Renaming a Controller

What happens if we change our controller to Vehicle.

public class Vehicle : ODataController

From Postman issue the GET request http://localhost:40000/OData/Cars

We get the following error.

We need to do a number of things to keep everything working

  • Our controller has to have the Controller suffix. So we need to call it VehicleController.
  • In order to indicate that our Vehicle controller is our Cars controllers, we need to add a class attribute [ODataRoutePrefix(“Cars”)]. This actually has a side effect that will cause all routes to be prefixed with Cars.
  • On all Car actions we need to remove Cars from the attribute, otherwise the path becomes …/Cars/Cars(….

We also need to add an attribute to any methods that currently do not have one.

The finished controller code looks like this.

public class VehicleController : ODataController
   private Repository _repo;

   public VehicleController()
      _repo = new Repository();

   // OData/Cars
   public IQueryable GetAllCars()
      return _repo.GetCars();

   // OData/Cars(Make='Vauxhall', Model='Zafira')
   [ODataRoute("(Make={KeyMake}, Model={KeyModel})")]
   public Car Get([FromODataUri] string KeyMake, [FromODataUri] string KeyModel)
      return _repo.GetCar(KeyMake, KeyModel);

   // OData/Cars(Category='4x4')
   public IQueryable GetCarByCategory([FromODataUri]string Category)
      return _repo.GetCars(Category);

Custom Routing

If you find the routing attributes do not address all your needs you can create your own custom routing class. I am not going to do that but I will explain the following.

  • If you need to locate the controller and action, create a class that inherits from IODataRoutingConvention. You will need to implement SelectController and SelectAction.
  • If you need to locate just the action, create a class that inherits from NavigationSourceRoutingConvention. You will need to implement SelectAction only.

Then add the custom class when defining the route configuration.


In the next blog I will look at selecting properties.