Using Routing With WebForms

UPDATE: I updated the sample to work with ASP.NET MVC Preview 3 version of Routing. This sample is now being hosted on CodePlex.

Download the demo here

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: ,,

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.
Requesting Gravatar... Ahtesham May 29, 2008 11:03 PM
# re: Using Routing With WebForms
i am new in MVC i download ur example but i trying to run this on viusal studio 2008 professional edition it give me error that

Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested URL: /Default.aspx

even i have selecte the default as my start up page and WebFormRoutingDemoWebApp as my startup project , am i missing something i just want to know how mvc works
Requesting Gravatar... Marble Wu Jun 13, 2008 6:43 PM
# re: Using Routing With WebForms
Wonderful~~
Requesting Gravatar... alex Jun 14, 2008 12:05 PM
# re: Using Routing With WebForms
thx, is that what i need
:)
Requesting Gravatar... Nguyen thoai Jun 26, 2008 1:28 AM
# re: Using Routing With WebForms
Thank you, it's very nice post :D
Requesting Gravatar... Max Jun 26, 2008 3:31 AM
# re: Using Routing With WebForms
Really helpfull. Thanks a lot :)
Requesting Gravatar... Nick Jul 22, 2008 7:37 AM
# re: Using Routing With WebForms
Phil, Does routing link data to the static physical page? Or can we link the data to a logical page as in URL Rewriting?
Requesting Gravatar... haacked Jul 22, 2008 9:32 AM
# re: Using Routing With WebForms
Nick, routing puts data into the requset context (via the RouteData values dictionary) which you can access from the page. The URL is completely separate from the page.
Requesting Gravatar... dkarantonis Sep 04, 2008 3:13 AM
# re: Using Routing With WebForms
Hi Phil,
very nice article indeed.
Just one quick question. Does url routing work with traditional asp.net 3.5 web sites, or only with asp.net mvc web application projects?
Requesting Gravatar... Marco Sep 11, 2008 8:37 AM
# re: Using Routing With WebForms
Hi Phil,

I've tried to use this and adapted it for Codeplex MVC Preview 5, but it doesn't work anymore. Registering routes works fine, but route.GetVirtualPath() returns null everytime. Are there any breaking changes in this method?

The only change I've made on your sample code is replacing the constructor of HttpContextWrapper2 with that one of HttpContextWrapper because the HttpContextWrapper class doesn't exist anymore (in RoutablePage class).

Thanks,
Marco
Requesting Gravatar... Andrew Feldman Sep 15, 2008 9:53 AM
# re: Using Routing With WebForms
Hi Phil, great blog! I have implemented a routing handler for web forms using the approach you outline here. One problem I am having is that HttpContext.Current.Session is null in the page I construct and route to in the WebFormRouteHandler. Session is not null on ordinary, non routed, pages in my application. I am using .NET 3.5 sp1, I have not used any of the MVC previews. Any insight is appreciated. Thank you!

What do you have to say?

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