Global route prefixes with attribute routing in ASP.NET 5 and MVC 6

Β· 673 words Β· 4 minutes to read

In the last post a few days ago we looked at adding a centralized route prefix to attribute routing in ASP.NET Web API.

I got a couple of follow up question about how to achieve the same in ASP.NET 5 and MVC 6 framework. Let’s have a look then (btw: this article is written using beta8 version of ASP.NET 5).

Update June 2016 πŸ”—

This post was written for ASP.NET Core beta8 and is now out of date. Please have a look at the updated version of the post.

Attribute routing in MVC 6 πŸ”—

First of all, the attribute routing in MVC 6 is much different then the implementation in Web API. The implementation details are no longer exposed, and there is no interface similar to IDirectRouteProvider which allows you to modify the routing behavior.

Instead, the encouraged way of adapting MVC 6 to your needs (and not just for routing - but for all other configuration scenarios) is to use conventions.

If you follow this blog, I have already blogged about that, in an article where we were building strongly typed routing using application conventions.

Using the same technique, we can support a centralized route prefix:

public class RouteConvention : IApplicationModelConvention  
{  
public void Apply(ApplicationModel application)  
{  
foreach (var controller in application.Controllers)  
{  
//customize attribute routes on each controller here  
}  
}  
}  

Another thing worth noting, is that in ASP.NET Web API, we had RouteAttribute - for defining a route on an action, and RoutePrefixAttribute for defining controller-wide prefixes.

In MVC 6, it’s a little simpler because there is only one RouteAttribute - if you put one ona controller, and there is another on an action in that controller, they will simply get merged together to produce a single route template. This has a de facto net result that’s the same as with RoutePrefixAttribute in Web API.

Creating RouteConvention πŸ”—

Since we have already established we need a custom IApplicationModelConvention, let’s create it. We’ll need to loop through all controllers.

Each controller that we loop through, will expose its attribute routing information via AttributeRoutes collection. This is a collection of objects describing the route metadata and the outline of a single item is shown below.

public class AttributeRouteModel  
{  
public AttributeRouteModel();  
public AttributeRouteModel([NotNullAttribute] AttributeRouteModel other);  
public AttributeRouteModel([NotNullAttribute] IRouteTemplateProvider templateProvider);

public IRouteTemplateProvider Attribute { get; }  
public bool IsAbsoluteTemplate { get; }  
public string Name { get; set; }  
public int? Order { get; set; }  
public string Template { get; set; }

public static AttributeRouteModel CombineAttributeRouteModel(AttributeRouteModel left, AttributeRouteModel right);  
public static string ReplaceTokens(string template, IDictionary<string, object> values);  
}  

Based on all that information, our approach and convention can be:

  • loop through all controllers
  • determine if it has any attribute routes
  • and if that’s the case, apply the central route prefix, otherwise, we create a new list of attribute routes just adding the central prefix as the only controller-level attribute route
public class RouteConvention : IApplicationModelConvention  
{  
public void Apply(ApplicationModel application)  
{  
var centralPrefix = new AttributeRouteModel(new RouteAttribute("api"));  
foreach (var controller in application.Controllers)  
{  
if (controller.AttributeRoutes.Any())  
{  
for (var i = 0; i < controller.AttributeRoutes.Count; i++) { controller.AttributeRoutes[i] = AttributeRouteModel.CombineAttributeRouteModel(centralPrefix, controller.AttributeRoutes[i]); } } else { controller.AttributeRoutes.Add(centralPrefix); } } } }

You can apply the central prefix using AttributeRouteModel.CombineAttributeRouteModel static method. It simply takes a left attribute route and right attribute route and merges them together.

And that’s it. You can can enable this convention in your Startup class, when calling AddMvc.

{  
// Add MVC services to the services container.  
services.AddMvc(opt => opt.Conventions.Insert(0, new RouteConvention()));  
}  

If you recall the Web API article from a few days ago, we capped it off with a slightly more interesting scenario, where we had a central version prefix.

The same could be achieved here, you can add any route parameter into the central prefix i.e.:

var centralPrefix = new AttributeRouteModel(new RouteAttribute("api/v{version}"));  

And you now have access to that version in any controller action you create:

public class ItemController : Controller  
{  
[Route("item/{id}")]  
public Item GetById(int id, int version)  
{  
//do stuff with version and id  
}  
}  

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github and on Mastodon.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP