Under the Hood: How Routes Generate URLs

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.


Product Team Aside
Let's take a moment and examine those two sentences. “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.” This is the point where the difference between Routing and URL rewriting becomes clear. Letting the Routing system generate URLs also separates concerns between not just the model, the view, and the controller, but also the powerful but silent fourth player, Routing.

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.

High-Level View of URL Generation

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.

1. The route collection loops through each route and asks, “Can you generate a URL given these parameters?” via the Route.GetVirtualPath method. This is similar to the matching logic that applies when matching routes to an incoming request.
2. If a route answers that question (that is, it matches), it returns a VirtualPathData instance containing the URL as well as other information about the match. If not, it returns null, and the Routing system moves on to the next route in the list.

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.

A Detailed Look at URL Generation

The Route class provides a specific implementation of the preceding high-level algorithm.


Simple Case
This is the logic most developers encounter when using Routing and is detailed in the following steps:
1. Developer calls a method such as Html.ActionLink or Url.Action. That method, in turn, calls RouteCollection.GetVirtualPath, passing in a RequestContext, a dictionary of values, and an optional route name used to select the correct route to generate the URL.
2. Routing looks at the required URL parameters of the route (URL parameters that do not have default values supplied) and makes sure that a value exists in the supplied dictionary of route values for each required parameter. If any required parameter does not have a value, URL generation stops immediately and returns null.
3. Some routes may contain default values that do not have a corresponding URL parameter. For example, a route might have a default value of pastries for a key named category, but category is not a parameter in the route URL. In this case, if the user-supplied dictionary of values contains a value for category, that value must match the default value for category. Figure 9.2 shows a flowchart example.
4. Routing then applies the route's constraints, if any. See Figure 9.3 for each constraint.
5. The route is a match! Now the URL is generated by looking at each URL parameter and attempting to fill it with the corresponding value from the supplied dictionary.

Ambient Route Values

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.


Simple Case
Suppose that you want to display a large list of tasks. Rather than dumping them all on the page at the same time, you may want to allow users to page through them via links. For example, Figure 9.4 shows a very simple interface for paging through the list of tasks.
The Previous and Next buttons are used to navigate to the previous and next pages of data, respectively, but all these requests are handled by the same controller and action.
The following route handles these requests:
public static void RegisterRoutes(RouteCollection routes)
{
  routes.MapRoute("tasks", "{controller}/{action}/{page}",
      new {controller="tasks", action="list", page=0 });
}
In order to generate links to the previous and next pages, you'd typically need to specify all the URL parameters in the route. So, to generate a link to page 2, you might use the following code in the view:
@Html.ActionLink("Page 2", "List",
 new {controller="tasks", action="List", page = 2})
However, you can shorten this by taking advantage of ambient route values. The following is the URL for page 2 of the list of tasks.
tasks/list/2
Table 9.6 shows the route data for this request.

Table 9.6 Route Data

Key Value
Controller Tasks
Action List
Page 2
To generate the URL for the next page, you only need to specify the route data that will change in the new request:
@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

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.

More Examples of URL Generation with the Route Class

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.

Table 9.7 Parameters and Resulting URL for GetVirtualPath

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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset