Supporting complex route patterns with the ASP.NET (MVC) routing engine
I’m currently working on an ASP.NET MVC application to help broaden my understanding and knowledge of the new framework and recently I found myself needing to be able to map a more complex route pattern to an action, than the default route implementation was able to do out of the box.
What I wanted to do was to be able to represent a path in a tree structure (in this case for navigation) which, at first glance, does not sound so complex, but when you start to think about the finer grain the tasks grows.
I wanted to be able to catch a hierarchy path of unknown depth so using a fixed pattern was impossible, which only left me with the greedy path parameter {*parameter} and that gave me
- navigation/{*path}
This worked as expected and I was able to catch a path with variable depth, but then my requirements changed; I needed to catch more data after the path it self
- navigation/{*path}/show/title={name}
which should capture
- navigation/games/playstation3/adventure/show/title=little-big-planet
- navigation/hardware/pc/parts/memory/ddr2/show/title=corsair-xms2-pro
And this is where I got slapped in the face because, per design, the greedy parameter definition in a route has to be the last thing that’s appended on a route, so I was unable to append more route information at the end because it threw an exception.
What I was left with was to either use the first part pattern and parse the value of the path parameter, inside of my action method or implement my own route class and provide support for a different route pattern.
I choose to do the latter, for a couple of reasons
- I really didn’t like the idea of having a capture-all action method and do more parsing there. It clutters up my controller code with routing logic
- This was a pain to handle with outbound routes, i.e URL which are generated by the Html.ActionLink method
- Implementing a custom route gave me the opportunity to dig deep inside the routing framework and get a better understanding of it. This really appealed to be, because even though I understood routing at a high level, I didn’t really know too much of the underlying framework.
Presenting the ComplexRoute class
Yes I know, I’m not satisfied with that name either (I’m open for suggestions, leave a comment!) but the darn thing needed a name :-) I won’t dissect every line of the source code, instead I will describe the major changes to the default route. If you want to dissect the implementation, please feel free to download the source code at the end of the post (again I’d love to hear your comments!).
So when I set out to create this class I knew I wanted to keep as much in common, as possible, with the default route class to make it more discoverable. What I ended up doing was to create a class which uses almost the exact same pattern syntax, but with a slightly different interpretation of it.
The biggest change I did was to remove the greedy parameter token, the asterix (*), and made it so that all parameters are greedy for as long as it’s not stopped by a literal in the route. Let me give you a couple of examples to illustrate what I mean.
-
test/{value}
The value parameter will catch anything that is appended after the test/ part of the URL. So if the provided URL was test/this/is/a/long/path then the value of the parameter will be this/is/a/long/path. -
test/{value}/html
The value parameter will catch anything that is appended after the test/ part of the URL, until it is stopped by the literal html. So if we reuse the URL from above, but append /html at the end, like so test/this/is/a/long/path then the value of the parameter will still be this/is/a/long/path.
This is important to understand and also to know that you cannot place two parameters directly after each other, not even with a forward slash between them. This is because the greedy nature of the parameters means they also trap forward slashes, so the parse engine wouldn’t know where the boundaries between the two parameters should be set.
This greedy by nature trait of the parameters enables us to create complex route patterns such as
- test/{value}/mode={mode}/search=part-{searchValue}-results
and even send in sub paths (forward slash separated values) to any of the values – again this works until it reaches the first literal URL segments after the parameter. So something like
-
navigation/path={path}
with the provided URL http://navigation/path=root/test1/test2/test3
would capture root/test1/test2/test3 into the path parameter.
The core of this route implementation is the custom pattern parser called RegularExpressionBuilder. It inspects the route pattern and injects regular expression in each of the parameter placeholders. This is what gives them the greedy by nature characteristic. I wrote a custom string parser at first but the quality of that code was enough to banish it back to the wretched place it came from (I spent well over a day trying to write the best darn string parser known to man and 20 minutes working on the regex version, hehe)!
All in all it was both an interesting and an educational challenge, one that I hope you also can learn something from by downloading the source code. If you use this in a project please let me know :-)
Disclaimer! Please accept this for what it is. It’s a first attempt at implementing a custom route pattern and even though I’ve taken the time to refactor, XML document, use compiled regular expressions, style-cop and FxCop the source code it still needs some attention. I’m definitely going to review the code again to check the exception handling and it also needs a lot more testing.
The download contains a small demo ASP.NET MVC application which shows how to hook up the custom route class and how to generate outbound URLs.
You can download the full source code at my codeplex page.