Click here to Skip to main content
15,888,454 members
Articles / All Topics

How-to Create an Easy and Flexible URL-Rewriter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
31 Mar 2011CPOL6 min read 12.8K   2
How-to: Create an easy and flexible URL-Rewriter

Today, I would like to show you how you can create a small, easy and flexible URL Rewriter / Redirector Module.

Introduction

In state of the art webs, a URL Rewriter is essential for a recognizable URL and for SEO (search engine optimization). Only this way, we can build dynamic / virtual URLs with which a user can guess the topic only by looking at the URL.

So a URL Rewriter just does nothing less than give non-file-matching URLs to the user and internally rewrites the path to a server-known script which returns the data. Let’s start with the modules.

In a normal ASP.NET web project, there are .aspx pages, perhaps .ashx HttpHandlers and in some cases, there is a global.asax to provide methods which handle a request. We could start the URL-Rewriter by adding some code in the existing (or in a new) global.asax, but this would not give us the flexibility to use the URL-Rewriter module in another project. So we will start our module by creating a new class-library calling it “RedirectorModule” and adding two classes called “RedirectHandler” and “RewriteHandler” (used for the demo):

First, we will prepare both files, that can handle incoming requests. To achieve this, we have to add a reference to System.Web (perhaps you have to import a reference to the System.Web .NET library) and implement the IHttpModule Interface this way:

Then, we have to attach a method to handle the requests to the HttpApplication.BeginRequest event:

C#
public void Init(HttpApplication context)
{
    context.BeginRequest += context_BeginRequest;
}

And implement the method:

C#
private void context_BeginRequest(object sender, EventArgs e)
{

}

So by attaching to the BeginRequest of the HttpApplication, our attached method will be executed every time a request will be processed by our module. Now we can start implementing the Redirect and Rewrite functions of our two HttpModules.

Redirect-Module

