The Definitive Guide to URL Rewriting in ASP.NET

Posted on 6/30/2006 @ 8:36 PM in #Vanilla .NET by | Feedback | 22139 views

What is URL Rewriting?

URL Rewriting refers to the process where your website receives a URL, and is arbitrarily able to redirect it to some other page ~ without the user knowing. The advantage this gives you, is that you can create friendly looking "pretty" URLs, have one single ASPX masquerade as many, and in general give you a much more flexible architecture.

How to do it in ASP.NET?

The simplest way to do this in ASP.NET is to write an HttpModule that takes this responsibility. For instance, in my upcoming webpart based framework, which I am going to call "SpareJoint", which also powers my website - www.winsmarts.com , uses URL Rewriting via an HTTP Module. SpareJoint which will be offered as open source is intended to be a solution that leverages the ASP.NET 2.0 WebPart framework, and allows you to easily setup and create interactive websites with pretty much any look & feel. Purty neat huh? Now pray to god that I get enough time to finish it up and offer it as open source in the next couple of months :).

Implementing a URL Rewriter module is simply a matter of

a) Create a class that implements IHttpModule

b) In the "Init" of that class, subscribe to the event handler as shown below -

public
void Init(HttpApplication context)
{
    context.BeginRequest +=new EventHandler(context_BeginRequest);
}

c)
In the "context_BeginRequest" method, do your actual URL Rewritin'. In SpareJoint, I read from the database, and map the current request to a new URL as specified in the Database. This is damn easy as shown in the code below.

void
context_BeginRequest(object sender, EventArgs e)
{
  
string urlToChangeTo;
      // Run some custom logic, in my case find urlToChangeTo from DB
      application.Context.RewritePath(urlToChangeTo);

}

d) Now if you run your application, you will note that your site starts URL rewriting. Now it is easy to jump with joy at this time and move on - but that would be a mistake. Don't forget step #e.

e) In the target ASPX, you need to do the reverse URL rewritin'. Why? why? Because if you don't, and "fakypage.aspx" requested, which is actually being served by "realpage.aspx", all postbacks will go to "realpage.aspx" - and thus completely confuse your application logic. not only that If you had a master page which used a ~/images and ~/styles, and "realpage.aspx" lived at "parentdirectory/realpage.aspx", all relative links will be screwed up :-/. So you need to add the following to the PreInit stage in your "mapped to" aspx (in this case realpage.aspx).

protected void Page_PreInit(object sender, EventArgs e)
{
   Context.RewritePath(
VirtualPathUtility.GetDirectory(Request.RawUrl) + VirtualPathUtility.GetFileName(Request.RawUrl));
}

Wait a minute, "PreInit"? WHY WHY? What's wrong with "Init"?

Well - the providers are called in a weird sequence. First Modules are called, (so URL Rewriting happens), then your page's PreInit is called, and then the PersonalizationProvider etc. etc., and then your page's Init. So if the reverse URL Rewriting didn't happen before the PersonalizatonProvider was called, it's LoadPersonalizationBlobs methods would get the incorrect path. YUK !!

So if the user added a WebPart to "fakepage.aspx", the personalization store will store it in "fakepage.aspx", but on subsequent requests, it will attempt to read data for "realpage.aspx" - which as you guessed it right, DOESN'T EXIST (because it was never saved).

If this sounds confusing - don't bother understanding :), just put your reverse rewritin' in PreInit - life will be happier that way.

THATS IT !! Now add it to your Web.config as shown below -

<httpModules>
<
add
  type
="Winsmarts.SpareJoint.Modules.URLRewrite,Winsmarts.SpareJoint.Modules"
name
="Winsmarts.SpareJoint.Modules"/>
</
httpModules>

BINGO !! You're ready all set to go !! w00t!! :)

 

Sound off but keep it civil:

Older comments..


On 11/8/2006 6:21:41 AM leafyoung said ..
unable to use WebPartManager1.Personalization.ToggleScope();


On 11/9/2006 5:46:03 PM Sahil Malik said ..
Leafy - what are you talking about man? :-/


On 11/11/2006 12:08:56 AM leafyoung said ..
@Sahil Malik: the below code just can not work! (my english is limited, sorry!)

public partial class Test : System.Web.UI.Page


{


protected void Page_PreInit(object sender, EventArgs e)


{


Context.RewritePath(VirtualPathUtility.GetDirectory(Request.RawUrl)


+ VirtualPathUtility.GetFileName(Request.RawUrl));


}

protected void Page_Load(object sender, EventArgs e)


{


WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode;


}

protected void LinkButton1_Click(object sender, EventArgs e)


{


// Throw exception here!!!


WebPartManager1.Personalization.ToggleScope();


}


}

Source Error:

Line 39: {


Line 40: // Throw exception here!!!


Line 41: WebPartManager1.Personalization.ToggleScope();


Line 42: }


Line 43: }

Source File: f:\website\WebPartTest\Test.aspx.cs Line: 41

Stack Trace:

[HttpException (0x80004005): The file '/WebPartTest/1.aspx' does not exist.]


System.Web.UI.Util.CheckVirtualFileExists(VirtualPath virtualPath) +3366807


System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile) +109


System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile) +93


System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory(VirtualPath virtualPath, HttpContext context, Boolean allowCrossApp, Boolean noAssert) +111


System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(VirtualPath virtualPath, Type requiredBaseType, HttpContext context, Boolean allowCrossApp, Boolean noAssert) +54


System.Web.UI.PageHandlerFactory.GetHandlerHelper(HttpContext context, String requestType, VirtualPath virtualPath, String physicalPath) +31


System.Web.UI.PageHandlerFactory.System.Web.IHttpHandlerFactory2.GetHandler(HttpContext context, String requestType, VirtualPath virtualPath, String physicalPath) +40


System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig) +139


System.Web.HttpServerUtility.Execute(String path, TextWriter writer, Boolean preserveForm) +411

[HttpException (0x80004005): Error executing child request for /WebPartTest/1.aspx.]


System.Web.HttpServerUtility.Execute(String path, TextWriter writer, Boolean preserveForm) +712


System.Web.HttpServerUtility.Transfer(String path, Boolean preserveForm) +56


System.Web.UI.WebControls.WebParts.WebPartPersonalization.TransferToCurrentPage(Page page) +297


System.Web.UI.WebControls.WebParts.WebPartPersonalization.ToggleScope() +179


Test.LinkButton1_Click(Object sender, EventArgs e) in f:\website\WebPartTest\Test.aspx.cs:41


System.Web.UI.WebControls.LinkButton.OnClick(EventArgs e) +105


System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument) +107


System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +7


System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +11


System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +174


System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5102


On 11/11/2006 1:06:37 AM Sahil Malik said ..
Leafy Young -

I have this working on my desktop. So I suggest you sit tight and wait for me to release the code in a month or so.

SM


On 11/12/2006 12:54:08 AM leafyoung said ..
really? i means i request the "1.aspx" which actually does not exist (mapped to "Test.aspx" using RewritePath), then the exception throws out. i think it is because ToggleScope() will check whether the page "1.aspx" exists and found not.

P.S. i found a solution on codeproject and it works@_@ ->


http://www.codeproject.com/useritems/sharpcms.asp


On 11/12/2006 12:57:59 AM leafyoung said ..
i think another solution is to write a class derived form "VirtualPathProvider", and make the "FileExists" function not to check the physical file exists or not.


On 11/12/2006 11:01:43 PM Sahil Malik said ..
Thanks for sharing these Leafy. I may find these useful in testing my starter kit.


On 2/8/2007 8:00:28 AM RichardPriddy said ..
Hello,

I have followed this and a few other tutorials through and have been having significant problems with URL rewriting when using the WebPartManager.

My page compiles and runs perfectly well until I start trying to edit the layout of my page etc... When in Design View I am unable to drag my webparts around the page and am also unable to minimise the controls. The only error message I get given is "Error WebPartManager is undefined" and "Error WebPartMenu is undefined" JavaScript errors.

Have you any idea where this might be coming from?


On 2/8/2007 9:35:31 AM Richard Priddy said ..
Hello again.

I have been testing my code further and have run across another problem with the URL rewriting. On my page I have a asp logon control and a LoginStatus control. If I am on a page which does not have a physical path (ie one where the URL has been rewritten I get this error:

'/URLRewriteTest/something/Default.aspx?process=something%2fdefault&' is not a valid virtual path.


Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: '/URLRewriteTest/something/Default.aspx?process=something%2fdefault&' is not a valid virtual path.

Source Error:

Line 20: //try


Line 21: //{


Line 22: Context.RewritePath(VirtualPathUtility.GetDirectory(Request.RawUrl) + VirtualPathUtility.GetFileName(Request.RawUrl));


Line 23: //}


Line 24: //catch { }

Source File: c:\Inetpub\wwwroot\URLRewriteTest\Default.aspx.cs Line: 22

Stack Trace:

[HttpException (0x80004005): '/URLRewriteTest/something/Default.aspx?process=something%2fdefault&' is not a valid virtual path.]


System.Web.VirtualPath.Create(String virtualPath, VirtualPathOptions options) +3536335


System.Web.VirtualPathUtility.GetDirectory(String virtualPath) +10


_Default.Page_PreInit(Object sender, EventArgs e) in c:\Inetpub\wwwroot\URLRewriteTest\Default.aspx.cs:22


System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15


System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +34


System.Web.UI.Page.OnPreInit(EventArgs e) +2012188


System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +521

Any clues?


On 10/23/2007 2:03:18 PM David Martin said ..
Hi Mr.Malik.


Thanks for the article, but....


i actually got the very same error.


is there a way you could show a bit more code. since you said yours worked, i keep trying but i can get it to work.


surely i am missing something, but i dont know what it is.


anyways, it could be great you could share a bit more on this.


mainly cos i am going nuts.


On 7/8/2008 7:23:42 AM The_Assimilator said ..
@Richard Priddy: you are getting that error because ASP.NET does not support funny characters (like ?, =, :) when performing URL rewriting.

You should "clean" the URLs before you try to redirect to them, for example:

protected void Page_PreInit(object sender, EventArgs e)


{


string cleanedUrl = CleanRawUrl(Request.RawUrl);


Context.RewritePath(VirtualPathUtility.GetDirectory(cleanedUrl) + VirtualPathUtility.GetFileName(cleanedUrl));


}

private string CleanRawUrl()string dirtyUrl)


{


return dirtyUrl.Replace("?", string.Empty).Replace("=", string.Empty).Replace(":", string.Empty);


}


On 7/17/2008 3:06:53 AM Manir said ..
Hi Mr. Malik,

I am implementing url rewrite. I have a url (simple html anchor) on the page (default.aspx) like cat_5_food_dining.html - which is fake and file does not exists. I want to catch this url on Application_BeginRequest event and rewrite the URL like categorydetails.aspx?catId=5. I debug the app, when I click on the URL, it does not goto Application_BeginRequest, and I got File Not found Error. On the Default.aspx page if I hot Refresh - it goes to BeginRequest method. Please let me know what I am doing wrong.

Thanks.


Manir


On 5/4/2012 5:32:31 PM Matt Mason said ..
Worked like a champ. Thanks for the virtual directory rewrite piece...