Thomas’ Developer Blog

August 20, 2008

URL Rewriting

URL Rewriting: The easy & complete way

 

URL Rewriting can be a pain at times. The truth is, it is a pain! There are tons of ways of doing it, but it gets very confusing, very fast trying to figure out which is best. As a result you try fixing something and it gets blown into pieces.

 

To help everyone once and for all I’ve developed a very EASY to understand guide to URL rewriting. I’m not going to go in depth on the technical information since unless you have an intermediate to advance understanding of .Net, you’ll just be lost anyway, or rereading this 20 times until you get it.

 

To begin URL Rewriting I’m going to use the easiest method known:

 

Context.RewritePath(“~/pies/index.aspx?q=123”, rebaseClientPath:=”false”)

 

Now this code must be placed within global.asax which should look like:

 

Sub Application_BeginRequest()

     Context.RewritePath(“~/pies/index.aspx?q=123”, rebaseClientPath:=”false”)

End Sub

 

Application_beginRequest() is a sub procedure used for beginning the request from the browser into .Net to play with. I’m not going into detail on this, but there are a lot of great articles out there about global.asax and I suggest you read it later on…

 

That’s it! That’s the only code you need! But wait! It can’t be that simple? Well… you’re right, because while that may rewrite the URL, it cause a lot of other problems. Let me begin to explain what this code does.

 

First off Context.RewritePath simply redirects the current URL to a different sever side path. So if you have the URL: http://site/pies/apple-pie/123.aspx, the server will think that URL is actually the above rewritten URL. Thus everything is fine! One problems, what about if you use APP_Themes and other functions?! This can be a problem since now my CSS code is all gone!

 

This is a simple problem to fix, which is what rebaseClientPath:=”false” does RebaseClientPath simply prevents .Net from being like, well typically app_themes is: ../../app_themes, but you rewrote the URL so let’s make the path to ../app_themes.

 

Obviously if you think about it, this won’t work, because our browser thinks we are at /pies/apple-pie/123.asp and not /pies/index.asp?q=123

 

To prevent .Net from trying to “fix this” we simply disable it by setting RebaseClientPath to false. Works like a charm!

 

Now then there is one other issue we need to fix, that pesky and extremely annoying form tag! I can’t stand how .Net tries to use the server side path instead of the client side path! Not only does it allow users to see the full URL on post back, such as for /pie/ turning into /pie/index.aspx but it also prevents the code from working on a URL rewrite! So now we have to go in and fix this…

 

How? This is where it gets a bit tricky! Its call custom server controls. If you don’t know what they are, you should really look into them if you’re serious about .Net.  To get the general idea custom server controls allow you to create your own tags, such as <pie:Eat runat=”server” />

 

I will not dare begin to try explaining how in depth custom controls can get.  So I’m going to instead give you a reference if you need to know how they work: http://msdn.microsoft.com/en-us/library/ms972970.aspx

 

I won’t go any further in detail then quickly pasting some code and showing you how it works.

 

In your app_code folder add the following under a new file called piecontrols.vb

 

Imports System
Imports System.Web
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Imports System.Security.Permissions

 

Namespace Pie.CustomControls
    Public Class Form
        Inherits System.Web.UI.HtmlControls.HtmlForm 

        Protected Overrides Sub RenderAttributes(ByVal writer As HtmlTextWriter)
            writer.WriteAttribute(“name”, Me.Name)
            MyBase.Attributes.Remove(“name”)
            writer.WriteAttribute(“method”, Me.Method)
            MyBase.Attributes.Remove(“method”)
            writer.WriteAttribute(“action”, “/pie/apple-pie/123.aspx”)) ‘<— This is the important line to pay attention to!

            Me.Attributes.Render(writer)

 

            If MyBase.ID IsNot Nothing Then
                writer.WriteAttribute(“id”, MyBase.ClientID)
           End If 

        End Sub
    End Class
End NameSpace

 

Ok now that you have that code, go ahead and save it and let me explain it briefly. What this code does is pretty simple in nature. You create a namespace to hold your custom controls. That namespace is the <pie: part of the tag. Generally just a holder. After that each tagname after that is a class such as <pie:”FORM” the form part is your class. Make sense? Moving on either way, lol.

 

After you have the class setup we go ahead and just import the current form class information:

    Inherits System.Web.UI.HtmlControls.HtmlForm