First, we start with the Redirector module. In this module, I will show you how you can create a URL-dependent redirector, which you can use for example for short-urls on your page (example: http://yourdomain.com/news will redirect to http://yourdomain.com/company/news/).

To achieve this, we have to add the following code to the context_BeginRequest() method.

We start with some initialization:

C#
// Initialization.
var application = (HttpApplication)sender;
var context = application.Context;
var request = context.Request;
var response = context.Response;

Then, we need the path and querystring parameters of the current request to decide where we have to redirect:

C#
// Load the path and query parameters.
var path = request.Url.AbsolutePath.ToLower(CultureInfo.CurrentCulture);
var query = request.Url.Query;

// Process the path parameter.
int lastSlashIndex = path.LastIndexOf('/');
if (lastSlashIndex > 0)
{
    path = path.Substring(0, lastSlashIndex);
}

In this code, we first get the lower variant of the Url.AbsolutePath. Then, the path will be cutoff at the last “/” that we only get the folder part of the path. After we have the raw URL-path part, we can process it. In our example, we make a simple switch / case:

C#
// Process the current URL.
switch(path)
{
    case "/news":
        response.Redirect("/company/news/");
        break;
}

This part is the main-processing of the URL. It can be done (like in our example) by a switch / case, with a database query over a redirect-table, with a regular expression search, or with anything else where you can decide what you want to do with the URL.

So the whole context_BeginRequest() looks like this:

C#
private void context_BeginRequest(object sender, EventArgs e)
{
    // Initialization.
    var application = (HttpApplication)sender;
    var context = application.Context;
    var request = context.Request;
    var response = context.Response;

    // Load the path and query parameters.
    var path = request.Url.AbsolutePath.ToLower(CultureInfo.CurrentCulture);
    var query = request.Url.Query;

    // Process the path parameter.
    int lastSlashIndex = path.LastIndexOf('/');
    if (lastSlashIndex > 0)
    {
        path = path.Substring(0, lastSlashIndex);
    }

    // Process the current URL.
    switch(path)
    {
        case "/news":
            response.Redirect("/company/news/");
            break;
    }
}

Now, we finished our test Redirect-Module and we can start implementing the Rewrite-Module.

Rewrite-Module

The Rewrite-Module is built mostly the same way as the Redirector-Module, so I will not repeat the equal code and just show you the starting “template”:

C#
private void context_BeginRequest(object sender, EventArgs e)
{
    // Initialization.
    var application = (HttpApplication)sender;
    var context = application.Context;
    var request = context.Request;
    var response = context.Response;

    // Load the path and query parameters.
    var path = request.Url.AbsolutePath.ToLower(CultureInfo.CurrentCulture);
    var query = request.Url.Query;

    // Process the path parameter.
    int lastSlashIndex = path.LastIndexOf('/');
    if (lastSlashIndex > 0)
    {
        path = path.Substring(0, lastSlashIndex);
    }

    // Process the current URL.
    // ## PROCESS CODE ##
}

Now the main difference between the redirect and the rewrite is that the rewrite usually does not break the request, while the redirect completely ends the request and sends a HttpResponse with the status code 302 (moved) back to the browser, which makes a new request to the new URL.

So in the rewrite module processing, we want to handle the request to rewrite the URL http://yourdomain.com/company/news/ to the internal URL http://yourdomain.com/default.aspx?category=company&page=news.

Here is the code to manage the rewrite to the internal URL:

C#
var pathParts = path.Split(new []{ '/' }, StringSplitOptions.RemoveEmptyEntries);

// process the path by the rewrite, if there is a path.
if (pathParts.Length > 0)
{
    // Define the rewrite path template.
    const string rewritePath = "~/Default.aspx?category={0}&page={1}";
       
    // Load the category part (first part of the path).
    var category = pathParts[0];
    // Load the page part (if there is more than one path part available).
    var page = pathParts.Length > 1 ? pathParts[1] : string.Empty;
               
// Rewrite the URL according the rewrite path template (and do not rebase the
// client path).
    context.RewritePath(
        String.Format(CultureInfo.CurrentCulture, rewritePath, category, page),
        false);
}

This is done just by splitting the path into its parts and assume that the first part is the category and the second part is the page parameter.

Now that we only rewrite unknown URLs (and that known pages still get accessed normally), we make a check around the above code, if the current URL exists in our file-system:

C#
if (!File.Exists(context.Server.MapPath(request.FilePath)))

So our rewrite code overall looks like this:

C#
private void context_BeginRequest(object sender, EventArgs e)
{
    // Initialization.
    var application = (HttpApplication)sender;
    var context = application.Context;
    var request = context.Request;

    if (!File.Exists(context.Server.MapPath(request.FilePath)))
    {
        // Load the path and query parameters.
        var path = request.Url.AbsolutePath.ToLower(CultureInfo.CurrentCulture);
        var query = request.Url.Query;

        // Process the path parameter.
        int lastSlashIndex = path.LastIndexOf('/');
        if (lastSlashIndex > 0)
        {
            path = path.Substring(0, lastSlashIndex);
        }

        // Process the current URL.
        var pathParts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);

        // process the path by the rewrite, if there is a path.
        if (pathParts.Length > 0)
        {
            // Define the rewrite path template.
            const string rewritePath = "~/Default.aspx?category={0}&page={1}";

            // Load the category part (first part of the path).
            var category = pathParts[0];
            // Load the page part (if there is more than one path part available).
            var page = pathParts.Length > 1 ? pathParts[1] : string.Empty;

            // Rewrite the URL according the rewrite path template (and do not rebase the client path).
            context.RewritePath(
                String.Format(CultureInfo.CurrentCulture, rewritePath, category, page),
                false);
        }
    }
}

After the HttpContext.RewritePath(), the server calls the rewritten page on the server (without any feedback to the browser before the rewritten page gets back). So we are able to create virtual paths according any specification (database defined URLs, Regex URLs, key / value based URLs, etc).

Web.config Modifications

