Problem
How does routing work in ASP.NET Core MVC.
Solution
In an empty project, update Startup
class to add services and middleware for MVC:
public void ConfigureServices(
IServiceCollection services)
{
services.AddMvc();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "goto_one",
template: "one",
defaults: new { controller = "Home", action = "PageOne" });
routes.MapRoute(
name: "goto_two",
template: "two/{id?}",
defaults: new { controller = "Home", action = "PageTwo" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Add a controller HomeController
, to demonstrate conventional routing (see discussion):
Add a controller WorkController
, to demonstrate attribute routing (see discussion):
Discussion
Routing in ASP.NET Core MVC is the mechanism through which incoming requests are mapped to controllers and their actions. This is achieved by adding Routing middleware to the pipeline and using IRouteBuilder
to map URL pattern (template) to a controller and action.
Routing Templates
Routing templates use literals and tokens (for routing parameters). Literals are matched exactly to the text in URL whereas tokens are replaced when matching a route.
To match a template, it must contain controller and action tokens as this is the key information MVC uses to locate controller/action. Other tokens in the URL are mapped to parameters of action methods using Model Binding.
When adding a route mapping, default values could be provided for tokens. This is useful when templates don’t contain controller or action tokens. Templates can also have optional tokens for action parameters.
Let’s look at an example template to understand the above points:
contact/{controller=Home}/{action=Index}/{id?}
Notice the following points:
- Tokens are enclosed in curly braces
{}
. We have 3 tokens here, controller, action and id
.
- We also have a literal “
contact
” that will be matched to URL text.
- Default values are provided for
controller
(Home
) and action
(Index
).
- Optional tokens are declared using question mark
?
.
Following URLs will match this template:
- /contact/Home/Index/1: Values provided for all tokens
- /contact/Home/Index: Optional token omitted
- /contact/Home: Default of Index used for action
- /contact: Default of Home and Index used for controller and action
Conventional Routing
Conventional Routing establishes a convention for the URL path, such that, given a template:
- First token maps to a controller
- Second token maps to an action
- Third token maps to an optional
id
action parameter
You could omit the controller and action from the template, as long as you provide default values for them. For instance in the below route will map URL /one
because defaults are supplied for required controller and action tokens:
routes.MapRoute(
name: "goto_one",
template: "one",
defaults: new { controller = "Home", action = "PageOne" });
Note: Add such specific routes before the general rule because routes are processed in order, as soon as a match is found the matching process ends.
Since routing middleware only uses controller and action tokens to map/execute an action, having multiple actions in a controller with the same name will throw an ambiguous exception. To resolve this issue, IActionConstraint
attributes (e.g. HttpGet, HttpPost
etc.) can be applied on actions:
Attribute Routing
Attribute Routing map URLs by applying routing template directly on the controller and action. The existence of controller and action in the template is not mandatory for attribute routing, as they don’t play any part in routing process.
We can use [Route]
or [HttpGet]
(and other verbs) attributes to specify templates. These templates can also have literals and tokens (except for controller and action tokens).
Attributes applied on the controller are merged with those on an action e.g. in the WorkController
, PageOne
action can be accessed via /work/one
URL:
URL Generation
Rather than hard-coding URL in our application, we can take advantage of MVC’s routing mechanisms to generate URLs. MVC has all the information to do so in the form of templates you supplied for mapping routes.
MVC provides IUrlHelper
interface to abstract away the capability for URL generation. This is exposed via Url
properties of controller base class, views and view components.
Two key methods of IUrlHelper
to produce URLs are:
Action
: By supplying action, controller and route values
RouteUrl
: By supplying action name and route values
If controller or route values are missing from the method’s parameters, MVC will pick up these from current request or method parameters (aka ambient values). The below method is in MobileController
:
Route values are supplied as anonymous object:
If MVC can’t map these values to URL tokens, these are concatenated with the URL as query string
parameters:
For attribute routing, the URL is constructed by looking at the [Route]
provided on an action.
A convenience method on ControllerBase
class is RedirectToAction
that uses URL generation to produce an action result, which redirects user request:
IUrlHelper
To inject IUrlHelper
as a dependency, you’ll need to first add it to the service container:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(factory =>
{
var actionContext = factory.GetService<IActionContextAccessor>()
.ActionContext;
return new UrlHelper(actionContext);
});
Sample
The sample source code has three controllers to demonstrate conventional routing, attribute routing and URL generation. The best way to understand routing would be to download the code and play with it, altering templates to see how routing works.