Today we’re going to discuss url partitioning. What’s url partioning? Well, I’m sure that you agree with me when I say that one of the greatest things about the routing assembly is that we can define parameters (or placeholders) on routes? Here’s an example that will surelly refresh your memory:
In the previous example, anything is an url placeholder. As we’ve seen, these placeholders are normally called url parameters and they will be replaced by specific values defined by the urls you use. In fact, you can even set default values and constraints on those parameters.
In the last post of the series, we’ve seen that the routing module (UrlRoutingModule) is responsible for intercepting the request and redirect it to the correct handler. At the time, I’ve said that:
Currently, the module handles two application events: the PostResolveRequestCache and the PostMapRequestHandler events. Most of the work is done on the PostResolveRequestCache. During that event, the module will use the RouteTable (more info about this object can be found on my previous post) object to get a route that matches the current request’s url.
When that happens, the module receives a valid RouteData instance (ie, a non-null RouteData instance). This object has all the necessary info about the current route.
What we didn’t see is how the current url’s request is mapped on a specific route that has been added to the routing table. That’s what we’ll see today!
Internally, the url patterns associated with a route is always partitioned in one or more elements of type PathSegment (btw,this is an internal class,so you won’t be able to use it in your code). Currently, there are two types of PathSegments: ContentPathSegments and SeparatorPathSegments.
SeparatorPathSegments are just that: elements that reprensent char / defined on a url (in the current release, you can only represent separators through / chars). ContentPathSegment elements represent all the other elements you can have in a url. These elements may have several PathSubSegment elements. Interestingly, there’s also two types of PathSubSegments: LiteralSubsegments and ParameterSubsegments.
LiteralSubsegments represent constant values defined on an url pattern associated with a route. On the other hand, ParameterSubsegments represent the url parameters. The following figure tries to show these relations.
Whenever we set the Url of a route object, we end up partitioning the string in several elements of the previous type. The partitioned url is kept by each Route object on a private instance of type ParsedRoute. The ParsedRoute.Parse method is responsible for kicking off the parsing of the url.
The first thing the method does is break the current url in several PathSegments elements. This initial partitioning uses the / char as a separator. In practice, this means that the strings delimited between each /pair will be represented by a ContentPath Segment element and that the separator char (/) will be represented by a SeparatorPathSegement element.
Notice that ContentPathSegment elements do require more processing than SeparatorPathSegment elements. We have 2 basics scenarios: each content segment may contain a constant value or a parameter value (keep in mind that we may have combinations of these elements in a content path, ie, a path may have several constant sub-segments or a mixed collection of constant and parameter sug-segments– more info on this on the next paragraphs).
When the parser finds a constant value, it will simply add an element of type LiteralSubsegment for that value. On the other hand, parameters will always be represented by ParameterSubsegment elements. Some examples will surelly help you understand the internal representation of these paths.
Example 1: Home/{whatever}
In this case, we’ll have 3 path segments: one for the separator char (represented by a single SeparatorPathSegment object) and two of type ContentPathSegment. The first, will have a single sub-segment path element, represented by an object of type LiteralSubsegment (this object will save the value Home in the Literal property of that class).
The second ContentPathSegment will also have one sub-segment, but in this case, we’ll get an element of type ParameterSubsegment. Besides saving the name of the parameter, this class has another property called IsCatchAll, which is true when we have a catch all parameter (read more about catch all parameters here).
Example 2: Home/{language}-{local}
In this example, we end up with 3 path segments: The first two are the same as the ones we’ve got on the previous example. The last one is also a ContentPathSegment, but in this case, we’ll end up with 3 sub-paths segments! The first and the third are ParameterSubsegments (parameters language and local). The second one is a LiteralSubsegment that will keep the ‘–’ literal value.
I really hope that the previous example have made the url parsing clear. In the previous example, we’ve never defined a catch all parameter. Here’s an example of a catch all parameter:
Home/{*whatever}
As you might recall, catch all parameters will match whatever url you pass in that are compatible with the “constant part” of the url pattern. When you partition the previous url in path segments, you’ll get a similar result to the one you got with example 1. The main difference is that in this case, you’ll have a catch all ParameterSubsegment (ie, the IsCatchAll property of the ParameterSubsegment object is set to true)!
Now that you understand url partitioning, it’s time to see how the UrlRoutingModule uses this info to decide if the current request is a routing request. Btw, I’m defining a routing request as a request that is intercepeted by the UrlModule module (and this will only happen when the module gets a valid RouteData object).
In the previous post, we’ve seen that UrlRoutingModule gets the current RouteData by calling the GetRouteData method over the internal route collection kept by the Rou
teTable object (in practice, this means that we go through all the Route objects and invoke the GetRouteData over each one of them).
This call is suficiently interesting for deserving a couple of paragraphs. The first thing this method (we’re still speaking about the GetRouteData method defined by the RouteCollection class) does is check the RouteExistingFiles property. When this property is set to false and the requested file exists on disk, the Route object doesn’t return a valid RouteData object (so, you’ll end up getting that file because the request won’t be intercepted by the UrlRoutingModule, leaving it to be processed by the default handler).
When this property is set to true (the default), you’ll end up getting routing even when there’s a physical file with the same url as the one requested (we’re assuming that the url pattern matches the current request’s url!).
As you might expect by now, getting a valid RouteData objects depends on the partitioning we’ve described in the previous paragraphs. The first thing the Route.GetRouteData method does is get a RouteValueDictionary with the values associated with the current request’s url. It will only return a valid dictionary (ie, non null value) when all the path segments are matched by values defined on the current url. It’s important to notice that the parameter values found on this dictionary may come from the current request’s url or from the default values you’ve set up on the route. So, what really matters is that the current url + defaults you’ve set up when you created the route cover all the url parameters.
The next step is performing constraint validation. As you might recall, you can apply constraints to the parameters defined on a route’s url. If all the constraints are ok, you’ll end up getting a valid RouteData object. At this time, it’s important to notice that the Values property of this object is built by copying the values maintained on the RouteValueDictionary that was built at the beginning of the Route.GetRouteData method (We’ve talked about this in the previous paragraph). The same thing happens to the DataTokens property: the value present on the RouteData object are copied from the ones defined on the Route.DataTokens collection.
By now you must have a good idea of how routing works. I’ll keep looking at the new MVC framework, so I believe that in the next days I’ll keep adding more posts on this topic. Keep tuned!
3 comments so far
11:17 am - 4-23-2009
Hey there,
First let me thank you for covering those materials so thoroughly! I accidently bumped into your blog and found myself reading and reading and reading…
A simple question (I think)… How can I achieve the following:
I have the following url:
http://mysite/MyController/MyAction/param
I want to somehow activate it using the following format:
http://mysite/param
You can understand I want to set the default controller to MyController and action to MyAction.
I prefer this to happen without harming the default route… Is this possible??
Assuming I have to change the default route, can you show me an example?
Thanks!!
1:12 pm - 4-23-2009
I”m not sure if I follow everything, but don”t forget that you can set routes witthout using parameters. So, if you add mysite/param, then that route could be processed if it”s placed before the default one…does this help?
10:46 am - 12-16-2009
6rMS0B lyomybveeqlh, [url=http://pbxzqafmznoc.com/]pbxzqafmznoc[/url], [link=http://dinucmfkmrwy.com/]dinucmfkmrwy[/link], http://xdcqvoitwxof.com/