So far, this chapter has focused mostly on how routes match incoming request URLs, which is the primary responsibility for routes. Another responsibility of the Routing system is to construct a URL that corresponds to a specific route. When generating a URL, a request for that generated URL should match the route that was selected to generate the URL in the first place. This allows Routing to be a complete two-way system for handling both outgoing and incoming URLs.
In principle, developers supply a set of route values that the Routing system uses to select the first route that is capable of matching the URL.
At its core, the Routing system employs a very simple algorithm over a simple abstraction consisting of the RouteCollection and RouteBase classes. Before digging into how Routing interacts with the more complex Route class, let's first look at how Routing works with these classes.
A variety of methods are used to generate URLs, but they all end up calling one of the two overloads of the RouteCollection.GetVirtualPath method. The following code shows the method signatures for the two overloads:
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values)
The first method receives the current RequestContext and user-specified route values (dictionary) used to select the desired route.
The second method accepts a third argument, the route name. Route names are unique within the route collection — no two routes can have the same name. When the route name is specified, the route collection doesn't need to loop through each route. Instead, it immediately finds the route with the specified route name and moves to step 2. If that route doesn't match the specified parameters, then the method returns null and no other routes are evaluated.
The Route class provides a specific implementation of the preceding high-level algorithm.
In some scenarios, URL generation makes use of values that were not explicitly supplied to the GetVirtualPath method by the caller. Let's look at a scenario for an example of this.
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("tasks", "{controller}/{action}/{page}", new {controller="tasks", action="list", page=0 }); }
@Html.ActionLink("Page 2", "List", new {controller="tasks", action="List", page = 2})
tasks/list/2
Key | Value |
Controller | Tasks |
Action | List |
Page | 2 |
@Html.ActionLink("Page 2", "List", new { page 2})
Even though the call to ActionLink supplied only the page parameter, the Routing system used the ambient route data values for the controller and action when performing the route lookup. The ambient values are the current values for those parameters within the RouteData for the current request. Explicitly supplied values for the controller and action would, of course, override the ambient values. To un-set an ambient value when generating a URL, specify the key in the dictionary of parameters and have its value set to either null or an empty string.
Overflow parameters are route values used in URL generation that are not specified in the route's definition. By definition, we mean the route's URL, its defaults dictionary, and its constraints dictionary. Note that ambient values are never used as overflow parameters.
Overflow parameters used in route generation are appended to the generated URL as query string parameters. Again, an example is most instructive in this case. Assume that the following default route is defined:
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
Suppose you're generating a URL using this route and you pass in an extra route value, page = 2. Notice that the route definition doesn't contain a URL parameter named “page.” In this example, instead of generating a link, you'll just render out the URL using the Url.RouteUrl method.
@Url.RouteUrl(new {controller="Report", action="List", page="123"})
The URL generated will be /Report/List?page=2. As you can see, the parameters we specified are enough to match the default route. In fact, we've specified more parameters than needed. In those cases, those extra parameters are appended as query string parameters. The important thing to note is that Routing is not looking for an exact match when determining which route is a match. It's looking for a sufficient match. In other words, as long as the specified parameters meet the route's expectations, it doesn't matter if extra parameters are specified.
Let's assume that the following route is defined:
public static void RegisterRoutes(object sender, EventArgs e) { routes.MapRoute("report", "reports/{year}/{month}/{day}", new {day = 1} ); }
Here are some results of some Url.RouteUrl calls that take the following general form:
@Url.RouteUrl(new {param1 = value1, parm2 = value2, ..., parmN, valueN})
Parameters and the resulting URL are shown in Table 9.7.
Parameters | Resulting URL | Reason |
year=2007, month=1, day=12 | /reports/2007/1/12 | Straightforward matching |
year=2007, month=1 | /reports/2007/1 | Default for day = 1 |
Year=2007, month=1, day=12, category=123 | /reports/2007/1/12?category=123 | “Overflow” parameters go into query string in generated URL |
Year=2007 | Returns null | Not enough parameters supplied for a match |