Using Routing With WebForms

In my last post I described how Routing no longer has any dependency on MVC. The natural question I’ve been asked upon hearing that is “Can I use it with Web Forms?” to which I answer “You sure can, but very carefully.”

Being on the inside, I’ve had a working example of this for a while now based on early access to the bits. Even so, Chris Cavanagh impressively beats me to the punch in blogging his own implementation of routing for Web Forms. Nice!

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!

He took advantage of the extensibility model by implementing the IRouteHandler interface with his own WebFormRouteHandler class (not surprisingly my implementation uses the same name) ;)

There is one subtle potential security issue to be aware of when using routing with URL Authorization. Let me give an example.

Suppose you have a website and you wish to block unauthenticated access to the admin folder. With a standard site, one way to do so would be to drop the following web.config file in the admin folder...

<?xml version="1.0"?>
<configuration>
    <system.web>
        
        <authorization>
            <deny users="*" />
        </authorization>

    </system.web>
</configuration>

Ok, I am a bit draconian. I decided to block access to the admin directory for all users. Attempt to navigate to the admin directory and you get an access denied error. However, suppose you use a naive implementation of WebFormRouteHandler to map the URL fizzbucket to the admin dir like so...

RouteTable.Routes.Add(new Route("fizzbucket"
  , new WebFormRouteHandler("~/admin/secretpage.aspx"));

Now, a request for the URL /fizzbucket will display secretpage.aspx in the admin directory. This might be what you want all along. Then again, it might not be.

In general, I believe that users of routing and Web Form will want to secure the physical directory structure in which Web Forms are placed using UrlAuthorization. One way to do this is to call UrlAuthorizationModule.CheckUrlAccessForPrincipal on the actual physical virtual path for the Web Form.

This is one key difference between Routing and URL Rewriting, routing doesn’t actually rewrite the URL. Another key difference is that routing provides a mean to generate URLs as well and is thus bidirectional.

The following code is my implementation of WebFormRouteHandler which addresses this security issue. This class has a boolean property on it that allows you to not apply URL authorization to the physical path if you’d like (in following the principal of secure by default the default value for this property is true which means it will always apply URL authorization).

public class WebFormRouteHandler : IRouteHandler
{
  public WebFormRouteHandler(string virtualPath) : this(virtualPath, true)
  {
  }

  public WebFormRouteHandler(string virtualPath, bool checkPhysicalUrlAccess)
  {
    this.VirtualPath = virtualPath;
    this.CheckPhysicalUrlAccess = checkPhysicalUrlAccess;
  }

  public string VirtualPath { get; private set; }

  public bool CheckPhysicalUrlAccess { get; set; }

  public IHttpHandler GetHttpHandler(RequestContext requestContext)
  {
    if (this.CheckPhysicalUrlAccess 
      && !UrlAuthorizationModule.CheckUrlAccessForPrincipal(this.VirtualPath
              ,  requestContext.HttpContext.User
              , requestContext.HttpContext.Request.HttpMethod))
      throw new SecurityException();

    var page = BuildManager
      .CreateInstanceFromVirtualPath(this.VirtualPath
        , typeof(Page)) as IHttpHandler;
      
    if (page != null)
    {
      var routablePage = page as IRoutablePage;
      if (routablePage != null)
        routablePage.RequestContext = requestContext;
    }
    return page;
  }
}

You’ll notice the code here checks to see if the page implements an IRoutablePage interface. If your Web Form Page implements this interface, the WebFromRouteHandler class can pass it the RequestContext. In the MVC world, you generally get the RequestContext via the ControllerContext property of Controller, which itself inherits from RequestContext.

The RequestContext is important for calling into API methods for URL generation. Along with the IRoutablePage, I provide a RoutablePage abstract base class that inherits from Page. The code for this interface and the abstract base class that implements it is in the download at the end of this post.

One other thing I did for fun was to play around with fluent interfaces and extension methods for defining simple routes for Web Forms. Since routes with Web Forms tend to be simple, I thought this syntax would work nicely.

public static void RegisterRoutes(RouteCollection routes)
{
  //first one is a named route.
  routes.Map("General", "haha/{filename}.aspx").To("~/forms/haha.aspx");
  routes.Map("backdoor").To("~/admin/secret.aspx");
}

The general idea is that the route url on the left maps to the webform virtual path to the right.

I’ve packaged all this up into a solution you can download and try out. The solution contains three projects:

  • WebFormRouting - The class library with the WebFormRouteHandler and helpers...
  • WebFormRoutingDemoWebApp - A website that demonstrates how to use WebFormRouting and also shows off url generation.
  • WebFormRoutingTests - a few non comprehensive unit tests of the WebFormRouting library.

WARNING: This is prototype code I put together for educational purposes. Use it at your own risk. It is by no means comprehensive, but is a useful start to understanding how to use routing with Web Forms should you wish. Download the demo here.

Technorati Tags: ,,
[ad] Free Bug Tracking & Project Management Software Axosoft’s OnTime 2007 allows software development teams to collaborate on software projects by tracking everything from defects to enhancements to helpdesk incidents in one easy-to-use database driven by an intuitive Windows, Web or VS.NET Integrated UI. Get a Free Single-User License ($200 Value!)

What others have said

Requesting Gravatar... Tim van der Schaaf Mar 12, 2008 2:44 AM
# re: Using Routing With WebForms
Hi Phil, thanks for the interesting posting. We are currently looking into Url Rewriting on an ASP.Net 2.0 based website. After reading this post I am considering using the Routing engine as it seems a much nicer, cleaner solution. So the question is, can we use the required dll's with ASP.Net 2.0?

Cheers,
Tim
Requesting Gravatar... Vijay Santhanam Mar 12, 2008 7:09 AM
# re: Using Routing With WebForms
Hi Phil,

Great code sample. Luv your work.

Just today I was trying to mimic an MVC-ish pattern with url rewriter (like subtext), http handlers and a custom view engine - all in web forms. This could do the job nicely.

Security be damned i say.

-V
Requesting Gravatar... Brian Mar 12, 2008 1:52 PM
# re: Using Routing With WebForms
Phil,

Thanks for this demo. This is truly awesome for those of us using third-party ISAPI tools. I haven't delved into the code too far yet, but I am curious to know if the full url can be accessed. Could I rewrite a subdomain as a folder path?

Thanks.
Requesting Gravatar... Dragan Panjkov Mar 12, 2008 5:08 PM
# re: Using Routing With WebForms
Phil,
there is a typo in code snippet posted in this post, inside GetHttpHandler should be CheckUrlAccessForPrincipal instead of CheckUrlAccessForPrincipa. Inside posted source code everything is OK.
Requesting Gravatar... Haacked Mar 12, 2008 11:33 PM
# re: Using Routing With WebForms
@Tim Right now, we only support and test with ASP.NET 3.5. You should try it with 2.0, but I offer no warranties. ;)

