Global route prefixes with attribute routing in ASP.NET Web API

As you may have learnt from some of the older posts, I am a big fan, and a big proponent of attribute routing in ASP.NET Web API.

One of the things that is missing out of the box in Web API’s implementation of attribute routing, is the ability to define global prefixes (i.e. a global “api” that would be prepended to every route) – that would allow you to avoid repeating the same part of the URL over and over again on different resources. However, using the available extensibility points, you can provide that functionality yourself.

Let’s have a look.

RoutePrefixAttribute

ASP.NET Web API allows you to provide a common route prefix for all the routes within a controller via RoutePrefixAttribute. This is obviously very convenient, but unfortunately that attribute, out of the box, cannot be used globally.

Here’s a typical use case per controller:

Of course in use cases like the one above, it would be nice to have to avoid that “api” repeated on every single controller.

Applying a route prefix globally

In order to apply route prefix globally, you will need to customize the way ASP.NET Web API creates routes from the route attributes. The way to do that, is to implement a custom IDirectRouteProvider, which can be used to feed custom routes into the Web API pipeline at application startup.

That would typically mean a lot of work though, since you would need to manually deal with everything from A to Z – like manually processing controller and action descriptors. A much easier approach is to subclass the existing default implementation, DirectRouteProvider, and override just the method that deals with route prefixes (GetRoutePrefix). Normally, that method will only recognize route prefixes from the controller level, but we want it to also allow global prefixes.

DirectRouteProvider exposes all of its members as virtual – allowing you to tap into the route creation process at different stages i.e. when reading route prefixes, when reading controller route attributes or when reading action route attributes. This is shown in the next snippet:

The CentralizedPrefixProvider shown above, takes a prefix that is globally prepended to every route. If a particular controller has it’s own route prefix (obtained via the base.GetRoutePrefix method invocation), then the centralized prefix is simply prepended to that one too.

Finally, the usage of this provider is as follows – instead of invoking the “regular” version of MapHttpAttributeRoutes method, you need to use the overload that takes in an instance of IDirectRouteProvider:

In this example, all attribute routes would get “api” prepended to the route template.

In more interesting scenarios, you can also use parameters with CentralizedPrefixProvider – after all, we are just building up a regular route template here. For example, the following set up, defining a global version parameter as part of every route, is perfectly valid too:

Now in any action method signature, you can add an additional version integer parameter, even though you don’t have it in the route template beside the action – because it will be propagated down from the CentralizedPrefixProvider. For example:

And obviously – the “api” in the controller level RoutePrefixAttribute is no longer needed too.

Be Sociable, Share!

  • Luca Ghersi

    Hi Filip, thanks for the code! It’s very useful!
    Just one thing: I guess you wrote the String.Format parameters inside GetRoutePrefix in the wrong order. It should be String.Format(“{0}/{1}”, _centralizedPrefix, existingPrefix) :)

    • http://www.strathweb.com/ Filip W

      you are absolutely right! thanks – corrected now

  • http://rehansaeed.com Muhammad Rehan Saeed

    Any idea how this would work for ASP.NET 5, the
    DirectRouteProvider has been removed (See https://github.com/aspnet/Mvc/issues/3107).

  • Søren Reinke

    Excellent article. :)

    Could you please look into why the page can’t be printed from Chrome? Your left side menu ruins everything in IE 11 also.

  • S. Wright

    I was previously using .Map(“/api”, app => {}) to configure my Owin pipeline out of of the IIS app pipeline since I wanted it separated from the IIS app it is currently integrated with. Unfortunately the IIS app is reliant on Session State so I had to bring my Web Api app back in so I could get access to Session with httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);. Long story short, I now needed to prefix all my routes with /api and this technique worked wonderfully.

    Thanks!

    • http://www.strathweb.com/ Filip W

      thanks!

  • http://jasonwatmore.com/ Jason Watmore

    Thanks for the tip, it works well.

  • Ismayil Malik

    awesome! just used it))