ASP.NET Routing… Goodbye URL rewriting?

UPDATES:
>
Added some detail about routing with IIS7.  See end of post 🙂
> .NET 3.5 SP1 includes ASP.NET Routing as part of the framework.  If you’re using ASP.NET AJAX (or anything else that uses resource handlers like WebResource.axd) be sure to check out this page.  Without it you’ll find all resource requests go through to your page handler! 🙂
> You can find a sample project and more details here.
> Be sure to check out Phil Haack’s post covering some of the security implications of this.  I’ve also added a related comment to the end of this post 🙂
> Added a new post that might help you resolve issues with relative file paths when using routes.  Check it out here.
> Added a VB.NET sample project here!  Thinking I need a new post to get all these updates under control 😉
> Added some SiteMap support here.
> Article on routing by Satheesh Babu here.
> ASP.NET 4.0 makes WebForm routing even easier; see Scott Guthrie’s post here.

The System.Web.Routing assembly introduced with .NET 3.5 SP1 (originally part of ASP.NET MVC) brings some interesting stuff for "traditional" ASP.NET developers.  Check out this post by Phil Haack (and be sure to keep up with his upcoming posts that will go into more detail).

One of the obvious uses for the new routing mechanism is as a "clean" alternative to URL rewriting (and possibly custom VirtualPathProviders for simple scenarios) for traditional / postback-based ASP.NET sites.  After a little experimentation I found some minimal steps that work pretty well:

  • Create a custom IRouteHandler that instantiates your pages
  • Register new Routes associated with your IRouteHandler
  • That’s it!

The IRouteHandler implementation can be as simple or elaborate as you like.  Just implement the GetHttpHandler method and return a new instance of an ASP.NET page (if you want to use an ASPX you can instantiate it with BuildManager.CreateInstanceFromVirtualPath).

Here’s a very simple IRouteHandler implementation that instantiates a single page (compiled or ASPX) for any request sent to it:

public class WebFormRouteHandler<T> : IRouteHandler where T : IHttpHandler, new()
{
    public string VirtualPath { get; set; }

    public WebFormRouteHandler( string virtualPath )
    {
        this.VirtualPath = virtualPath;
    }

    #region IRouteHandler Members

    public IHttpHandler GetHttpHandler( RequestContext requestContext )
    {
        return ( VirtualPath != null )
            ? (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath( VirtualPath, typeof( T ) )
            : new T();
    }

    #endregion
}

This example could be useful in a site with a single ASPX that hosts multiple ASCXs as its "pages" (maybe one that uses the inbuilt SiteMap as a mapping mechanism between public URLs and ASCXs).  For more traditional sites, your GetHttpHandler would return separate page instances based on the RequestContext it’s provided with (RequestContext includes the routing details extracted from the URL; MVC would create a Controller at this point).

Routes are usually registered in the Application_Start handler in Global.asax.  Here’s a simple example based on the "single ASPX / multiple ASCX" approach that passes several routes to a single page (MyPage.aspx):

protected void Application_Start( object sender, EventArgs e )
{
    RegisterRoutes( RouteTable.Routes );
}

public static void RegisterRoutes( RouteCollection routes )
{
    // Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable
    //       automatic support on IIS6 and IIS7 classic mode

    var routeHandler = new WebFormRouteHandler<Page>( "~/MyPage.aspx" );

    routes.Add( new Route( "{page}", routeHandler ) );
    routes.Add( new Route( "AccountServices/{page}", routeHandler ) );
    routes.Add( new Route( "Default.aspx", routeHandler ) );
}

Phil Haack has a post that covers some security implications of this approach.  Like Phil suggests, the ‘insecure’ behavior might be exactly what you want.  You could prevent direct URL access to your ASPX’s (using ASP.NET’s existing mechanisms) and consider them just resources to be used by your IRouteHandler.

Also note that Phil includes a mechanism for passing the RequestContext to your page (just define and implement the IRoutablePage interface).

Routing in IIS6

By default IIS6 only passes certain requests to ASP.NET (.ASPX, .ASMX. ASHX etc).  To allow the routing mechanism to handle your requests you need to set up “Wildcard Application Mapping” for your application.  This causes ALL requests to your site (even static file requests) to go through ASP.NET.  This isn’t as bad as it sounds, but if you’re wanting to squeeze every last drop of performance from your app, IIS7 might be a better option.

You can find details on setting up Wildcard Application Mapping here on TechNet.

Routing in IIS7

To use all this goodness in IIS7, there are a couple of extra steps you need to take (instead of the "wildcard mapping" needed in IIS6):

  • Derive a concrete class from UrlRoutingHandler.  Something like this:
    /// <summary>
    /// Simple UrlRoutingHandler implementation
    /// </summary>
    public class RoutingHandler : UrlRoutingHandler
    {
        protected override void VerifyAndProcessRequest( IHttpHandler httpHandler, HttpContextBase httpContext )
        {
        }
    }
     
     
  • In web.config, under <modules> inside <system.webServer>, add this:
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  • Also under <modules> you’ll need to add a new attribute if you want Session state to work:
    <modules runAllManagedModulesForAllRequests="true">
  • Finally, under <handlers> (inside <system.webServer> again), add a reference to the RoutingHandler we just defined (remember to change the SimpleRoutingTest namespace and assembly name to your own):
    <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="SimpleRoutingTest.RoutingHandler, SimpleRoutingTest"/>

That should be all you need 🙂

Hope this helps!

kick it

Silverlight 2 – 2D Physics revisited

It’s awesome that Silverlight 2 [beta 1] is now with us; the world just became a far cooler place 🙂  It can’t all be good of course; sadly my earlier Silverlight 1.1 demos no longer work 😦

Fortunately it took almost no effort to get things moving again.  If you’ve got Silverlight 2, you can try the updated demo here (or click the image).  There’s also a new “heads will roll” feature for your viewing pleasure.  Source code coming soon…

 image

You might notice objects occasionally roll behind each other (BulletX is a 3D physics engine; I’m probably missing a constraint), or you might get kicked to a blank page…  They’re my bugs, not Silverlight’s 🙂  I’ll update here when they’re fixed.