Site Search:
Sign in | Join | Help

This Blog

Syndication

ASP.NET

Notes, Tricks and Tips on ASP.NET Coding

URL Rewriting

This blog will detail how to do URL Rewriting.

The idea here is to provide a method to allow us to write URLs like these

http://www.store.com/articles/links
http://www.store.com/articles/aboutus
http://www.store.com/articles/resources

and have them resolve to

http://www.store.com/articles.aspx/links
http://www.store.com/articles.aspx/aboutus
http://www.store.com/articles.aspx/resources

Note that the source URLs don't have any '.aspx' at all.

You can skip all the discussion and simply download the sample project here, if you want.

Sources used in this article:
http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
http://urlrewriter.net/

This approach requires IIS 7, and will also run in design mode in Visual Studio 2008. If you don't have this technology, Scott Gu details other options in his blog (see the link above).


So, how's it done?
Several techniques are melded together.
1 - We use the URL Rewriting code from www.urlrewriter.net
2 - There are issues with postbacks on forms that use this technique. In order to solve that we 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 solves the problem neatly.

 


URL Rewriting using www.urlrewriter.net

UrlRewriter.NET is an open-source, light-weight, highly configurable URL rewriting component for ASP.NET 1.1 and 2.0. UrlRewriter.NET provides similar IIS Rewrite capabilities that the Apache web server provides with mod_rewrite and .htaccess. You don’t need to install an ISAPI Rewrite filter to use the component. Best of all, UrlRewriter.NET is free and licensed with a very permissive MIT-style licence.

To use, download and unpack the project, place the dll in your projects bin directory and set a reference to it.
Make the following changes to your web.config file

<?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 we've configured the UrlRewriter in both the <httpModules> and IIS7 <modules> section, we 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.

To test, write a default.aspx page like this

 


<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    default page<br />
    <a href="articles.aspx?art=links" mce_href="articles.aspx?art=links">links (using the regular style of link)<br />
    <a href="articles/links" mce_href="articles/links">links (using the 'rewritten' link)</a>
    <br />
    This image is show to prove that we won't lose the reference to it<br>
    <asp:Image ID="imgGabby" runat="server" ImageUrl="~/images/gabby.jpg" />
    </div>
    </form>
</body>
</html>

and an articles.aspx page like this

 


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">


<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <a href="/Default.aspx" mce_href="/Default.aspx">Home<br />
        articles <br />
        article name: <br ID="lblArticleNumber" runat="server" />
        <br ID="btnPostBack" runat="server" Text="Post Back" />
        </asp:Label ID="lblTime" runat="server">
        <br />
        This image is show to prove that we won't lose the reference to it<br>
        <asp:Image ID="imgGabby" runat="server" ImageUrl="~/images/gabby.jpg" />


    </div>
    </form>
</body>
</html>

I added code behind the articles page like to demonstrate what's going on. In the Page_Load we get the 'art' value from the querystring and display it on the page. There is also a label named 'lblTime' that we populate on the postback event of btnPostBack, to demonstrate the issue that occurs on post backs. We'll solve that in the next section. For now, get this working. Note that we also include an image on this page, to demonstrate that we are not loosing the references in an of this manuvering. The key is to qualify all css and image sources to the root of the application: "style.css" becomes "/style.css", and anything referenced in an asp control just needs the '~' in front of it, like this: <asp:Image ID="imgGabby" runat="server" ImageUrl="~/images/gabby.jpg" />

 


    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Request("art") > "" Then
            Me.lblArticleNumber.Text = Request("art")
        End If
    End Sub


    Protected Sub btnPostBack_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnPostBack.Click
        Me.lblTime.text = Now
    End Sub


Next, let's look at the more complicated postback issue. Without this code, if you send your page to http://mysite.com/articles/links, if for any reason you do a postback on that page it will show up the second time as http://mysite.com/articles.aspx?art=links. In order to fix this, add a new 'browser' file (WEBSITE > ADD NEW ITEM, choose the 'browser file' icon). This should show up in the App_browsers folder. Here is the code:

 


<browsers>
 <browser refID="Default">
  <controlAdapters>
   <adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
      adapterType="FormRewriterControlAdapter" />
  </controlAdapters>
 </browser>
</browsers>

Now create a new class called FormRewriter.vb (this should appear in the App_Code folder). Here is the code:

 


Imports Microsoft.VisualBasic


Public Class FormRewriterControlAdapter
    Inherits System.Web.UI.Adapters.ControlAdapter


    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
        MyBase.Render(New RewriteFormHtmlTextWriter(writer))
    End Sub


End Class


Public Class RewriteFormHtmlTextWriter
    Inherits HtmlTextWriter


    Sub New(ByVal writer As HtmlTextWriter)
        MyBase.New(writer)
        Me.InnerWriter = writer.InnerWriter
    End Sub


    Sub New(ByVal writer As System.IO.TextWriter)
        MyBase.New(writer)
        MyBase.InnerWriter = writer
    End Sub


    Public Overrides Sub WriteAttribute(ByVal name As String, ByVal value As String, ByVal fEncode As Boolean)


        ' If the attribute we are writing is the "action" attribute, and we are not on a sub-control, 
        ' then replace the value to write with the raw URL of the request - which ensures that we'll
        ' preserve the PathInfo value on postback scenarios


        If (name = "action") Then


            Dim Context As HttpContext
            Context = HttpContext.Current


            If Context.Items("ActionAlreadyWritten") Is Nothing Then


                ' Because we are using the UrlRewriting.net HttpModule, we will use the 
                ' Request.RawUrl property within ASP.NET to retrieve the origional URL
                ' before it was re-written.  You'll want to change the line of code below
                ' if you use a different URL rewriting implementation.


                value = Context.Request.RawUrl


                ' Indicate that we've already rewritten the <form>'s action attribute to prevent
                ' us from rewriting a sub-control under the <form> control


                Context.Items("ActionAlreadyWritten") = True


            End If


        End If


        MyBase.WriteAttribute(name, value, fEncode)


    End Sub


End Class

Exactly how this works is beyond the scope of this article, but it does and this should be all you need. 

 

Comments

No Comments

About Steve Gray

Steve is a seasoned (translate: old) developer in VB and ASP.NET. He spends most of his time in Dynamics GP, writing custom mods for consulting firms. Crystal reports, eConnect, VS Tools for Dynamics... anything that comes along.