Tip/Trick: Url Rewriting with ASP.NET

People often ask me for guidance on how they can dynamically "re-write" URLs and/or have the ability to publish cleaner URL end-points within their ASP.NET web applications.  This blog post summarizes a few approaches you can take to cleanly map or rewrite URLs with ASP.NET, and have the option to structure the URLs of your application however you want.

Why does URL mapping and rewriting matter?

The most common scenarios where developers want greater flexibility with URLs are:

1) Handling cases where you want to restructure the pages within your web application, and you want to ensure that people who have bookmarked old URLs don't break when you move pages around.  Url-rewriting enables you to transparently forward requests to the new page location without breaking browsers.

2) Improving the search relevancy of pages on your site with search engines like Google, Yahoo and Live.  Specifically, URL Rewriting can often make it easier to embed common keywords into the URLs of the pages on your sites, which can often increase the chance of someone clicking your link.  Moving from using querystring arguments to instead use fully qualified URL's can also in some cases increase your priority in search engine results.  Using techniques that force referring links to use the same case and URL entrypoint (for example: weblogs.asp.net/scottgu instead of weblogs.asp.net/scottgu/default.aspx) can also avoid diluting your pagerank across multiple URLs, and increase your search results.

In a world where search engines increasingly drive traffic to sites, extracting any little improvement in your page ranking can yield very good ROI to your business.  Increasingly this is driving developers to use URL-Rewriting and other SEO (search engine optimization) techniques to optimize sites (note that SEO is a fast moving space, and the recommendations for increasing your search relevancy evolve monthly).  For a list of some good search engine optimization suggestions, I'd recommend reading the SSW Rules to Better Google Rankings, as well as MarketPosition's article on how URLs can affect top search engine ranking.

Sample URL Rewriting Scenario

For the purpose of this blog post, I'm going to assume we are building a set of e-commerce catalog pages within an application, and that the products are organized by categories (for example: books, videos, CDs, DVDs, etc).

Let's assume that we initially have a page called "Products.aspx" that takes a category name as a querystring argument, and filters the products accordingly.  The corresponding URLs to this Products.aspx page look like this:

http://www.store.com/products.aspx?category=books
http://www.store.com/products.aspx?category=DVDs
http://www.store.com/products.aspx?category=CDs

Rather than use a querystring to expose each category, we want to modify the application so that each product category looks like a unique URL to a search engine, and has the category keyword embedded in the actual URL (and not as a querystring argument).  We'll spend the rest of this blog post going over 4 different approaches that we could take to achieve this.

Approach 1: Use Request.PathInfo Parameters Instead of QueryStrings

The first approach I'm going to demonstrate doesn't use Url-Rewriting at all, and instead uses a little-known feature of ASP.NET - the Request.PathInfo property.  To help explain the usefulness of this property, consider the below URL scenario for our e-commerce store:

http://www.store.com/products.aspx/Books
http://www.store.com/products.aspx/DVDs
http://www.store.com/products.aspx/CDs

One thing you'll notice with the above URLs is that they no longer have Querystring values - instead the category parameter value is appended on to the URL as a trailing /param value after the Products.aspx page handler name.  An automated search engine crawler will then interpret these URLs as three different URLs, and not as one URL with three different input values (search engines ignore the filename extension and just treat it as another character within the URL). 

You might wonder how you handle this appended parameter scenario within ASP.NET.  The good news is that it is pretty simple.  Simply use the Request.PathInfo property, which will return the content immediately following the products.aspx portion of the URL.  So for the above URLs, Request.PathInfo would return "/Books", "/DVDs", and "/CDs" (in case you are wondering, the Request.Path property would return "/products.aspx").

You could then easily write a function to retrieve the category like so (the below function strips out the leading slash and returning just "Books", "DVDs" or "CDs"):

    Function GetCategory() As String

        If 
(Request.PathInfo.Length 0Then
            Return 
""
        
Else
            Return 
Request.PathInfo.Substring(1)
        
End If

    End Function

Sample Download: A sample application that I've built that shows using this technique can be downloaded here.  What is nice about this sample and technique is that no server configuration changes are required in order to deploy an ASP.NET application using this approach.  It will also work fine in a shared hosting environment.

Approach 2: Using an HttpModule to Perform URL Rewriting

An alternative approach to the above Request.PathInfo technique would be to take advantage of the HttpContext.RewritePath() method that ASP.NET provides.  This method allows a developer to dynamically rewrite the processing path of an incoming URL, and for ASP.NET to then continue executing the request using the newly re-written path.

For example, we could choose to expose the following URLs to the public:

http://www.store.com/products/Books.aspx
http://www.store.com/products/DVDs.aspx
http://www.store.com/products/CDs.aspx

This looks to the outside world like there are three separate pages on the site (and will look great to a search crawler).  By using the HttpContext.RewritePath() method we can dynamically re-write the incoming URLs when they first reach the server to instead call a single Products.aspx page that takes the category name as a Querystring or PathInfo parameter instead.  For example, we could use an an Application_BeginRequest event in Global.asax like so to do this:

    void Application_BeginRequest(object sender, EventArgs e) {

        
string fullOrigionalpath Request.Url.ToString();
        
        if 
(fullOrigionalpath.Contains("/Products/Books.aspx")) {
            Context.RewritePath(
"/Products.aspx?Category=Books");
        
}
        
else if (fullOrigionalpath.Contains("/Products/DVDs.aspx")) {
            Context.RewritePath(
"/Products.aspx?Category=DVDs");
        
}
    } 

The downside of manually writing code like above is that it can be tedious and error prone.  Rather than do it yourself, I'd recommend using one of the already built HttpModules available on the web for free to perform this work for you.  Here a few free ones that you can download and use today:

These modules allow you to declaratively express matching rules within your application's web.config file.  For example, to use the UrlRewriter.Net module within your application's web.config file to map the above URLs to a single Products.aspx page, we could simply add this web.config file to our application (no code is required):

<?xml version="1.0"?>

<configuration>

  
<configSections>
    
<section name="rewriter"  
             requirePermission
="false" 
             type
="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
  </
configSections>
  
  
<system.web>
      
    
<httpModules>
      
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>
    </
httpModules>
    
  
</system.web>

  
<rewriter>
    
<rewrite url="~/products/books.aspx" to="~/products.aspx?category=books" />
    <
rewrite url="~/products/CDs.aspx" to="~/products.aspx?category=CDs" />
    <
rewrite url="~/products/DVDs.aspx" to="~/products.aspx?category=DVDs" />
  </
rewriter>  
  
</configuration> 

The HttpModule URL rewriters above also add support for regular expression and URL pattern matching (to avoid you having to hard-code every URL in your web.config file).  So instead of hard-coding the category list, you could re-write the rules like below to dynamically pull the category from the URL for any "/products/[category].aspx" combination:

  <rewriter>
    
<rewrite url="~/products/(.+).aspx" to="~/products.aspx?category=$1" />
  </rewriter>  

This makes your code much cleaner and super extensible.

Sample Download: A sample application that I've built that shows using this technique with the UrlRewriter.Net module can be downloaded here

What is nice about this sample and technique is that no server configuration changes are required in order to deploy an ASP.NET application using this approach.  It will also work fine in a medium trust shared hosting environment (just ftp/xcopy to the remote server and you are good to go - no installation required).

Approach 3: Using an HttpModule to Perform Extension-Less URL Rewriting with IIS7

The above HttpModule approach works great for scenarios where the URL you are re-writing has a .aspx extension, or another file extension that is configured to be processed by ASP.NET.  When you do this no custom server configuration is required - you can just copy your web application up to a remote server and it will work fine.

There are times, though, when you want the URL to re-write to either have a non-ASP.NET file extension (for example: .jpg, .gif, or .htm) or no file-extension at all.  For example, we might want to expose these URLs as our public catalog pages (note they have no .aspx extension):

http://www.store.com/products/Books
http://www.store.com/products/DVDs
http://www.store.com/products/CDs

With IIS5 and IIS6, processing the above URLs using ASP.NET is not super easy.  IIS 5/6 makes it hard to perform URL rewriting on these types of URLs within ISAPI Extensions (which is how ASP.NET is implemented). Instead you need to perform the rewriting earlier in the IIS request pipeline using an ISAPI Filter.  I'll show how to-do this on IIS5/6 in the Approach 4 section below.

The good news, though, is that IIS 7.0 makes handling these types of scenarios super easy.  You can now have an HttpModule execute anywhere within the IIS request pipeline - which means you can use the URLRewriter module above to process and rewrite extension-less URLs (or even URLs with a .asp, .php, or .jsp extension).  Below is how you would configure this with IIS7:

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

  
<configSections>
    
<section name="rewriter" 
             requirePermission
="false" 
             type
="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
  </
configSections>
  
  
<system.web>
      
    
<httpModules>
      
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" />
    </
httpModules>
    
  
</system.web>

  
<system.webServer>

    
<modules runAllManagedModulesForAllRequests="true">
      
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule" />
    </
modules>

    
<validation validateIntegratedModeConfiguration="false" />

  </
system.webServer>

  
<rewriter>
    
<rewrite url="~/products/(.+)" to="~/products.aspx?category=$1" />
  </
rewriter>
  
</configuration>

Note the "runAllManagedModulesForAllRequests" attribute that is set to true on the <modules> section within <system.webServer>.  This will ensure that the UrlRewriter.Net module from Intelligencia, which was written before IIS7 shipped, will be called and have a chance to re-write all URL requests to the server (including for folders).  What is really cool about the above web.config file is that:

1) It will work on any IIS 7.0 machine.  You don't need an administrator to enable anything on the remote host.  It will also work in medium trust shared hosting scenarios.

2) Because I've configured the UrlRewriter in both the <httpModules> and IIS7 <modules> section, I can use the same URL Rewriting rules for both the built-in VS web-server (aka Cassini) as well as on IIS7.  Both fully support extension-less URLRewriting.  This makes testing and development really easy.

IIS 7.0 server will ship later this year as part of Windows Longhorn Server, and will support a go-live license with the Beta3 release in a few weeks.  Because of all the new hosting features that have been added to IIS7, we expect hosters to start aggressively offering IIS7 accounts relatively quickly - which means you should be able to start to take advantage of the above extension-less rewriting support soon.  We'll also be shipping a Microsoft supported URL-Rewriting module in the IIS7 RTM timeframe that will be available for free as well that you'll be able to use on IIS7, and which will provide nice support for advanced re-writing scenarios for all content on your web-server.

Sample Download: A sample application that I've built that shows using this extension-less URL technique with IIS7 and the UrlRewriter.Net module can be downloaded here

Approach 4: ISAPIRewrite to enable Extension-less URL Rewriting for IIS5 and IIS6

If you don't want to wait for IIS 7.0 in order to take advantage of extension-less URL Rewriting, then your best best is to use an ISAPI Filter in order to re-write URLs.  There are two ISAPI Filter solutions that I'm aware of that you might want to check-out:

  • Helicon Tech's ISAPI Rewrite: They provide an ISAPI Rewrite full product version for $99 (with 30 day free trial), as well as a ISAPI Rewrite lite edition that is free.
  • Ionic's ISAPI Rewrite: This is a free download (both source and binary available)

I actually don't have any first-hand experience using either of the above solutions - although I've heard good things about them.  Scott Hanselman and Jeff Atwood recently both wrote up great blog posts about their experiences using them, and also provided some samples of how to configure the rules for them.  The rules for Helicon Tech's ISAPI Rewrite use the same syntax as Apache's mod_rewrite.  For example (taken from Jeff's blog post):

[ISAPI_Rewrite]
# fix missing slash on folders
# note, this assumes we have no folders with periods!
RewriteCond Host: (.*)
RewriteRule ([^.?]+[^.?/]) http\://$1$2/ [RP]

# remove index pages from URLs
RewriteRule (.*)/default.htm$ $1/ [I,RP]
RewriteRule (.*)/default.aspx$ $1/ [I,RP]
RewriteRule (.*)/index.htm$ $1/ [I,RP]
RewriteRule (.*)/index.html$ $1/ [I,RP]

# force proper www. prefix on all requests
RewriteCond %HTTP_HOST ^test\.com [I]
RewriteRule ^/(.*) http://www.test.com/$1 [RP]

# only allow whitelisted referers to hotlink images
RewriteCond Referer: (?!http://(?:www\.good\.com|www\.better\.com)).+
RewriteRule .*\.(?:gif|jpg|jpeg|png) /images/block.jpg [I,O]

Definitely check out Scott's post and Jeff's post to learn more about these ISAPI modules, and what you can do with them.

Note: One downside to using an ISAPI filter is that shared hosting environments typically won't allow you to install this component, and so you'll need either a virtual dedicated hosting server or a dedicated hosting server to use them.  But, if you do have a hosting plan that allows you to install the ISAPI, it will provide maximum flexibility on IIS5/6 - and tide you over until IIS7 ships.

Handling ASP.NET PostBacks with URL Rewriting

One gotcha that people often run into when using ASP.NET and Url-Rewriting has to-do with handling postback scenarios.  Specifically, when you place a <form runat="server"> control on a page, ASP.NET will automatically by default output the "action" attribute of the markup to point back to the page it is on.  The problem when using URL-Rewriting is that the URL that the <form> control renders is not the original URL of the request (for example: /products/books), but rather the re-written one (for example: /products.aspx?category=books).  This means that when you do a postback to the server, the URL will not be your nice clean one.

With ASP.NET 1.0 and 1.1, people often resorted to sub-classing the <form> control and created their own control that correctly output the action to use.  While this works, it ends up being a little messy - since it means you have to update all of your pages to use this alternate form control, and it can sometimes have problems with the Visual Studio WYSIWYG designer.

The good news is that with ASP.NET 2.0, there is a cleaner trick that you can use to rewrite the "action" attribute on the <form> control.  Specifically, you can take advantage of the new ASP.NET 2.0 Control Adapter extensibility architecture to customize the rendering of the <form> control, and override its "action" attribute value with a value you provide.  This doesn't require you to change any code in your .aspx pages.  Instead, just add a .browser file to your /app_browsers folder that registers a Control Adapter class to use to output the new "action" attribute:

You can see a sample implementation I created that shows how to implement a Form Control Adapter that works with URLRewriting in my sample here.  It works for both the Request.PathInfo and UrlRewriter.Net module approaches I used in Approach 1 and 2 above, and uses the Request.RawUrl property to retrieve the original, un-rewritten, URL to render.  With the ISAPIRewrite filter in Approach 4 you can retrieve the Request.ServerVariables["HTTP_X_REWRITE_URL"] value that the ISAPI filter uses to save the original URL instead.

My FormRewriter class implementation above should work for both standard ASP.NET and ASP.NET AJAX 1.0 pages (let me know if you run into any issues).

Handling CSS and Image Reference Correctly

One gotcha that people sometime run into when using Url Rewriting for the very first time is that they find that their image and CSS stylesheet references sometimes seem to stop working.  This is because they have relative references to these files within their HTML pages - and when you start to re-write URLs within an application you need to be aware that the browser will often be requesting files in different logical hierarchy levels than what is really stored on the server.

For example, if our /products.aspx page above had a relative reference to "logo.jpg" in the .aspx page, but was requested via the /products/books.aspx url, then the browser will send a request for /products/logo.jpg instead of /logo.jpg when it renders the page.  To reference this file correctly, make sure you root qualify CSS and Image references ("/style.css" instead of "style.css").  For ASP.NET controls, you can also use the ~ syntax to reference files from the root of the application (for example: <asp:image imageurl="~/images/logo.jpg" runat="server"/>

Hope this helps,

Scott

P.S. For more ASP.NET 2.0 Tips and Tricks, please check out my ASP.NET 2.0 Tips, Tricks and Tutorials Page.

P.P.S. Special thanks to Scott Hanselman and Michael K. Campbell for testing my Form Control Adapter with their sites.

Published Monday, February 26, 2007 9:27 PM by ScottGu

Comments

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 1:26 AM by Lenard

Thanks Scott, this was a wonderful post! I was actually doing URL rewriting since ASP.NET 1.1, but this article still had some new ideas for me.

For handling postbacks, I first used the sub-classing of the form class, but then I moved to another solution where I used JavaScript to write a new  action attribute for the form. Although the HTML source still contains the wrong URL (the ugly one), it now would post back to the correct address. (This solution has drawbacks of course) But I never thought of control adapters! :)

One trick with CSS and resource files I use: set up IIS so that ASP.NET would handle .CSS and .JPG files. When the request comes into the Application object (where I do some rewriting) I remove the logical paths from these content files, and then let the ASP.NET static file handler return them.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 2:00 AM by ScottGu

Hi Tarun,

Glad you like the article! :-)

You should be able to create a URL like you listed above if you use the HttpModule solution on IIS7, or the ISAPIRewrite solution on IIS5/6.

If you add a .aspx extension to the end of your GUID, then you can also use the UrlRewriter.Net and UrlRewriting.net HttpModules on all versions of IIS/ASP.NET.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 2:06 AM by ScottGu

Hi Lenard,

Thanks for the good tips on the static files.

I think you'll like the control adapter technique, since it doesn't require any client-side script (it will work even if a browser has this turned off), and it ends up making the page code very clean.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 2:26 AM by Vikram

Interesting article. I have been using URL Rewriting for my Site through a http module and used a new control for the form action. I thnk I will plan to  move to the new control adapter as you said.

The trick with CSS is rather important when doing the rewriting. After reading the post I am really eager to  see IIS 7.0's extension for URL Rewriting.

Thanks

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 2:53 AM by Richard

Thanks Scott, this is a really good round-up of rewriting methods.

Another simple method is to use a custom 404 error handler.  Just create a new page - e.g. 404.aspx, and do whatever processing you need with the incoming page name.  Then, when you have worked out what the product id is, just do a server.transfer to the actual product.aspx?productid=12345 page.

The disadvantage is that you need access to the IIS configuration, but apart from that it is very simple, and your fake pages can have a .htm extension.

The main advantage is that it is easy to do database lookups in the 404.aspx - e.g. to find the productid from the page name etc.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 3:33 AM by Eirik

Greater article. I too have been using URL rewriting since .net 1.1, but the Form Control Adapter was new to me. Could you please provide a c# version of the Form Control Adapter? I'm having trouble converting it.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 4:27 AM by AG

Fantastic article. The control adapter technique for fixing the form action is one that is new to me, and took me all of 3 seconds to implement on my site. Many thanks.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 4:55 AM by Cosmin

Hi Scott,

Great article.

If  for url rewriting is used a custom HttpModule, in order to not have problems with css and image paths it can be used HttpContex.RewritePath(string path, bool rebaseClientPath) method with rebaseClientPath set to false. With this approach it doesn’t matter how you define your url’s to css or image files.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 6:57 AM by Flaxon

Hello,

one littel hint for the 'Handling CSS and Image Reference Correctly' and post back 'form' issue:

You can Re-rewrite the url in your module in the

application.PostMapRequestHandler event to the original url that was requested.

Simply store the requested url in the Application_BeginRequest method to Context.Items("orgUrl").

Once the Handler is mapped to the 'correct' .aspx (e.g.) which has been done before PostMapRequestHandler you can reassign the orginal request url.

Thus, the relative path problems to css files/images will not occure and the action attribute is rendered correctly

Ex:

If HttpContext.Current.Items("orgUrl") IsNot Nothing Then

   Dim _orgUrl As String

   _orgUrl = CStr(HttpContext.Current.Items("orgUrl"))

   HttpContext.Current.RewritePath(_orgUrl, True)

End If

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 7:40 AM by Doug Rees

Hi Scott,

Great stuff! Only one problem in implementing this so far, and that if a page uses a cached usercontrol Im getting nasty errors as below. And this is just a normal page, not one that is being remapped etc. I can fix it temporarily by trapping the page name and not using the rewriter for that page, but something is not right...

An instance of 'RewriteFormHtmlTextWriter' could not be used as an HtmlTextWriter. Make sure the specified class can be instantiated, extends System.Web.UI.HtmlTextWriter, and implements a constructor with a single parameter of type System.IO.TextWriter. at System.Web.UI.Page.CreateHtmlTextWriterFromType(TextWriter tw, Type writerType) at System.Web.UI.BasePartialCachingControl.Render(HtmlTextWriter output) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.Control.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) at System.Web.UI.Adapters.ControlAdapter.Render(HtmlTextWriter writer) at FormRewriterControlAdapter.Render(HtmlTextWriter writer)

