24. August 2008 21:23 by Janko in Tutorials | tags:

I hope you hate URLs that look like this www.mysite.com/page.aspx?something=1&somethingelse=15&other=abcd&category=32. If you do, let's have a little reminder on rewriting URLs. But, if you don't, please take this article seriously :)

So, you hate such URLs

Or you'll hate it after you realize how easy it is to make it nicer. Besides all, URL rewriting will improve your rankings on search engines. Search engines like Google will easily index your "static" URLs, instead of dynamic URLs.

There are several ways to accomplish URL Rewriting. I'll explain how to do this by using HttpModule and how to overcome the postback bug that is the outcome of URL rewriting.

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. 

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

If you liked this article why don't you share it:

Stumble it delicious Digg it Float it DZone it Kick it Bump it E-mail

Comments

Pingbacks and trackbacks

  1. Trackback from DotNetKicks.com URL rewriting in ASP.NET web applications
  2. Pingback from mgalinks.wordpress.com 2008 August 26 - Links for today « My (almost) Daily Links

Add comment

   
        Country flag
biuquote
Loading