Using Routing With WebForms

asp.net, code, asp.net mvc 0 comments suggest edit

UPDATE: I updated the sample to work with the final version of ASP.NET Routing included with ASP.NET 3.5 SP1. 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 allusers. 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: aspnetmvc,Routing,ASP.NET

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

64 responses

  1. Avatar for Tim van der Schaaf
    Tim van der Schaaf March 11th, 2008

    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

  2. Avatar for Vijay Santhanam
    Vijay Santhanam March 11th, 2008

    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

  3. Avatar for Brian
    Brian March 12th, 2008

    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.

  4. Avatar for Dragan Panjkov
    Dragan Panjkov March 12th, 2008

    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.

  5. Avatar for Haacked
    Haacked March 12th, 2008

    @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.

  6. Avatar for Denny Ferrassoli
    Denny Ferrassoli March 12th, 2008

    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!

  7. Avatar for Tony
    Tony March 24th, 2008

    How would you pass url segments (routed) to parameters?
    /items/47 to itemcalc.aspx?iid=47
    ?

  8. Avatar for Haacked
    Haacked March 25th, 2008

    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?

  9. Avatar for Anthony
    Anthony April 28th, 2008

    Hi Phil,
    URL Routing replace URL Rewriter?

  10. Avatar for Anthony Main
    Anthony Main May 7th, 2008

    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?!

  11. Avatar for dmitry39
    dmitry39 May 15th, 2008

    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

  12. Avatar for Haacked
    Haacked May 15th, 2008

    @dmitry39 feel free.

  13. Avatar for Ahtesham
    Ahtesham May 29th, 2008

    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

  14. Avatar for Marble Wu
    Marble Wu June 13th, 2008

    Wonderful~~

  15. Avatar for alex
    alex June 14th, 2008

    thx, is that what i need
    :)

  16. Avatar for Nguyen thoai
    Nguyen thoai June 25th, 2008

    Thank you, it's very nice post :D

  17. Avatar for Max
    Max June 25th, 2008

    Really helpfull. Thanks a lot :)

  18. Avatar for Nick
    Nick July 21st, 2008

    Phil, Does routing link data to the static physical page? Or can we link the data to a logical page as in URL Rewriting?

  19. Avatar for haacked
    haacked July 21st, 2008

    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.

  20. Avatar for dkarantonis
    dkarantonis September 3rd, 2008

    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?

  21. Avatar for Marco
    Marco September 10th, 2008

    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

  22. Avatar for Andrew Feldman
    Andrew Feldman September 14th, 2008

    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!

  23. Avatar for AC
    AC October 12th, 2008

    Thanks for the code sample. Just a minor point; I didn't find the use of extension methods to be helpful in the example code. No offense, but it muddles the use System.Web.Routing that you're trying to communicate across. It also forces me to load it into Visual Studio which I don't always want to do.

  24. Avatar for prabhjot singh
    prabhjot singh October 31st, 2008

    respected sir,
    I am new to ASP.NET. I am working on URL routing for last couple of weeks, although i have implemented routing on the static pages in my site, but i get stuck when i have to implement it on dynamic URL's.
    eg
    If i have to generate url's for the each user profile in my site, then the relative URL which needs to be generated should not contain the query string values such as the user id.
    secondly
    if i have two pages( home.aspx and profile.aspx) in my site in the same folder eg "logged". The actual Url is mysite/logged/home.aspx.
    i want the relative url to be mysite/index, which i have achieved using your code but now when i click on some other page and then when again i hit in the home tab the url displayed is mysite/logged/index which is wrong. The folders name is shown again in the url.
    what should i do? hope you understand what i want to achieve.
    regards
    prabhjot

  25. Avatar for LD
    LD November 9th, 2008

    Does IIS 5.1 support web form routing?

  26. Avatar for Jay
    Jay November 16th, 2008

    I'm trying to use this on shared host that doesn't allow reflection, and the methods to render a link apparently uses this because they give me error on this. I don't really understand reflection, could someone point out to me what parts in rendering the link that are reflection and how to get around it? Thanks. Hopefully someone still reads this...

  27. Avatar for Leigh
    Leigh November 25th, 2008

    How can we get this working in conjunction with the SiteMap? More specifically, how can it be made to work with the dynamic aspect of routing?

  28. Avatar for Edmund
    Edmund December 3rd, 2008

    It worked fine, till I switched the web project to use the "Local IIS Web Server" instead of the "VS Development Server".
    I am using a XP development machine with its local (oldish) IIS 5.1
    Is there a way to develop against that IIS version?
    What about IIS 6 on a production Windows Server 2003?
    Thanks

  29. Avatar for Mike
    Mike December 6th, 2008

    I'm having the same problem as Andrew. Everything works great but the pages loose their Session state. Session is fine if I access the page via their full path rather than a route. Any thoughts?

  30. Avatar for Timothy Khouri
    Timothy Khouri December 29th, 2008

    For those of you who are having the "null SessionState" (or null User, etc) error... see my frustration/solution here: stackoverflow.com/.../asp-net-routing-do-custom...

  31. Avatar for Edmund
    Edmund January 11th, 2009

    I tried implementing this with MasterPages, where the master page folds the links to other pages (used as a menu). However, Html.Routlink does not exsit on the master page as it is implemented at the page class level.
    How to have the link on a master page?
    Thanks

  32. Avatar for Jeromy Irvine
    Jeromy Irvine January 22nd, 2009

    This is exactly what I needed for a project. Is there any chance that something like this will make it into the framework in the future?
    One note, I think there is a bug in the Demo download. HtmlHelper.cs, Line 50, Col 36 passes null rather than the "name" parameter.

  33. Avatar for Dmitry
    Dmitry February 15th, 2009

    This is exactly what I needed. However, it works great in the Visual Studio built-in server but if I try to run it in II7 (integrated mode) the UriRoutingHandler throws an exception "Cannot create an abstract class."
    Thanks,
    Dmitry

  34. Avatar for Dmitry
    Dmitry February 15th, 2009

    I fixed that problem by deriving a handler from UrlRoutingHandler and referencing it.
    However, I have another issue now. If the routing does not end with an .aspx extension, I get "Request for principal permission failed." when trying to use PrincipalPermission Demand() authentication using a custom principal.

  35. Avatar for Fenster
    Fenster February 24th, 2009

    I have found it as a plugin to download in the internet.

  36. Avatar for Mitch
    Mitch April 2nd, 2009

    Has anybody tried using routing with native iis7 output caching. It has a setting for varyByQueryString but since the qs is not changing, a page like /product/1 and /product/2 mapped to product.aspx would return the same thing?
    Great artice. Thanks,

  37. Avatar for Josh Lewis
    Josh Lewis April 3rd, 2009

    I have implemented your solution in site that I had already built that uses a lot of ASP.NET AJAX, specifically the ScriptManager control. However, all of my ajax JavaScript is bombing out. The first error I get is 'ASP.NET Ajax client-side framework failed to load'. I have looked all over the web and it appears that many others are having the problem and everyone has a different solution. I get the same errors on VS08 integrated web server and Server 2003 IIS 6.0 with wildcard mapping enabled.
    Any thoughts or a push in the right direction would be very much appreciated.
    Thanks,
    Josh

  38. Avatar for Josh Lewis
    Josh Lewis April 5th, 2009

    Just wanted to follow up and say that I got it working with AJAX after finding your comment on Stack Overflow here stackoverflow.com/.../how-to-ignore-route-in-as...
    The one comment that I want to make is that it is important to keep the order of your routes in mind else this fix doesn't work.
    Thanks for a great post.
    Josh

  39. Avatar for Eric
    Eric January 14th, 2010

    I have implemented your solution and find that it works great, with one exception. I load up a RouteCollection using some data from a database to act as a VirtualPathProvider. When the user hits my domain: domain/abc, the abc part is transalted into a querystring value to pull some data.
    My issue, is that when the abc becomes a value that wasn't loaded into the RouteCollection, I obviously get errors. How is this handled?
    Great post,
    Eric

  40. Avatar for Saravanan
    Saravanan February 6th, 2010

    Thanks. This is really very useful. But one i am getting one problem in using this with Master page. I found that i can't use <%=Html.RouteLink("/Home", "Login")%> in MasterPage. I can use only <%=Html.RouteLink("/Home", "Login")%> in the page inherited from routablepage class. Is that correct? or any other solution to use <%=Html.RouteLink("/Home", "Login")%> in masterpage.
    Regards,
    Saravanan

  41. Avatar for Sarp
    Sarp February 6th, 2010

    I tried several very simple routing sentence tests in my VS 2008 3.5SP1. I could not achieve to run any. I also tested two other sample websites. Both did not worked in my VS.
    I downloaded SimpleRoutingTest project from Codeplex, opened and run it in VS 2008 3.5 SP1. Great! it worked.
    I have a website where I want to use it. I copied SimpleRoutingTest there. It did not worked in this VS.
    I simplified the approach and created a new website in a new empty VS solution and opened SimpleRoutingTest in the solution also. I copied all files except bin, obj and properties from SimpleRoutingTest to website. I added references of Routing and Abstractions to website. Removed SimpleRoutingTest project from solution. (tried both moved / not moved helpers to App_Code). It does not run. What I am missing? What can be solution to run routing in my website?

  42. Avatar for Patrick Oliveros
    Patrick Oliveros February 21st, 2010

    I noticed that the codeplex project is no longer visible. Any fix to the link?

  43. Avatar for Στέλιος Δ.
    Στέλιος Δ. February 21st, 2010

    I've only tried url rewriting so far but I'm definitely moving to asp.net routing!

  44. Avatar for ztruk_002
    ztruk_002 February 23rd, 2010

    I'd like to see the code, but the link doesn't seem to be working. Any reason?
    thx

  45. Avatar for vythees
    vythees March 2nd, 2010

    Thanks for your wonderful post.
    The demo download page in codeplex is blank.
    Thanks again.

  46. Avatar for Scott Fraley
    Scott Fraley April 14th, 2010

    Still no fix to the broken link to yer demo eh? :(

  47. Avatar for Chicago mover
    Chicago mover August 11th, 2010

    Thanks for such a great post.

  48. Avatar for Sherwin
    Sherwin August 12th, 2010

    Anyone has a mirror link for phil's demo?, Anyway thanks Phil I Hope I read it two years ago >.<

  49. Avatar for winbeanye
    winbeanye August 12th, 2010

    good .thank you

  50. Avatar for Richard Everett
    Richard Everett September 9th, 2010

    Phil,
    Thanks for the technique - it really helps us. However we've noticed that on occasion the first call to CreateInstanceFromVirtualPath can take a long while (tens of seconds) to return. During that time we can see a C# compiler process (csc.exe) chewing up 10%-50% of our CPU cycles.
    Any thoughts on potential fixes for this issue?

  51. Avatar for Gary A.
    Gary A. September 19th, 2010

    I realize that this is just a partial solution, but where do you catch the SecurityException? I define my routes in a Global.asax.cs file, but in the real world, I'd probably just redirect the user to a login page. Perhaps I'm missing something in my understanding of this?

  52. Avatar for Venkat
    Venkat December 14th, 2010

    Hai,
    The download link doesn't not working

  53. Avatar for Naveen Jose
    Naveen Jose December 14th, 2010

    Hi Phil,
    You removed the download demo? Cannot access it.

  54. Avatar for Jayesh
    Jayesh January 17th, 2011

    Hi,
    I am using URL Routing for display user profiles. I am using master page and child pages. The profile page is a child page and the master page has a login status control. When I login (say user x) through the modal popup which is in the master page itself, i can browse the site with smoothly and reach the routed page. When I log out from the routed page using the login status i get redirected to my home page as required as my login status also is logged out. If i login from this homepage now with another username say y, i get logged in successfully.
    The problem is that when I again use the routed to get to the routed page, I get the previous user x still logged in. When i come to homepage I see user y logged in. So I have two sessions working; one in the routed page, and other in the whole of the website.
    I will be obliged if anyone can point out the session problem. :)

  55. Avatar for Joy
    Joy July 26th, 2011
  56. Avatar for victoryans
    victoryans August 31st, 2011

    I just want to asked if there's a workaround in routing using masterpage. I put the links in masterpage (i.e. first link has a href of /products/list and the other link has a href of category/name) if i click the first link it redirect me to the correct url which is http://mysite/products/list but if i click the next link it will redirect me to http://mysite/products/category/name but the link should be http://mysite/category/name

  57. Avatar for Javad
    Javad October 19th, 2011

    Hi phil,
    very nice code.
    I'm using it in my projects, but now i want route sub domains like
    japan.mysite.com, at all {Country}.mysite.com and route this page to Country.aspx
    how can i do this.
    thanks
    javad

  58. Avatar for Tina
    Tina March 30th, 2012

    This seems to perform a 302 redirect. Is there any way to explicitly set it to be a 301?

  59. Avatar for Divya
    Divya September 13th, 2012

    Hello Phill,
    Thanks for useful startup code. I have few doubts though.
    1) I have a legacy asp.net web form application. I want to do 301 redirect request to newly created asp.net mvc application
    - Asp.net webform: http://www.mysite.com/user/divya-1234.aspx
    - Asp.net MVC: http://www.mysite.com/user/1234/divya

    2) Since I will need to write lots of redirect statement, I want to understand what will be best possible way to do this (Architecture or convention should i keep in mind.)
    3) What other SEO considerations i should keep in mind besides this?
    Thank you so much for your help.

  60. Avatar for Sunil Acharya
    Sunil Acharya January 9th, 2013

    download link is not available..  :(

  61. Avatar for firoz khan
    firoz khan May 8th, 2014

    hack download link is not working.......

  62. Avatar for tom
    tom March 8th, 2015

    It's been a while since the last Weekly Source Code , but I have the holidays and the preponderance of.

  63. Avatar for T.J. Crowder
    T.J. Crowder June 9th, 2015

    Very useful. That said, it would be REALLY HANDY if people would stop leaving off the imports they're relying on when posting code. The code above uses imports from several different namespaces, showing them would save people time there's zero reason to waste.

    So far, here are the ones it relies on: System.Security, System.Web, System.Web.Compilation, System.Web.UI, System.Web.Routing, System.Web.Security.

    Don't go looking for where IRoutablePage is; you won't find it. You're meant to define it yourself, with that RequestContext property, and the implement that on your page if you want.

  64. Avatar for Kerry Sealey
    Kerry Sealey April 15th, 2016

    Helpful blog post ! I Appreciate the points ! Does anyone know where I can find a blank DD 1750 document to complete ?