@Dragan thanks! Fixed

@Brian no, Routing is specific to an Application. So it handles everything after the ApplicationPath. For subdomain rewriting, you'll still need to use UrlRewriting.
Requesting Gravatar... Denny Ferrassoli Mar 13, 2008 9:55 AM
# re: Using Routing With WebForms
Hey Phil,
Thanks for the great example. I implemented your example in a project but had a question. To get the RouteData Values does the page need to be a RoutablePage?

Thanks!
Requesting Gravatar... Tony Mar 25, 2008 4:50 AM
# re: Using Routing With WebForms
How would you pass url segments (routed) to parameters?

/items/47 to itemcalc.aspx?iid=47

?
Requesting Gravatar... Haacked Mar 25, 2008 10:35 PM
# re: Using Routing With WebForms
Hi Tony, you wouldn't. Routing is not Url Rewriting. Instead, you'd pull the values from Route Data. For example, suppose you had a route with the following url pattern:

items/{id}

Which you mapped to ItemCalc.aspx. If you make ItemCalc.aspx inherit from RoutablePage (it's in the code for this article), then you would have access to this.RouteData.Values["id"] in order to get the value.

Does that make sense?
Requesting Gravatar... Anthony Apr 28, 2008 1:29 PM
# re: Using Routing With WebForms
Hi Phil,

URL Routing replace URL Rewriter?
Requesting Gravatar... Anthony Main May 08, 2008 9:03 AM
# re: Using Routing With WebForms
Hey, nice implementation I've implemented it in my latest development framework, only issue Im having is that whist running on VS Dev Server the root url works (i.e http://localhost:1234/) but when using localhost with a wildcard mapping (iis5.1) root url (i.e. http://localhost/) 404's. Event when I have a default.aspx route.

Any ideas?!
Requesting Gravatar... dmitry39 May 16, 2008 7:15 AM
# re: Using Routing With WebForms
Thx, great post. Phil, can i translate and repost it into my Russian blog about asp.net programming (of course with a link to your original post)?

Regards from Russia
Requesting Gravatar... Haacked May 16, 2008 9:17 AM
# re: Using Routing With WebForms
@dmitry39 feel free.

What do you have to say?

(will show your gravatar)
Please add 4 and 7 and type the answer here: