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.

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:

C#

1

2

3

4

5

6

7

namespaceMicrosoft.AspNet.Mvc.ApplicationModels

{

publicinterfaceIApplicationModelConvention

{

voidApply([NotNullAttribute]ApplicationModel application);

}

}

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.

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.

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

publicclassTypedRoute:AttributeRouteModel

{

publicTypedRoute(stringtemplate)

{

Template=template;

HttpMethods=newstring[0];

}

publicTypeInfoControllerType{get;privateset;}

publicMethodInfoActionMember{get;privateset;}

publicIEnumerable<string>HttpMethods{get;privateset;}

publicTypedRoute Controller<TController>()

{

ControllerType=typeof(TController).GetTypeInfo();

returnthis;

}

publicTypedRoute Action<T,U>(Expression<Func<T,U>>expression)

{

ActionMember=GetMethodInfoInternal(expression);

ControllerType=ActionMember.DeclaringType.GetTypeInfo();

returnthis;

}

publicTypedRoute Action<T>(Expression<Action<T>>expression)

{

ActionMember=GetMethodInfoInternal(expression);

ControllerType=ActionMember.DeclaringType.GetTypeInfo();

returnthis;

}

privatestaticMethodInfo GetMethodInfoInternal(dynamicexpression)

{

varmethod=expression.Body asMethodCallExpression;

if(method!=null)

returnmethod.Method;

thrownewArgumentException("Expression is incorrect!");

}

publicTypedRoute WithName(stringname)

{

Name=name;

returnthis;

}

publicTypedRoute ForHttpMethods(paramsstring[]methods)

{

HttpMethods=methods;

returnthis;

}

}

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.

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.

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?

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

all the code on this blog is MIT

Cole Mickens

Great! Just curious, as I’d been wanting strongly typed routing with MVC6 and was dismayed to see that Annotation-only seemed the way forward.

I had to make a change or two, as some property names have changed, but I’m successfully using this code with DNX Beta 4. Thanks!

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…