Regards,

Doug

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 8:53 AM by Charles

Excellent article. I'm going to experiment with these methods right away!

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 9:17 AM by Perry

As always great article Scott!

I wanted to offer another approach, call it Approach 3.5 :)

You can simply redirect all requests to ASP.NET by adding * to the aspnet_isapi.dll and then you can use UrlRewriter rules as in IIS7.

BTW thanks for showing the control adapter technique, this brings a whole new appreciation to this feature.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 11:22 AM by davidacoder

I had always been under the impression that one could do URLs like in 4) with wild card mapping Asp.net in IIS6. But maybe I am wrong?

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 11:49 AM by Brian

URL rewriting is great and the examples you provided are excellent!  It would be great if you could elaborate on how url rewriting affects webpart personalization.  It seems like a tried this a while back and ran into troubles because the personalization provider was looking for the physical aspx file that didn't exist.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 12:07 PM by PWills

Greatest. Post. Evar.

Seriously, 'pretty URLs' is the question I am asked most frequently. Now I can just point people to this post. Thanks Scott!!

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 12:56 PM by ScottGu

Hi David,

You can * script map all files to be served by the ASP.NET ISAPI with IIS today - and this does provide a lot more flexibility with URL Rewriting via a HttpModule.

The challange, though, is that you can still run into some cases with folder names that won't work (because IIS will first try to serve out the content as a folder and bypass the ISAPI extension).

That is why I'd recommend an ISAPI Filter mechanism like ISAPIRewrite (approach 4 above).  It will work with all types of URLs with IIS 5/6 today.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 12:58 PM by ScottGu

Hi Doug,

Good catch on the user control output caching scenario!  That was a scenario I hadn't tested, and as you discovered wasn't handling correctly in my control adapter.

I just updated my samples above to fix this - it involved adding another contstructor to the 'RewriteFormHtmlTextWriter' class I created.  I've tested it with a few output caching scenarios and haven't run into any issues with it. Let me know if you find any other gotchas and I'll roll out updates for them.

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 2:59 PM by Marvin

