Introduction
Most of developers I talked to would prefer to rewrite URL by getting URLs dynamically from the database, rather than just create paths from QueryStrings.
I made a little research and discovered that most of the articles describe the second scenario - you have a URL like this:
www.mysite.com/products.aspx?productID=5 or www.mysite.com/catalogue.aspx?id=furniture
What you can do simply by reading the query string is to create virtual URL like this:
www.mysite.com/products/product5.aspx or www.mysite.com/furniture/default.aspx
But what if you have a more complex application, for example a content management system that allows users to insert unlimited number of pages and go to unlimited depths? In that case, you should create your own Module.
Beside all of this, URL rewriting will improve your rankings on search engines. Search engines like Google will easily index your "static" urls, instead of dynamic urls.
How to do this?
There are several ways to accomplish this. I'll explain how to do this by using HttpModule and how to overcome the postback bug that is the outcome of URL rewriting.
Let's start
Ok, let's start now. Suppose you have a content management system that stores entire pages in the database. So you can have a Home page, and Home page can have sections Products and Services, and each one of these can have their own child pages or sections, and so on.
So we want dynamic URL like this: Default.aspx?SectionID=5&ItemID=22 to look like /catalogue/furniture/chairs/chair5.aspx or whatever the business logic requirement is.
In the example in this article I will not use a database in order to keep it simple, but you imagine there is a database that keeps the URL for each page. I'll use hard-coded Dictionary that will keep some sample pages.
Note: You can download the full code in the attachment.
First, we'll make a data access object that will search the database for requested url and return its dynamic url. These are the methods in SampleDAO that will simulate the database and getting the url from there:
public string GetRealPath(string requestedUrl)
{
string path = "";
Dictionary<string, string> paths = GetPathsFromDatabase();
if (paths.ContainsKey(requestedUrl))
paths.TryGetValue(requestedUrl, out path);
return path;
}
private static Dictionary<string, string> GetPathsFromDatabase()
{
Dictionary<string, string> paths = new Dictionary<string, string>();
paths.Add("/URLRewrite/FirstSection/Default.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=1");
paths.Add("/URLRewrite/SecondSection/Default.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=2");
paths.Add("/URLRewrite/FirstSection/Page1.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=1&Item=1");
paths.Add("/URLRewrite/FirstSection/Page2.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=1&Item=2");
paths.Add("/URLRewrite/SecondSection/Page1.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=2&Item=1");
paths.Add("/URLRewrite/SecondSection/SubSection/AnotherOne/Page5.aspx".ToLower(), "/URLRewrite/Default.aspx?SectionID=2&Item=5");
paths.Add("/URLRewrite/Default.aspx".ToLower(), "/URLRewrite/Default.aspx");
return paths;
}
What does this means? When you create links on your web pages, you will no longer use query parametrised URLs but rather static URLs. Now what? If you request a page that doesn't exist, HttpException will be thrown or default redirection will occur, but this is the place where Rewrite module comes in.
Rewrite module (in our Example UrlRewriteModule) implements IHttpModule interface. This means you will have to implement two methods: Init and Dispose. Init method is more important. It accepts HttpApplication and lets you hook on whatever you want. In our case we'll hook on Application BeginRequest event so that we can read a requested URL each time a new request begins its lifecycle.
Look at the code below. In the Application_BeginRequest event handler we get the requested url which is a static URL, and search the database to find the corresponding dynamic url.
public void Init(System.Web.HttpApplication app)
{
app.BeginRequest += new EventHandler(Application_BeginRequest);
}
private void Application_BeginRequest(object sender, EventArgs e)
{
System.Web.HttpApplication app = (System.Web.HttpApplication)sender;
string requestedUrl = app.Request.Path.ToLower();
string realUrl = GetRealUrl(requestedUrl);
if (!String.IsNullOrEmpty(realUrl))
app.Context.RewritePath(realUrl, false);
}
private string GetRealUrl(string requestedUrl)
{
// Implement your own logic here
SampleDAO sampleDAO = new SampleDAO();
return sampleDAO.GetRealPath(requestedUrl);
}
There is one more thing to do before this handler becomes fully functional. We have to register it in web.config file. We just have to add a new httpModule in the system.web section. Under name you have to type it's class name, and under type you'll have to type it's full assembly name. Since our module is in the current project in App_Code folder, we'll just provide its name as a type.
<httpModules>
<add name="UrlRewriteModule" type="UrlRewriteModule"/>
</httpModules>
So, what's the problem?
And that's all you need to make it work. However, there is a problem with postback when you are using URL rewriting. Here's the problem: the action attribute of the server-side form tag on your page will point to the original page, and it's url is not a static one, but rather dynamic. So you will loose your URL rewrite functionality on each postback.
The solution is to "tweak" the output of the action attribute. This approach is known as Control adapter architecture. You can read a post by Scott Guthrie that describes this approach here.
Anyway, we'll just have to add a new .browser file and register our FormRewriterControlAdapter.
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
adapterType="FormRewriterControlAdapter" />
</controlAdapters>
</browser>
FormRewriterControlAdapter class inherits from System.Web.UI.Adapters.ControlAdapter. The code is quite simple, so check it out below:
public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(new RewriteFormHtmlTextWriter(writer));
}
}
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
: base(writer)
{
this.InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
: base(writer)
{
base.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
if (name == "action")
{
HttpContext Context = HttpContext.Current;
if (Context.Items["ActionAlreadyWritten"] == null)
{
value = Context.Request.RawUrl;
Context.Items["ActionAlreadyWritten"] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
}
And that fixes the problem entirely.
Conclusion
This is all you have to do to make URL rewriting work properly. The logic for getting the coresponding dynamic URL for the requested one can be different. You can store both static and dynamic paths in a database and perform a search, you can use regular expressions, you can create the methods for parsing the requested url and search for the item in the database, or you can implement your own custom logic.
You can get more information about URL rewriting on Wikipedia, or read very detailed explanation on MSDN articles. And don't forget to download the code in the attachment.
I hope this helps!