Friday 19 February 2010

URL Routing Niggles

I've been playing with URL re-routing in Web Forms recently. Although originally supplied as part of the MVC framework the classes can now be used in standard web forms so you can have nice, neat, copy-and-paste friendly, search engine optimised URLs in your web forms application.

The basics of how to do this are well documented, so I won't bore you with them. If you don't know how it's done, I'll let 4-Guys bore you instead.

Trying to implement it I ran into a couple of small hurdles though, which I thought I'd document for reference. It's a little embarrassing really because both of these things are quite clear if you read the documentation properly instead of skimming and getting down to trying it out.

Optional & Extensible Parameters

The first is how to make parameters optional. Let's say for example that I have a search function with two parameters: firstname and surname. I might want the url for that search function to look like this:
/people/search/(searched text for firstname)/(searched text for surname)/
Let's further assume that I have various other pages I want to map inside the people folder, so things like:
/people/summary/
/people/management/

So I define my virtual URL like this:

Dim urlPattern As String = "people/{action}/{firstname}/{surname}"

And as soon as I do that, /people/summary/ fails. It's expecting a firstname and a surname as part of the URL and doesn't recognise the address.

To solve thisWe add a dictionary to the route value, like so:

reportRoute = New Route(urlPattern, New ReportRouteHandler)
reportRoute.Defaults = New RouteValueDictionary(New With {.firstname = "", .surname = ""})

This instructs the route that the firstname and surname parameters can be replaced with a default - in this case an empty string - if they're missing. And of course this being a dictionary you can add as many key/value pairs as you need.

But there's still one more thing. What if we might want to include some additional information in that URL, or even just make sure that longer URLs fail gracefully? At the moment /people/search/matt/j/thrower/ will fail. To solve this, add an asterisk to the final part of your virtual URL:

Dim urlPattern As String = "people/{action}/{firstname}/{*surname}"

And voilĂ ! The * indicates that the final parameter is extensible and can be of any length.

Getting Values from Parameters in Target Pages

The other thing that you'll probably want to do with this is to have a target page read the items in the url parameter collection. Right now you can access the values in {action}, {firstname}, {lastname} and so on inside route handling functions, but the target web form won't know about them.

My first approach to this was to change the parameters into querystrings and include them in the redirect part of the route handler:

Dim handlerUrl As String
Dim hand As Web.IHttpHandler
Select Case CStr(routeData.Values("action")).ToLower()
Case "search"
handlerUrl = String.Format("/people/search.aspx?firstname={0}&surname={1}", routeData.Values("firstname"), routeData.Values("surname"))
End Case

hand = DirectCast(System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(handlerUrl, GetType(Web.UI.Page)), Web.IHttpHandler)

Which blows up in your face because the querystring isn't a valid virtual path.

The way round this is to utilise a handy collection in HttpContext called Items, which allows you to store key/value pairs of data:

Dim handlerUrl As String
Dim hand As Web.IHttpHandler
Select Case CStr(routeData.Values("action")).ToLower()
Case "search"
handlerUrl = "/people/search.aspx"
For Each urlParm In requestContext.RouteData.Values
requestContext.HttpContext.Items(urlParm.Key) = urlParm.Value
Next
End Case

hand = DirectCast(System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(handlerUrl, GetType(Web.UI.Page)), Web.IHttpHandler)

And you can pick these up in your target page like so:

Dim firstname As String = HttpContext.Current.Items("firstname").ToString
Dim surname As String = HttpContext.Current.Items("surname").ToString

No comments:

Post a Comment