Monday, November 30, 2009

Mapping URLs using ASP.NET 2.0 Web Forms

ASP.NET 4.0 is coming with new feature - URL routing with ASP.NET Web Forms

It is pretty cool feature, but require upgrade up to .NET 4.0.
I am not sure that such a good feature is a real reason to get upgraded, and in most cases you will stay with current version on .NET.

But I want it and I want it now, with .NET 3.5. I am going to implement it!

Requirements

I want an API as close as possible to .NET 4.0 version:

RouteCollection.MapPageRoute() helper method in ASP.NET 4.0 to map the "/products/Category_1" URL to a "Products.aspx" page that lives immediately under the application root directory:



void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup

RegisterRoutes(System.Web.Routing.RouteTable.Routes);

}

void RegisterRoutes(System.Web.Routing.RouteCollection routes)
{

routes.MapPageRoute(
"product-browse",
"products/{category}",
"~/Products.aspx");

}



Page.GetRouteData() method to retrieve the "category" parameter value mapped using the /products/{category} URL filter.



protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = (string)this.GetRouteData().Values["category"];
}

Page.GetRouteUrl() helper method to lookup the route within the URL routing system, optionally specify parameters to it, and then retrieve an actual URL that it maps back to. For example, the below code would retrieve a URL value of “/products/Category_1”:


protected void Page_Load(object sender, EventArgs e)
{
HyperLink1.NavigateUrl =
this.GetRouteUrl("product-browse", new { category = "Category_1" });
}


Implementation

For getting it works I need to implement a IRouteHandler, which will handle all routes I set using MapPageRoute() method:



public class PageRouteHandler : IRouteHandler, IHttpHandler
{
private readonly string _virtualPath;
public static object RouteData = new object();

public PageRouteHandler(string virtualPath)
{
_virtualPath = virtualPath;
}

#region IRouteHandler Members

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
HttpContext.Current.Items[RouteData] = requestContext.RouteData;
return this;
}

#endregion

#region IHttpHandler Members

public bool IsReusable
{
get { return false; }
}

public void ProcessRequest(HttpContext context)
{
context.Server.Transfer(_virtualPath, true);
}

#endregion
}


It saves current route data into HttpContext and returns itself as IHttpHandler. Implementation of IHttpHandler might be done in different ways, but I prefer Server.Transfer().

Next step is implementing of extension methods. Here they are:



public static void MapPageRoute(this RouteCollection routes,
string name, string url, string target)
{
routes.Add(name, new Route(url, new PageRouteHandler(target)));
}

public static RouteData GetRouteData(this Page page)
{
return (HttpContext.Current.Items[PageRouteHandler.RouteData] as RouteData)
?? new RouteData();
}

public static string GetRouteUrl(this Page page, string name, object values)
{
return GetRouteUrl(page, name, new RouteValueDictionary(values));
}

public static string GetRouteUrl(this Page page, string name,
RouteValueDictionary values)
{
return RouteTable.Routes.GetVirtualPath(null, name, values).VirtualPath;
}


Summary

Such Web Forms routing makes it easy to implement clean, SEO friendly, URLs.
It can by used with ASP.NET MVC as well, you can have applications that mix the two.
And it does not require upgrade up to .NET 4.0.

Resources

The source code of sample web side I used here might be found at http://code.google.com/p/izlabs/source/browse/#svn/trunk/UrlPageRouting