Tried Approach 1 and now my Theme is lost when taking a link where I have changed the Query String variable to a Request.PathInfo. My Theme is set in the web.config Pages for a site-wide default setting. So how about them apples?

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 3:15 PM by ScottGu

Hi Marvin,

I just verified that Themes do work with the Url-Rewriting approach above (I set the theme in the web.config file like you did).

My guess is that you are running into the gotcha I mentioned above with absolute/root relative references in images and stylesheets (see the "Handling CSS and Image Reference Correctly" section).  

You'll want/need to absolutely reference paths from the root (or use the ~/ trick) when using Url Rewriting.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 4:17 PM by Jeff

You can also use an HttpHandler to read in the page name, and use PageParser.GetCompiledPageInstance(pagePath, filePath, context) to fire off some other page. In the handler, take the "x" in x.aspx and put it in HttpContext.Items, and have the actual page look up that item the same way you would a query string.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 6:10 PM by arnold

Hello Scott

I'm interested in how I could do URL rewriting within an AJAX postback?!, if this is possible at all.

Regards

Arnold

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 7:34 PM by David

Scott,

Thanks, I inherited an app which uses UrlRewriter.net, and the Adapter you provided was the exact solution I needed to the postback issue.

I converted the adapter code to C# if anyone needs it.

Cheers

David

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 8:36 PM by MIchael Neel

This topic was the subject of my Article in the march 2007 issue of aspNet Pro - I used HttpContext.RewritePath with a custom SiteMapProvider.  I haven't formatted the article for my blog yet, but it will be up soon(tm).

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 11:12 PM by steve schofield

Nice article Scott.  Thanks for sharing.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, February 27, 2007 11:16 PM by ScottGu

Hi Arnold,

The good news is that my FormRewriter Control Adapter above works just fine with ASP.NET AJAX.

Thanks,

Scott

# C# version of the form rewriter

Wednesday, February 28, 2007 2:30 AM by Eirik

finally figured out how to convert the Form Rewriter:

public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter

{

protected override void Render(System.Web.UI.HtmlTextWriter writer)

{

base.Render(new RewriteFormHtmlTextWriter(writer));

}

}

public class RewriteFormHtmlTextWriter : HtmlTextWriter

{

public RewriteFormHtmlTextWriter(HtmlTextWriter writer) : base(writer)

{

this.InnerWriter = writer.InnerWriter;

}

public override void WriteAttribute(string name, string value, bool fEncode)

{

if (name == "action")

{

HttpContext Context;

Context = HttpContext.Current;

if (Context.Items["ActionAlreadyWritten"] == null)

{

value = Context.Request.RawUrl;

Context.Items["ActionAlreadyWritten"] = true;

}

}

base.WriteAttribute(name, value, fEncode);

}

}

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 6:10 AM by Marvin

How do you change it so the link below is inserted properly by Asp.Net when using Themes?

<link href="App_Themes/Default/StyleSheet.css" type="text/css" rel="stylesheet" /><style type="text/css">

It is not "root relative"...

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 9:29 AM by Corey Roth

I am surprised that the urlMappings section of the web.config added in ASP.NET 2.0 was not mentioned here.  Is that not recommended for simple rewriting?

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 9:30 AM by Nathanael Jones

Nice article!

This will clear up many points that simply aren't covered properly anywhere else.

I've been having numerous issues with AppRelativeTemplateSourceDirectory defaulting to the browser URL instead of the rewritten URL. Any ideas? I am using the UrlRewritingNet engine.

Thanks,

Nathanael Jones

# Clarification

Wednesday, February 28, 2007 10:19 AM by Nathanael Jones

Further clarifying my previous question, Page.AppRelativeTemplateSourceDirectory is set correctly, but child controls specified declaratively do not inherit the value. Is this the correct behavior?

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 11:52 AM by Thanks for sharing. this article

can u send one post about multi ligual site

with use app resource

plz

plz

plz

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 1:42 PM by Nathanael Jones

Well, I did eventually figure out what was causing the AppRelativeTemplateSourceDirectory values to be off.

Pages that included server-side script tags, or code-behind files always work fine. However, if your asp.net page is purely declarative markup, the .TemplateControl references for the whole hierarchy are incorrect. For example, if a master page is used, all controls will have their TemplateControl properties set to the master page user control, even if the controls in question are located on the content page in a different directory. Normally, controls inside a contentplaceholder will have their TemplateControl property set to Page.

