Tim

Footprints in the snow of a warped mind

CodeManagement

Where to find me

Flickr Icon  Twitter Icon  Linked In Icon  FaceBook Icon  Windows Live Alerts Butterfly  RSS 2.0 

Business Protection by Crisis Cover

Tag Cloud

AJAX (4) Analysis (3) ASP (6) ASP.Net (57) Error Reporting (4) Web Service (2) WSDL (1) Atlas (2) Born In The Barn (1) Business (87) Business Start-up Advice (30) Client (17) Expanding Your Business (21) Recruitment (1) C# (20) Canoeing (4) Canoe Racing (5) Cheshire Ring Race (5) Racing (2) Training (4) CIMA (1) Cisco (1) 7970G (1) CMS (1) Code Management (1) Cohorts (3) Commerce4Umbraco (1) Content (1) Content Management (1) Content Management System (1) CSS (3) dasBlog (5) DDD (1) Design (10) Icons (1) Development (22) eCommerce (8) Employment (2) General (39) Christmas (6) Fun and Games (11) Internet (22) Random (46) RX-8 (8) Helpful Script (3) Home Cinema (2) Hosting (2) HTML (1) IIS (11) iPhone (1) JavaScript (4) jQuery (1) Marketing (6) Email (1) Multipack (1) MVC (1) Networking (3) Nintendo (1) Nuget (1) OS Commerce (1) Payment (1) Photography (1) PHP (1) PowerShell (2) Press Release (1) Productivity (2) Random Thought (1) Security (2) SEO (5) Server Maintenance (6) Server Management (11) Social Media (2) Social Networking (3) Experiment (1) Software (10) Office (5) Visual Studio (13) Windows (4) Vista (1) SQL (8) SQL Server (19) Statistics (2) Stored Procedure (1) TeaCommerce (1) Testing (2) The Site Doctor (125) Turnover Challenge (1) Twitter (3) uCommerce (9) Umbraco (29) 2009 (1) 2011 (1) Web Development (65) WebDD (33) Wii (1) XSLT (1)

Blog Archive

Search

<February 2012>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

Recent Comments

Blog Archive

Various Links

Blogs I Read

[Feed] Google Blog
Official Google Webmaster Central Blog
[Feed] Matt Cutts
Gadgets, Google, and SEO
[Feed] Ol' Deano's Blog
My mate Dean's blog on my space, equally as random as mine but not off on as much of a tangent!
[Feed] Sam's Blog
Sam is one of my younger brothers studying Product Design and Manufacture at Loughborough, this is his blog :) Enjoy!

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

newtelligence dasBlog 2.2.8279.16125

Send mail to the author(s) Email Me (Tim Gaunt)

© 2012 Tim Gaunt.

Sign In

# Monday, February 21, 2011

Beware! Context.RewritePath does not end the current execution path

Monday, February 21, 2011 8:07:07 AM (GMT Standard Time, UTC+00:00)

We've recently been working on an inherited project that's got some interesting "features", one of which is how they handle URL rewriting.

There are a number of HTTPHandlers that can be plugged into your application or ISAPI filters to enable URL rewriting in IIS (and now routes in ASP.Net 4 etc) but a fairly classic/old method was to handle it within your Global.asax's Application_BeginRequest method.

A Simple Example

The example below rewrites a path such as "/article/123.aspx" and transforms it to "/articledisplay.aspx?id=132"

<%@ Application Language="C#" %>
<script RunAt="server">
    /// <summary>
    /// Begins the application request
    /// </summary>
    /// <param name="sender">The source of the event</param>
    /// <param name="e">A System.EventArgs that contains the event data</param>
    void Application_BeginRequest(Object sender, EventArgs e)
    {
        // Get the current path (this will be an root relative link e.g. /article/123.aspx)
        string path = Request.Path.ToLower();

        // Work out if we want to transform it
        if (path.Contains("/article/"))
        {
            string id = path.Replace("/article/", String.Empty).Replace(".aspx", String.Empty);
            Context.RewritePath(String.Concat("~/articledisplay.aspx?id=", id), false);
        }
    }
