System.Web.Routing URL matching and generation

by grendel 25. May 2009 14:50

Recently .NET got a new feature - System.Web.Routing - which is a general purpose routing engine used at this point (within the realms of .NET class library) by System.Web.Mvc and System.Web.DynamicData namespaces. Most of System.Web.Routing is pretty straight-forward and easy to understand, but there's one pretty poorly documented feature (albeit a very important one, not to say crucial) - the route URLs.

During the past week was fixing several bugs in the Mono implementation of System.Web.Routing engine, all of which were related to route URL parsing and virtual path generation. At the beginning of the week I thought the task would be quick and easy, but it turned out that documentation of some aspects of the subject is few and far between and, in effect, correct implementation of the support took me the whole week. Since the feature is important for both the implementor of the namespace as well as for the end user, this blog entry attempts to collect and explain all the details of the subject.

URLs are used for two purposes in System.Web.Routing - for checking if a request matches a route (in this instance the route URL is considered to be specified using a simple pattern DSL) and for generating virtual paths (in this instance the route URL is considered to be a replacement pattern for the virtual path).

Note: all of the information below has been acquired through tests and whatever little documentation is available on the subject, I saw no source code for .NET's version (we never do that at Mono, as you probably know) so there might be errors and mistakes in the description. If you find one, please let me know (or better yet - create a simple test case which demonstrates the failure and file a bug for Mono's Sys.Web component. If you're interested in the tests we have, feel free to take a look

From the two, route matching is better documented, but let me outline the rules which the matching URLs follow:

  • URL must not start with either ~ or / characters
  • URL must not contain the ? character.
  • Each URL is built from zero or more segments separated with the / character
  • Each segment may contain any number of sections enclosed in the { and } characters.
  • Within one segment, no two sections can be adjacent, they must be separated with literal string of characters.
  • No section name must be empty. Empty in this case is understood as "not containing any characters" - you can have a section name consisting only of whitespace (including tabs and newlines).
  • Sections are parsed using hungry matching - that is, they will consume all the text from their starting point all the way to the start of the last ocurrence of the following literal or to the end of input, whichever comes first.
  • A segment may contain a catch-all section which is marked by putting * as the first character of the section name. Catch-all sections must come alone within the segment and they are valid only in the last segment of the URL

This is a pretty simple set of rules and is easy to implement (see PatternParser.cs in Mono repository)

 

Let's pass to the other part of the subject, virtual path generation. The theory seems simple here - take the original route URL and replace it with provided values. The simple recipe has embedded traps, however. Once you discover the rules, they're easy and the algorithm is clear, but discovering them is, as it turns out, not so easy today. Thanks to Atushi Enomoto's work on implementing our System.Web.Routing there already was basic support for the parsing/matching/generating operations which covered the most common cases. It didn't work correctly for the more obscure and less frequently encountered scenarios. There was very little meaningful information on the web I could find on those and, by far, the most valuable piece of information came from the "ASP.NET MVC 1.0" book (Chapter 4). All gathered together, however, it still didn't cover everything. What you can see below is a conglomerate of the above information with knowledge learned from tests.

There are several terms you need to be familiar with in order to understand what's going on:

Required values
Values which are found in the route URL (i.e. sections) which don't have defaults provided
Default values
Values provided by the programmer in the route's Default dictionary.
Ambient values
Values gathered by the routing engine from the current request. They are accessible via RequestContext.RouteData.Values
Constraints
Programmer can provide the routing engine with a set of per-route constraints. Constraints may be presented in two flavors - either strings, which are treated as regular expressions, or instances of the IRouteConstraint interface. Those values are consulted when generating the virtual path.
Overflow values
Those are the values provided explicitly in the call to Route.GetVirtualPath that are not in the route URL and also are not specified in the Defaults and Constraints dictionaries attached to the Route class. Such values are appended as query parameters to the generated virtual path.

Given the information above, the rules for virtual path generation are outlined below. This is where it becomes a bit entangled.

  • If there are any required parameters in the current route, a check is made whether all of them are present in the dictionary passed by user to the GetVirtualPath method.
    • If any are missing, there are several rules before we decide it's a mismatch:
      • If there are no defaults
      • or the defaults dictionary contains at least one value used in the route URL
      • and the user values do not contain any value used in the route URL,
      • then the code is allowed to check for the required value in the ambient values. If the value is not found there, we have a mismatch.
      • If the above four conditions are not met and the value is not found in the user values, we have a mismatch
    • If all of them are present, checks are performed to see whether
      • there are any default values which are not used in the route URL.
      • If any such entries are found, another check is made to make sure that any corresponding values found in the user-passed dictionary have exactly the same value as specified in the defaults. If the check fails, we have a mismatch.
      • Further on, the code looks whether there are any constraints defined for the route and, if yes, iterates over the dictionary to make sure that all of the constraints are met.
      • If there are no errors, we have a match and we can start generating the virtual path.

This completes the preliminary checks whether the route qualifies for virtual path generation in the context of the current request.

 

The actual virtual path code generation can begin now. Code generating the virtual path can trim the result if by skipping continuous block of values at the end of the URL if all of the values are defaults. After this process is done, code looks for the overflow values and appends them to the resulting virtual path as query parameters. Both names and values in the query are encoded using Uri.EscapeDataString.

This concludes the virtual path generation process. Hope somebody finds this description useful :)

Tags:

Mono | ASP.NET

Comments (3) -

Amir Shimoni
Amir Shimoni
5/28/2009 11:10:41 AM #

Great job. We are now that much closer to running real ASP.net MVC applications on Mono.

Reply

grendel
grendel
5/29/2009 4:44:35 AM #

Thanks, but let me say that ASP.NET MVC applications already run on Mono.

Reply

Thresa Rhoda
Thresa Rhoda
3/20/2011 10:32:59 AM #

ty!

Reply

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

RecentComments

Comment RSS