Symptoms only appear when relative paths are used in a content page, and the content page is located in a different folder from the master page. In this case, the relative paths will be resolved from the the master page location, instead of the content page location.

I suspect this has something to do with the ASP.NET compilation system. Maybe you could shed some light on the subject? This is getting too deep for me.

Thanks,

Nathanael

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 2:54 PM by David James Smith

Hi,

I am missing something?

For ASP.Net 2 you can simply use the urlMappings web.config element to achieve this.

Dave.

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, February 28, 2007 10:21 PM by Simone

Yep, David is right, why all that when we have rewriting inside the core of ASP.NET 2.0?

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 5:38 AM by Robert Black

How would you rewrite a URL if what you're bringing back are SQL uniqueidentifiers?

i.e if the url was

displayMe.aspx?unid={0a301bb3-2bc7-4d78-a63b-38ff649487ef}

can this be rewitten?

Thanks

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 7:15 AM by Mikael Östberg

Using urlMappings in web.config is not a  dynamic solution.

If you're looking for something more scalable and dynamic, you will have to implement one of the above mentioned methods.

::m

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 11:07 AM by Josh Stodola

Unbelievably detailed!  I feel so much smarter after reading your articles.  Thank you.

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 11:42 AM by ScottGu

Hi Robert,

You could change your URL that currently is:

displayMe.aspx?unid={0a301bb3-2bc7-4d78-a63b-38ff649487ef}

to something like:

/Users/{0a301bb3-2bc7-4d78-a63b-38ff649487ef}.aspx

or with ISAPIRewrite or IIS7:

/Users/{0a301bb3-2bc7-4d78-a63b-38ff649487ef}/

If you wanted to.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 11:44 AM by ScottGu

Hi Simone,

The built-in URLMapping feature in ASP.NET 2.0 unfortunately doesn't give you as much flexibility as the UrlMapper.net and UrlMapping.net modules I mentioned in my article above.  It works for some scenarios, but in general I'd recommend the 4 approaches I listed in the blog post above over it.

Hope this helps,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 1, 2007 12:35 PM by Scott

Any word about the loginstatus control not working with the url rewriting?  I suppose I can write my own.  But what good is having inherent asp.net controls if they don't function with something else supported by asp.net?

# re: Tip/Trick: Url Rewriting with ASP.NET

Friday, March 2, 2007 12:07 PM by Michael

Does anyone else have an issue with using RewritePath, and when the HttpModule rewrites the url the Page.IsPostBack property is false?  Is there any way to resolve this?

# re: Tip/Trick: Url Rewriting with ASP.NET

Saturday, March 3, 2007 9:22 PM by ScottGu

Hi Michael,

I just tried duplicating the problem you mentioned with Page.IsPostback, and haven't been able to reproduce it (the behavior is fine using the samples I uploaded above).

I'm wondering if you might have introduced an issue somewhere with some other code?

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Saturday, March 3, 2007 9:26 PM by ScottGu

Scott - what problem are you having with the loginstatus control?  Can you send me more details - I can't seem to repro one.

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Sunday, March 4, 2007 8:34 PM by JeffyPop

Could Scott or someone put together a simple demo/starter of how to do URL rewriting with a master page, the page content and navigation coming out of a SQL Server 2005 Express database, and some postback handling?  I'm completely lost trying to rewrite a site that was built in Classic ASP that has the old "ugly" links the menus and content in an Access database.  I'd also love an example of postback redirecting to the same page without polluting the browser history with stale versions of a page.

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, March 6, 2007 11:34 PM by Avinash Dewangan

I am first time user of url rewriting, and this helps me a lot to do the task completely.

thanks

# ControlAdapter throwing "Object reference..." exception?

Wednesday, March 7, 2007 12:10 AM by Josh

Hi, this is wonderful!  I've been looking for this solution for quite a while now.  I, of course, have a problem.

The FormRewriterControlAdapter is throwing an "Object reference not set to an instance of an object" exception on an AJAX callback.  I just have a simple page with a textbox on it and when you click a button it appends an exclamation mark onto the end and puts the result in a label.  Anyone else have a similar problem?

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, March 7, 2007 12:19 AM by ScottGu

Hi Josh,

Can you .zip up and email me a simple repro of the AJAX call back issue you are seeing?

I tested the FormRewriterControlAdapter with the <asp:updatepanel> control and it worked fine.  There might be another scenario I missed though.  If you send me a repro that I can use locally, I'll figure out what is the issue.

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Wednesday, March 7, 2007 1:35 AM by ScottGu

Hi Josh - sure, feel free to use my Microsoft email (scottgu@microsoft.com).

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 8, 2007 6:54 AM by Firoz Ansari