That line is very important! That makes the class essentially a form tag! Now we just gota go in and “hack” up .net to force the form tag to use the action we want.

 

We need to override the RenderAttributes routine to do this. All this does is instead of adding the default attributes we go in and play around and fix it up. In this case we change the default form action to our new one that we want, “/pie/apple-pie/123.aspx”

All that’s left is to register the tag prefix and switch the current <form runat=”server”> with <pie:form runat=”server”>

 

Anyway, this should explain the 3 key things you must do for URL rewriting:

 

1)      Setup Global.asax to rewrite the URL

2)      Make sure RebaseClientPath is set to false

3)      Rewrite the action element of the tag.

 

This will likely be a bit confusing to everyone at first, but reread it a few times and you’ll get it.

 

FULL CODE SAMPLE:

–globa.asax–
<% @ Language=”VB” %>
<% @ import Namespace=”System.IO” %>

<
SCRIPT LANGUAGE=”VB” RUNAT=”Server”>
Sub Application_BeginRequest()
  httpcontext.current.items(“URLSource”) = Request.ServerVariables(“URL”) ‘Gets current URL and stores it before it is rewritten
  httpcontext.current.items(“QueryStringSource”) = Request.ServerVariables(“QUERY_STRING”) ‘Gets current qstring and stores it before it is rewritten

If (left(Request.ServerVariables(“URL”),len(“/pie/”)) = “/pie/”) Then ‘Checks to see if URL starts with “/pie/”
  Dim myURL as string = Request.ServerVariables(“URL”) ‘Original URL
  Dim ScriptFile as String = right(myURL,len(myURL)-instrRev(myURL,”/”)) ‘Name of script “123.aspx”

  If instrRev(myURL,”.”) <> 0 Then ‘Makes sure the URL is not open i.e. /pie/
    If IsNumeric(left(ScriptFile,instrRev(ScriptFile,”.”)-1)) Then ‘Makes sure the “123” part of “123.aspx” is numeric.
      ‘The following URL rewrites the URL from /pie/apple-pie/123.aspx to /pie/index.aspx?q=123 and prevents .Net from adjusting App_Themes
      Context.RewritePath(“~/pie/index.aspx?q=” & left(ScriptFile,instrRev(ScriptFile,”.”)-1),rebaseClientPath:=”false”)
    End If
  End If
End If
End Sub

</
script>

–App_Code/ServerControls.vb–


Imports System
Imports System.Web
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Imports System.Security.Permissions

Namespace OtakuElite.ServerControls

Public Class Form
  Inherits System.Web.UI.HtmlControls.HtmlForm 'Sets class to form tag type object

  Protected Overrides Sub RenderAttributes(ByVal writer As HtmlTextWriter) 'Overrides attributes for form tag
    writer.WriteAttribute("name", Me.Name)
    MyBase.Attributes.Remove("name")
    writer.WriteAttribute("method", Me.Method)
    MyBase.Attributes.Remove("method")
 
    'OPTIONAL--HIDES index.aspx from postback!
    'In this example I have excluded this! You would use this if you plan on using custom handler mappings such as .EXT

    'If InStrRev(HttpContext.Current.Items("URLSource"), ".aspx") <> 0 Then
    '  HttpContext.Current.Items("URLSource") = Left(HttpContext.Current.Items("URLSource"), InStrRev(HttpContext.Current.Items("URLSource"), "/"))
    'End If

    'In the following code the action URL is determined using the original URL information gathered from before. 
    'It adds a qstring if it exists
    If HttpContext.Current.Items("QueryStringSource") <> "" Then
      writer.WriteAttribute("action", "" & HttpContext.Current.Items("URLSource") & "?" & HttpContext.Current.Items("QueryStringSource"))
    Else
      writer.WriteAttribute("action", "" & HttpContext.Current.Items("URLSource"))
    End If

    Me.Attributes.Render(writer)

    If MyBase.ID IsNot Nothing Then
      writer.WriteAttribute("id", MyBase.ClientID)
    End If
  End Sub
End Class
End Namespace

--/pie/index.aspx--

<% @ Page Language="VB" AutoEventWireup="false" EnableSessionState="true" EnableViewState="True" %>
<% @ Register TagPrefix="Pie" Namespace="Pie.ServerControls"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head runat="server" >
</head>
<body>
  <Pie:form id="form1" runat="server"> 
    'My Code goes here!
  </Pie:form> 
</body>


 

 

 

 

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: