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.