At the end, we need to inform our web server (normally an IIS), that the URL processing must be done not by the standard ASP.NET HttpModule (which just returns physical files) but by our newly created Redirect- and Rewrite module. This requires some modifications in the site’s web.config file.

First, we have to add and enable the HttpModules in our web.config by adding these lines:

XML
<configuration>
    <system.web>
        <httpModules>
            <clear/>
            <add name="RedirectHandler" type="RedirectorModule.RedirectHandler"/>
            <add name="RewriteHandler" type="RedirectorModule.RewriteHandler"/>
        </httpModules>
    </system.web>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true">
            <remove name="RedirectHandler"/>
            <add name="RedirectHandler" type="RedirectorModule.RedirectHandler"/>
            <remove name="RewriteHandler"/>
            <add name="RewriteHandler" type="RedirectorModule.RewriteHandler"/>
        </modules>
    </system.webServer>
</configuration>

In the httpModules node of the configuration/system.web, first we need to clear the existing modules, then we have to add our own modules. There, you have to specify the name of the HttpModule and the type which will handle the requests. Now because the redirects must be executed mostly before the rewrites, we have to first add the redirect module. After that, we add the rewrite module.

The second part of the web.config modifications, the system.webserver part, is only needed for IIS 7 or higher for ASP.NET 3.5 / 4.0 projects. The parameter “runAllManagedModulesForAllRequests” does just say the IIS, that the below stated modules just handle all requests (and not just the ASP.NET known file-requests).

Attention: On the IIS 6, there are modifications on the IIS configurations needed that the ASP.NET knows that all file-types should be handled by the ASP.NET parser. The Wildcard Application Mapping settings are described here.

Finish, Conclusion and Known Problems

Now, we are finished with our redirect and rewrite module and we can start having fun with it. :)

In my opinion, this is the best way to get custom URLs which are highly customizable and really good for handling SEO problems!

Known Problems

The rewrite is really cool, but there are some points where you have to look at. Especially, you have to be careful by using forms, because the form-action URL will be completely messed up after a rewrite (if you use internal query-string parameters in the rewritten URL). This can be handled through a ControlAdapter for the form server control, where you just reset the action URL to an empty string at the rendering phase. This way, a post-back will always be handled by the current URL.

So I hope you enjoyed this HowTo! If you have any suggestions or any feedback (or found errors here), please feel free to write a comment! I really appreciate all comments!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Switzerland Switzerland
I'm a software developer with ASP.NET experience since 2007

Projects:
=========
See my projects list here: http://dotnetcorner.ch/Home/Projects

=========
More about me: http://dotnetcorner.ch
My Blog: http://dotnetcorner.ch/Home/Blog

Comments and Discussions

 
QuestionIt is not working properly. problem generated in web.config Pin
Dadu Da30-Aug-12 0:31
Dadu Da30-Aug-12 0:31 
AnswerRe: It is not working properly. problem generated in web.config Pin
Christoph Keller30-Aug-12 0:51
Christoph Keller30-Aug-12 0:51 
Hi,

It seems that you do not have access to the .dll where the RedirectorModule is located. Have you tried to change the type to:

XML
<httpModules>
  <clear/>
  <add name="RedirectHandler" type="RedirectorModule.RedirectHandler, <YourLibraryName>, Version=1.0.0.0, Culture=neutral"/>
  <add name="RewriteHandler" type="RedirectorModule.RewriteHandler, <YourLibraryName>, Version=1.0.0.0, Culture=neutral"/>
</httpModules>


There you give the ASP.NET Application a hint to search the type in the library <YourLibraryName>.

If this is still not working, I need to know the exact assembly-name and namespace where the Classes RedirectHandler and RewriteHandler are located. Also if you sign (strong-name) the .dll, you have to add , PublicKeyToken=xxxxxxxxx at the end of the type-string.

Hope this helps.

Best regards,
Chris
New Url Un-Shortener available: http://dotnetcorner.ch/tools/unshortener/

Check out my website http://dotnetcorner.ch/ and my blog http://blog.dotnetcorner.ch/

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.