Table of contents

Last updated .

< Previous chapter

The MVC URL Routing

In MVC, there is no direct correspondance between a request (URL) and a file on disc. Instead, requests are routed to controller classes that process the request. You may recall that such public methods of controller classes that handle a request are termed "action" methods. The MVC routing serves two purposes:

URLs

Routing works by matching parts of URL with one or more patterns. This pattern matching is only concerned with the part of the URL that is not the servername or query string. This part of the URL constitutes a path that consists of segments, separated by '/', for example:

http://myserver.com:7001/consulting/mvc/search?iq=high

In this example, there are three segments, namely "consulting", "mvc" and "search". These are the parts of the URL that are considered in routing. However, in addition to the mapping of a URL to a controller class, routing also has a job on the side of fetching parameters from the query string, which are then passed to action methods.

The routing scheme is configured by registering one or more routes. When a request comes in, the MVC framework enumerates the configured routes and uses the first one that matches. This means that it matters in what order the routes are registered. Generally, register the most specific patterns first and the most general ones last. In more technical terms, the routes are configured in the Configure method of the Startup class by calling the Microsoft.AspNet.Routing.IRouteBuilder.MapRoute method. When declaring routes, the route itself is given a name and each segment is identified by a name contained in a set of curly braces. Let's see the one route that is declared when you create a new MVC project in Visual Studio using the Web Application template:

app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });

This code declares a route, which has a name of "default" and a template property. The template is the pattern that the URL of an incoming request is matched with. This particular route matches a URL with three segments, of which the last one is optional, as indicated by the '?' suffix. The segments are named "controller", "action" and "id", respectively. The first two are set up with default values, which will be used, if that segment is missing from the url. Note that words such as "controller" or "action" have no special meaning in relation to routing per se. They only acquire special meaning after a route has been matched and then used by the MVC framework to find a controller to call. The above route will match URLs such as:

A URL with more than three segments would not be matched.

Literal segments

As shown above, you can define a named variable for a segment, but it's also possible to assign a constant value to a segment or part of a segment. For example the template "admin/{controller}/{action}" will match URL's of precisely three segments, of which the first one must be "admin". As another example, consider the template "admin{controller}/{action}". It will match URLs consisting of two segments, where the first segment starts with "admin". So, the URL http://myserver.com/adminhome/index is a match for the template and the value of the resulting route's controller property will be "home".

Redirection to controllers

Instead of defining default values for controller or action inline as part of the template property, they can be defined separately in a defaults property of the route, which takes an anonymous object with the default values. This is less convenient than the inline syntax, but allows you to create a redirect for a route to a controller of a different name. This can be very useful in that it can be used to maintain backward compatibility with old URL schemes. Say, for example, you plan to change the name of the HomeController to NewHomeController. This would break bookmarks and links that customers may already have created to a .../Home/... URL. You can then insert a route as the first one that catches that situation:

app.UseMvc(routes => { routes.MapRoute( name: "OldHome", template: "Home/{action}", defaults: new { controller = "NewHome" }); routes.MapRoute( name: "default", template: "{controller=NewHome}/{action=Index}/{id?}"); });

With these two routes, users requesting a URL such as .../Home/Index would actually be served by the new NewHomeController.

Custom segment variables

In addition to the built-in Controller and Action segment variables that MVC uses to work out which controller action to call, you can define your own. An example of this has already been shown with the custom id variable used for digesting a parameter, passed as a segment in the URL. At runtime, all route values will be available in the RouteData object (of type Microsoft.AspNet.Routing.RouteData). I added a new view to my test app to investigate this. It simply lists the contents of the RouteData.Values dictionary:

Views/Shared/Route.cshtml
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
@model Microsoft.AspNet.Routing.RouteData <!doctype html> <html> <head> <title>RouteData</title> </head> <body> <h2>RouteData:</h2> <ul> @foreach (var key in Model.Values.Keys) { <li><strong>@key</strong>: @Model.Values[key]</li> } </ul> </body> </html>

The very simple controller renders the view, passing on the RouteData property as the model for the view:

Controllers/RouteController.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
using Microsoft.AspNet.Mvc; namespace MVCTutorial.Controllers { public class RouteController : Controller { public IActionResult Index() { return View("Route", RouteData); } } }

The Route controller is targeted with the following route configuration in Startup.Configure:

Startup.cs (partial)
1:
2:
3:
4:
5:
..... routes.MapRoute( name: "default", template: "{controller=Route}/{action=Index}/{someParam?}/{otherParam?}"); .....

The contents of RouteData:

RouteData
RouteData

The RouteData.Values property is of type IDictionary<string, object>, meaning the values are untyped. You'll have to convert to values of the appropiate type. A more elegant approach is that of declaring parameters of the action method of the same name(s) as the route values. MVC's model binding will then try to convert to the specified type. I modified the sample to experiment with this. The controller now has a parameter of the same name as one of the route values I set up in the route configuration. I opted for a decimal type:

Controllers/RouteController.cs (partial)
1:
2:
3:
4:
5:
6:
7:
..... public IActionResult Index(decimal someParam) { ViewData["someParam"] = someParam; return View("Route", RouteData.); } .....

The view now displays the value of the parameter, fetched from the ViewData:

Views/Shared/Route.cshtml (partial)
1:
2:
3:
4:
5:
6:
7:
8:
9:
..... <h2>RouteData:</h2> someParam: @ViewData["someParam"] <ul> @foreach (var key in Model.Values.Keys) { <li><strong>@key</strong>: @Model.Values[key]</li> } </ul> .....

Requesting localhost[port] shows that the someParam value defaults to 0, as you'd expect. Requesting localhost[port]/route/index/17.9 makes the value acquire the value 17.9. The parameter can also be passed as a query string parameter, as the third screen shot shows:

Default values
Default values
RouteData
Value from segment
RouteData
Value from query string

If the same value is passed in a URL segment and in the query string, the segment wins.

< Previous chapter


2016 by Niels Hede Pedersen Linked in