Strongly typed routing for ASP.NET MVC 6 with IApplicationModelConvention

This is something I hacked together last night, but it was a very interesting exercise into customizing the new (or rather, future) ASP.NET MVC 6 to suit your needs.

If you visit this blog from time to time, some time ago I blogged about building strongly typed routing provider for ASP.NET Web API (code is here). That was built around extensibility points provided by the direct routing mechanism (better known as direct routing’s default implementation – attribute routing).

So I thought, it would be fun to port this solution to MVC 6. However, while MVC 6 supports attribute routing, it does not provide the same abstractions over the routing mechanism. Instead it exposes something new for both MVC and Web API developers – IApplicationModelConvention, which is what we’ll use here.

Background

If you skim through the old Web API post you’ll get the idea, but nevertheless, briefly – imagine we want to have something like this:

This is effectvely attribute routing – since we have one route definition pointing to a specific action (no catch all or catch many definitions that you might have in regular routing) – except its centralized.

In fact I noticed the MVC team is actually considering supporting something like this themselves.

IApplicationModelConvention

IApplicationModelConvention is the recommended way to customize the framework to your needs. While in Web API or MVC, in order to perform any more advanced customization, you’d have to replace various internal services – potentially breaking other things in the process (example: custom Web API action selector typically would break the API explorer), in MVC 6 IApplicationModelConvention is a one stop shop for convenient customizations.

The interface is very simple:

With the ApplicationModel exposing all controllers and filters that have been discovered by MVC. Then you can iterate through them, and access all discovered actions, action parameters and all useful framework components like that.

You can then plug it in against your MVC options object in the Startup class of your MVC application, and the convention will applied by the MVC runtime as your application is started.

Just to give you example – we used a custom IApplicationModelConvention in the Omnisharp project to apply HttpPost attribute to all actions automagically.

Building up typed routing on top of IApplicationModelConvention

So, kind of similarly to what happened in the above mentioned Omnisharp example, we’ll use a custom convention to apply attribute routing to all of the actions a user specifies in the typed routing setup.

So let’s start by defining our TypedRoute class – which will represent the typed route model. It should inherit from AttributeRouteModel, the standard way of MVC 6 for storing attribute route information.

The class is virtually identical to the original Web API one, with a couple of small differences. Since pretty much all MVC 6 internals deal with TypeInfo rather than Type, we use that too. The crucial properties – Template (for the route template) and Name (for the route name) are actually defined in the base AttributeRouteModel for us.

We also have a tiny fluent API that allows us to specify HTTP Methods relevant for a given route and the name of the route. The rest is rather self explanatory.

Next step is to create our IApplicationModelConvention. We’ll store the route dictionary there, and use it when iterating through different controllers to annotate its actions with appropriate attribute routing.

The routes will be stored in a dictionary where the key is TypeInfo (a controller) and a value is a list of our TypedRoute instances – each pointing to a different method.

Inside our application model convention we iterate through the controllers, look up the corresponding entry in our Routes dictionary. Then we try to match the action on the controller with the action inside the Routes dictionary entry for that controller.

Finally, we assign our route (remember it derives from AttributeRouteModel) to the property AttributeRouteModel on the action and copy the supported HTTP methods. Should our route have no default HTTP methods, MVC 6 will treat is as if it supported all of them for us.

Adding extension methods to allow registration

So we have our infrastructure set up – we have a custom IApplicationModelConvention and a TypedRoute model which extends the default AttributeRouteModel. All that’s left at this point is to add some plumbing that will allow the user to get his routes into our Routes dictionary.

Since we wanted to to do this off the MvcOptions object (see our introduction in the post), let’s add the extension methods there.

There isn’t really much exciting stuff going on here:

  • we have EnableTypedRouting method which will register our TypedRoutingApplicationModelConvention into opts.ApplicationModelConventions.Add
  • we have TypedRoute method which is the generic version (for all HTTP verbs)
  • we have 4 versions of methods allowing to register GET, POST, PUT, DELETE methods with a shorthand syntax. Same is available through the main TypedRoute too, so this is just for convenience

And that’s everything. As a final touch we can throw in a simple Params class to ignore the action arguments (those are defined by the template anyway):

And that’s everything, we can now use our typed routing like we wished all along:

This creates:

  • – a GET route to /homepage
  • – a GET route to /aboutpage/{name}
  • – a POST route to /sendcontact

All of which go against the relevant methods on our HomeController. Since the API is fluent, you can also give the routes names so that you can use them with i.e. link generation.

And that’s about it – a 20 min hack to get typed routing into MVC6. As such – disclaimer here – it’s not perfect, but it demonstrates the usage of IApplicationModelConvention for doing interesting MVC customizations.

Be Sociable, Share!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1818()

  • Pingback: Dew Drop – March 12, 2015 (#1973) | Morning Dew()

  • clement_911

    Nice one. While on the strongly typed routes topic, is there something similar to T4MVC builtin MVC6? It’s one thing to define routes with types, but it would be good to be able to build links with types too…

  • Tyson Benson

    Very good, can you suggest how you would provide a type safe way to register routes for controllers in areas? Also, can I get the absolute url from an instance of TypedRoute? One more thing, in the route template for the typed attribute, can I use {controller} and {action} like in ASP.NET 5 AttributeRoutes?

  • Cole Mickens

    Do you have a license in mind for this code?

  • Aurelia

    I would be interested to see how to such strong typing (which I applaud) could be reconciled with Microsofts move to Tag Helpers e.g. html attributes such as asp-controller=”home”`, which are encouraging magic strings…

  • Ивайло Кенов

    I have used the code in this article to build a NuGet package and referenced the blog post from the source code: https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting I hope you don’t mind :)

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

      awesome, I love it!

      • Ивайло Кенов

        Thank you for sharing it on twitter :)