</script>

Ignoring the pro's and cons of using this method to rewrite the paths, one thing you should be aware of is that Context.RewritePath does not end the code execution.

Why's that important to know?

The problem with this is simple, if you have additional rules later in the code, they will also run -which could end up causing quite a lot of confusion.

Consider the following example Application_BeginRequest method (overlook the semantics of the code):

// Get the current path (this will be an root relative link e.g. /category/123.aspx)
string path = Request.Path.ToLower();

// It's a category request
if (path.Contains("/category/"))
{
    // Use the id to perform some form of database lookup i.e. the category
    int id = Category.GetIdByUrl(path);

    // Redirect the user to the page to display the category
    Context.RewritePath(String.Concat("~/rewritepath.aspx?type=category&id=", id), false);
}

// It's a product request
if (path.Contains("/product/"))
{
    string id = path.Replace("/product/", String.Empty).Replace(".aspx", String.Empty);

    // Redirect the user to the page to display the category
    Context.RewritePath(String.Concat("~/rewritepath.aspx?type=product&id=", id), false);
}

Now think about where the visitor would end up if they go to: /category/product/123.aspx is it /rewritepath.aspx?type=category&id=123 or /rewritepath.aspx?type=product&id=123?

Due to the way Context.RewritePath works, it executes /rewritepath.aspx?type=product&id=123 so where's the issue?

The problem is that although the pages behind it don't actually get executed until the end of the Application_BeginRequest method, because the url satisfies both the url rules, the database call will happen -even though in reality, it's not needed. The result is that you could end up with a massive (and unnecessary) overhead to every request hitting the ASP.Net engine (and if you have wildcard mapping enabled bare in mind that's every request -including images/css/javascript).

So if you feel the need to include additional rules (or additional processing e.g. database calls), consider the order of your rules and return from the method as soon as you know your rules are getting fulfilled, the above example would then be written as:

// Get the current path (this will be an root relative link e.g. /category/123.aspx)
string path = Request.Path.ToLower();

// It's a product request
if (path.Contains("/product/"))
{
    string id = path.Replace("/product/", String.Empty).Replace(".aspx", String.Empty);

    // Redirect the user to the page to display the category
    Context.RewritePath(String.Concat("~/rewritepath.aspx?type=product&id=", id), false);

    // We know that the user is going to the right place so no more rules need to be executed -return
    return;
}

// It's a category request
if (path.Contains("/category/"))
{
    // Use the id to perform some form of database lookup i.e. the category
    int id = Category.GetIdByUrl(path);

    // Redirect the user to the page to display the category
    Context.RewritePath(String.Concat("~/rewritepath.aspx?type=category&id=", id), false);

    // There are no more rules so no need to return
}

Other things to note

Using my somewhat simplistic example, there are a number of other things you could do to improve the code's maintainability and performance:

  • Consider using "StartsWith" instead of contains to make the match more specific (the code we had used "IndexOf() != -1" throughout -but then also had a couple of "IndexOf() == -1" to spice it up). There may be a minor overhead doing this however it makes it a lot easier to understand what you want to achieve.
  • Try not to do anything other than "forward" the request on as quickly as possible i.e. no database calls!
  • Add known exclusions at the start of the code where you know you don't need to rewrite the path i.e. the folders for CSS, images and JavaScript. You could also approach this by wrapping all rules in an inclusion i.e. if(path.StartsWith("category") || path.StartsWith("product"))
  • You should write the rules not only in order of importance/process but while doing so, give consideration to which will be processed most frequently e.g. if you have a rule that's only use by 1 in 1000 calls as the first rule, this will be checked before the relevant rules 999 times in 1000 -so what may be a very small overhead could become a large bottleneck as soon as your site starts to grow in popularity
  • Document each rule clearly i.e. explaining what url formats should trigger it
  • Where rules are mutually exclusive, use "else if" rather than just "if" to make it clear that you don't expect the others to be run if the first is valid.
 

Don't forget to follow me on Twitter.