When working with attribute routing in Web API 2 or MVC 5 it was relatively easy to get the route to the controller and the controller name out of sync. That was because the route always had to be specified as a string, so whenever you changed the name of the controller you would always have to change the string in the route attribute too.
That could be easily forgotten - especially if you use refactoring tools of Visual Studio or an external refactoring plugin.
This issue has been addressed in MVC6 with a tiny addition - the introduction of [controller] ad [action] tokens into attribute routing.
The problem π
In a typical Web API project (actually, MVC as well), you might have a controller like this:
[Route("api/hello")]
public class HelloController : Controller
{
[Route]
public string Get()
{
return "hello";
}
}
If, for whatever reason, you were working with Remote Procedure Calling instead of RESTful controllers, you might have something like this:
[Route("api/hello")]
public class HelloController : Controller
{
[Route("GetHello")]
public string GetHello()
{
return "hello";
}
}
In either case, the relationship between the controller and its route, and the action and its route, were non-existant - purely relying on the fact that you maintain them by hand. (note, if you read my Web API book you might be aware of a workaround).
The solution in MVC6 π
By using the new [controller] token in your attribute routes you can ensure that the controller name in the route, is kept in sync with the name of the controller class. In the example below, [controller] will always be expanded to the name of the controller regardless of any future refactorings - in this case it will be Hello.
Route("api/[controller]")]
public class HelloController : Controller
{
[Route]
public string Get()
{
return "hello";
}
}
The same principle applies to an [action] token - which is going to be expanded to the name of the action. This is obviously relevant for RPC-types APIs only.
For example the setup below will match the URL such as /api/hello/GetHello/1 and so on.
[Route("api/[controller\]")]
public class HelloController : Controller
{
[Route("[action]/{id:int}")]
public string GetHello(int id)
{
return "hello " + id;
}
}
How does it work under the hood?
ASP.NET MVC will use a class called AttributeRouteModel and its public static method ReplaceTokens to replace the tokens with the values from route defaults.
Since the controller and action names are guaranteed to be in the route dictionary defaults, the tokens are certain to be expanded correctly. This process happens only once - at application startup, for all routes, from within the ControllerActionDescriptorBuilder class and its Build method.
If you go a step earlier in the MVC setup process, the builder itself is called from within ControllerActionDescriptorProvider which is an IActionDescriptorProvider. The purpose of the provider is to provide a set of action descriptors which represent all callable HTTP endpoints of your application. The provider technically can be customized/replaced - however, this shouldn’t be done. As Yishai from the MVC team pointed out, they actually recommend using the ApplicationModel to customize the ActionDescriptors instead.