Ok. After making little code hacking, I am now able to render page with ASP.NET Theme. For that, I have remove runat="server" attribute from HEAD tag and put custom CSS path in HEAD. With that, I have also remove Theme="Default" attribute from Page directive. And outcome: extension less URL in IIS 5.1 under WindowXP Professional using HttpModule.

Thanks,

Firoz

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, March 8, 2007 10:39 AM by kazuk

Hi Scott.

I do URL Rewriting and handling PostBack on my application.

1. save original URL and Context.RewritePath to inner server url on Application_BeginRequest

2. Context.RewirtePath to saved original URL on Application_PreHandlerExecute

This method not require any aspx changes, and not require additional file.

# re: Tip/Trick: Url Rewriting with ASP.NET - sitemap

Wednesday, March 14, 2007 6:02 AM by louzoid

Hi Scott, great post!

I am developing a small site and the pathinfo approach is just perfect for my needs.  Unfortunately it is screwing up my .net sitemap provider which can no longer grab the currentnode:(

anybody have any ideas on how I can resolve this?

cheers all.

# ControlAdapter throwing "Object reference..." exception?

Wednesday, March 14, 2007 10:44 AM by Corné

Hello Josh / Scott,

The "Object reference not set..." error i had was because i made a mistake with translating the code from vb to c#. I forgot to add this line to the constructor of our htmltextwriter.

Maybe this solves your problem too!

public RewriteFormHtmlTextWriter(HtmlTextWriter writer) : base(writer)

{

this.InnerWriter = writer.InnerWriter;

}

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, April 3, 2007 6:27 AM by Shail

Hello Scott,

I am using url rewrite as you specified above in approach 2.

When I use Gridview, its going back to original URL, not the rewrited one.

Have you tested that also

# re: Tip/Trick: Url Rewriting with ASP.NET

Monday, April 9, 2007 11:04 AM by Sunny

I do not know how I have to resolve the css files. The option with the ~ does not works in <link href="../css/stylesheet.css" rel="stylesheet" type="text/css" /> and the option to use an absolute link is a hack, in my opinion.

Does anyone have found a solution to tackle this problem or miss I something?

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, April 12, 2007 1:32 AM by Chad

Scott,

I'm running into a problem with ASP.NET 2.0 themes while rewriting my urls. I saw what you posted as the fix, but how do you rebase the way asp.net is linking the css files in a theme?

I could simply remove the runat="server" tag from the head section of the master file, then manually link the css files... but that defeats the purpose of the asp.net themes doesn't it?

Thanks in advance for any help you can provide on this!

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, April 12, 2007 6:53 AM by Shail

Hello Sunny,

You are right that we need to use absolute path, as ~ is not working.

We also tried to solve it too much, but there was  no work around other than absolute path.

Because of strict deadlines we have to go for hack.

Scott,

is it possible for you to include CSS, themes and images in your example code.

That will be great for us

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, April 24, 2007 2:15 AM by Ryan Williams

Hey Scott,

What was the resolution to Josh's problem?  I too am getting null ref exceptions when doing AJAX postbacks (using ISAPI rewrite as well).  Works great for standard (non-AJAX) stuff.

System.NullReferenceException was unhandled by user code

 Message="Object reference not set to an instance of an object."

 Source="System.Web.Extensions"

 StackTrace:

      at System.Web.UI.PageRequestManager.RenderFormCallback(HtmlTextWriter writer, Control containerControl)

      at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)

      at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)

      at System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer)

      at System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output)

      at System.Web.UI.Adapters.ControlAdapter.Render(HtmlTextWriter writer)

      at FormRewriterControlAdapter.Render(HtmlTextWriter writer) in c:\src\Web\App_Code\FormRewriterControlAdapter.cs:line 23

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, April 24, 2007 11:36 AM by ScottGu

Hi Ryan,

I believe the problem you are running into is that you either don't have the ControlAdapter class registered, or perhaps you translated it to VB and didn't implement the same logic?

I believe Josh had ported it to C#, but had left out one of the constructors, which was causing the error.

Thanks,

Scott

# re: Tip/Trick: Url Rewriting with ASP.NET

Tuesday, April 24, 2007 9:25 PM by Ryan Williams

Indeed, the port was the issue.  I had both constructors, but one was incorrect.

# re: Tip/Trick: Url Rewriting with ASP.NET

Thursday, April 26, 2007 11:34 AM by Shail

Hi Scott,

Please update your example for Themes and images. We are still doing that hard coding.

That will keep our faith in ASP.NET and its power