ASP.NET 2.0: URL Mapping with RegEx Support

12. November 2005

The one big limitation of the URL Mapping functionality built in to ASP.NET 2.0 is that it doesn't support regular expressions. I ported my v1.1 URL Mapping implementation over to v2.0 and added support for regular expressions. It really wasn't too difficult; I only had to modify two lines of code to port it over to ASP.NET 2.0. Then I had to modify 4 lines of code to add RegEx support. My implementation works just like the ASP.NET 2.0 URL Mapping functionality with the addition of RegEx.

My code allows to create Url Mappings similar to the following:
~/Chris.aspx to ~/Default.aspx?p=chris
~/Show154.aspx to ~/Default.aspx?p=154

Some Performance tips for using this code:

1.       Use as few Url Mapping definitions as possible since it parses them from the first to the last and stops when it finds a match. If a page requested doesn't match any of the definitions it will go through all of them before moving on to complete the page request.

2.       Put the most frequently used Url Mappings first in the list so that they the ones that are parsed first.

3.       The first Url Mapping in placed in my code (<add url="~/(.*)default\.aspx" mappedUrl="~/$1default.aspx" />) is a little trick that allows all requests for the Default.aspx page in the root or any sub-folder of the application to be completed with out having to parse through the entire list of Url Mappings.

Regular expression support just seems logical in the v2.0 URL Mapping functionality, but I don't know why Microsoft didn't add it. I guess they wanted me to have something to do.  :)

Enjoy!

Code for this implementation listed below:

/App_Code/RegExUrlMappingBaseModule.vb

Imports Microsoft.VisualBasic

Imports System.Web

Namespace RegExUrlMapping_HTTPModule

Public Class RegExUrlMappingBaseModule

Implements System.Web.IHttpModule

Sub Init(ByVal app As HttpApplication) Implements IHttpModule.Init

AddHandler app.AuthorizeRequest, AddressOf Me.BaseModuleRewriter_AuthorizeRequest

End Sub

Sub Dispose() Implements System.Web.IHttpModule.Dispose

End Sub

Sub BaseModuleRewriter_AuthorizeRequest(ByVal sender As Object, ByVal e As EventArgs)

Dim app As HttpApplication = CType(sender, HttpApplication)

Rewrite(app.Request.Path, app)

End Sub

Overridable Sub Rewrite(ByVal requestedPath As String, ByVal app As HttpApplication)

End Sub

End Class

End Namespace

/App_Code/RegExUrlMappingConfigHandler.vb

Imports Microsoft.VisualBasic

Imports System.Configuration

Imports System.Xml

Namespace RegExUrlMapping_HTTPModule

Public Class RegExUrlMappingConfigHandler

Implements IConfigurationSectionHandler

Dim _Section As XmlNode

Public Function Create(ByVal parent As Object, ByVal configContext As Object, ByVal section As System.Xml.XmlNode) As Object Implements System.Configuration.IConfigurationSectionHandler.Create

_Section = section

Return Me

End Function

''' Get whether url mapping is enabled in the app.config

Friend Function Enabled() As Boolean

If _Section.Attributes("enabled").Value.ToLower = "true" Then

Return True

Else

Return False

End If

End Function

''' Get the matching "mapped Url" from the web.config file if there is one.

Friend Function MappedUrl(ByVal url As String) As String

Dim x As XmlNode

Dim oReg As Regex

For Each x In _Section.ChildNodes

oReg = New Regex(x.Attributes("url").Value.ToLower)

If oReg.Match(url).Success Then

Return oReg.Replace(url, x.Attributes("mappedUrl").Value.ToLower)

End If

Next

Return ""

End Function

End Class

End Namespace

/App_Code/RegExUrlMappingModule.vb

Imports Microsoft.VisualBasic

Imports System.Web

Imports System.Configuration

Namespace RegExUrlMapping_HTTPModule

Public Class RegExUrlMappingModule

Inherits RegExUrlMappingBaseModule

Overrides Sub Rewrite(ByVal requestedPath As String, ByVal app As HttpApplication)

''Implement functionality here that mimics the 'URL Mapping' features of ASP.NET 2.0

Dim config As RegExUrlMappingConfigHandler = CType(ConfigurationManager.GetSection("system.web/RegExUrlMapping"), RegExUrlMappingConfigHandler)

Dim pathOld As String, pathNew As String = ""

If config.Enabled Then

pathOld = app.Request.RawUrl

''Get the request page without the querystring parameters

Dim requestedPage As String = app.Request.RawUrl.ToLower

If requestedPage.IndexOf("?") > -1 Then

requestedPage = requestedPage.Substring(0, requestedPage.IndexOf("?"))

End If

''Format the requested page (url) to have a ~ instead of the virtual path of the app

Dim appVirtualPath As String = app.Request.ApplicationPath

If requestedPage.Length >= appVirtualPath.Length Then

If requestedPage.Substring(0, appVirtualPath.Length).ToLower = appVirtualPath.ToLower Then

requestedPage = requestedPage.Substring(appVirtualPath.Length)

If requestedPage.Substring(0, 1) = "/" Then

requestedPage = "~" & requestedPage

Else

requestedPage = "~/" & requestedPage

End If

End If

End If

''Get the new path to rewrite the url to if it meets one

''of the defined virtual urls.

pathNew = config.MappedUrl(requestedPage)

''If the requested url matches one of the virtual one

''the lets go and rewrite it.

If pathNew.Length > 0 Then

If pathNew.IndexOf("?") > -1 Then

''The matched page has a querystring defined

If pathOld.IndexOf("?") > -1 Then

pathNew += "&" & Right(pathOld, pathOld.Length - pathOld.IndexOf("?") - 1)

End If

Else

''The matched page doesn't have a querystring defined

If pathOld.IndexOf("?") > -1 Then

pathNew += Right(pathOld, pathOld.Length - pathOld.IndexOf("?"))

End If

End If

''Rewrite to the new url

HttpContext.Current.RewritePath(pathNew, false)

End If

End If

End Sub

End Class

End Namespace

/Web.Config

<configuration>

<!-- Declare the custom 'RegExUrlMapping' section and handler -->

<configSections>

<sectionGroup name="system.web">

<section name="RegExUrlMapping" type="RegExUrlMapping_HTTPModule.RegExUrlMappingConfigHandler"/>

</sectionGroup>

</configSections>

<system.web>

<!-- Tell ASP.NET to use the RegEx URL Mapping HTTP Module -->

<httpModules>

<add type="RegExUrlMapping_HTTPModule.RegExUrlMappingModule" name="RegExUrlMappingModule"/>

</httpModules>

<!-- The RegEx URL Mapping parser goes through these in sequential order. -->

<RegExUrlMapping enabled="true">

<add url="~/(.*)default\.aspx" mappedUrl="~/$1default.aspx" />

<add url="~/Chris.aspx" mappedUrl="~/Default.aspx?p=chris"/>

<add url="~/show(.*)\.aspx" mappedUrl="~/Default.aspx?p=$1&amp;section=3"/>

</RegExUrlMapping>

</system.web>

</configuration>

 

ASP.NET 1.1 Url Mapping code can be found here: http://pietschsoft.com/Blog/archive/2005/07/04/717.aspx

Update 11/14/2005 - Scoth Guthrie has posted about why they didn't add Regular Expression support to the ASP.NET 2.0 URL Mapping on his blog. He has also posted a link to this blog entry in that blog post. It's pretty cool that my blog has gotten noticed by him.

http://weblogs.asp.net/scottgu/archive/2005/11/14/430493.aspx

 

Update 9/10/2007 - I fixed a bug in the RegExUrlMapping code that was preventing ASP.NET Themes from correctly loading.

In the RegExUrlMappingModule class change the following line of the Rewrite method:

   HttpContext.Current.RewritePath(pathNew)

To be the following instead:

   HttpContext.Current.RewritePath(pathNew, false)

I already made this change to the code above so anyone copying it from now on will get this fix.

 

Update 3/23/2009 -I just found that this post is mentioned in the "ASP.NET 2.0 MVP Hacks and Tips" book that was published in May 2006 on Page 357. I find that one of my blog posts being mentioned in a book like this to be really interesting. Thanks for the props guys!!

 

General



Comments

11/15/2005 1:12:00 PM #
Have you thought about caching the positive matches?
12/1/2005 12:07:00 AM #
Hi,

Good job! This works fine but I have a question...

If I use a masterpage and a theme that automaticaly insert a css file, if I try to rewrite a url like "/english/mypage/p123l1.aspx" to "/default.aspx?idpage=123&idlanguage=1" my css don't work, because it takes the path of the rewrited url.

My rule is this one:

<add url="~/.*p(.*)l(.*)\.aspx" mappedUrl="~/default.aspx?idpage=$1&amp;idlanguage=$2" />

So, how can I make this working?

Maybe this is impossible because the theme automaticaly include the css with a relative path, so that's why http://Microsoft.com" target="_blank">Microsoft didn't include regular expression support?

Thanks in advance for your answer.
12/3/2005 11:38:00 AM #
Great Work with this! Just what I searched for.

Thanks alot.
12/5/2005 1:37:00 PM #
Also very pleased with how easy it was to implement this code...

One question though... how/where do i capture the QueryString values though?

I am mapping

http://mysite/books/list.aspx

to

http://mysite/list.aspx?Type=books

but don't know where/how to get "books" from....  the page trace shows REQUEST_QUERYSTRING empty inside the header variables
12/5/2005 1:41:00 PM #
nvm...

the trace is showing the "pre" mapped header information, actually pulling the values in the code brings "post" mapped
12/31/2005 12:20:00 AM #
I converted the above code to C# for any that prefer's to use C# over VB.NET

Compilied it and Tested it on my current project and everything seems to work without a problem.

You can download the converted code here.

http://www.nfscars.net/UrlRewriting-2.0-C.zip" rel="nofollow">http://www.nfscars.net/UrlRewriting-2.0-C.zip">http://www.nfscars.net/UrlRewriting-2.0-C.zip" rel="nofollow">http://www.nfscars.net/UrlRewriting-2.0-C.zip

Have Fun, Mike
antares
antares
12/31/2005 7:37:00 PM #
there will be error while the page post back...
1/3/2006 10:58:00 AM #
I know this isn't a 100% solution. I haven't tested out some reported issues related to using this with Masterpages and Themes. I have thought about caching the mapping results, but I this post was about how to do Regex URL Mapping. I basically wanted to keep it simple.

If you are interested in fixing the issue of the pages posting back to the actually page executed instead of the mapped url; look at the "Finally - the BIG RewritePath issue" section of this page: http://edsid.com/blog/articles/160.aspx" rel="nofollow">http://edsid.com/blog/articles/160.aspx">http://edsid.com/blog/articles/160.aspx" rel="nofollow">http://edsid.com/blog/articles/160.aspx
1/9/2006 8:36:00 AM #
What happens if there is no match? All I see is

[NullReferenceException: Object reference not set to an instance of an object.]
   RegExUrlMapping_HTTPModule.RegExUrlMappingConfigHandler.MappedUrl(String url) +129
   RegExUrlMapping_HTTPModule.RegExUrlMappingModule.Rewrite(String requestedPath, HttpApplication app) +562
   RegExUrlMapping_HTTPModule.RegExUrlMappingBaseModule.BaseModuleRewriter_AuthorizeRequest(Object sender, EventArgs e) +61
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +138
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +65

when nothing matches. What should the "default" string be?
Brian Bauer
Brian Bauer
1/10/2006 4:29:00 PM #
First of all... thanks for the code.  This has solved a problem we're going to have on a web site I'm developing where different organization names will be part of the URL.  Those organization names need to be stripped off of the URL and saved but are not part of the underlying structure of the web site.  

For Glen who asked how to make this work with master pages and themes... we're doing the same thing and I experienced the same sort of problem where the rewritten URL gets you to the correct page, but the underlying theme elements hang on to the "extra" portions of the URL and therefore don't resolve correctly.  What I did was change the final line in the RegExUrlMappingModule so that instead of doing a RewritePath I do a Response.Redirect to the new path.  This incurs some overhead, but all my theme references then resolve appropriately.  Hope this helps.
1/18/2006 1:22:00 PM #
Hi,

Thomas Bandt and i developed a Version which handles Themes, Postbacks am more.

Free free to download and use it. It's free.

http://www.urlrewriting.net

We think we solved all problems.
Justin Wignall
Justin Wignall
1/19/2006 12:25:00 AM #
After using your code for a very short while (only stated .net 2.0 recently) I want to thank you for it but point out to everyone the above link to http://www.urlrewriting.net/ It rocks!

Only been using for 5 minutes and already just want to rush over to Germany and hug those guys
1/21/2006 8:52:00 AM #
I've just tried urlrewriting.net - yeah it's a good replacement to virtually everything.

The only thing I can do is my datestamp on the physical file, ie:

Dim dateString As String
            Dim objFile As System.IO.FileInfo
            objFile = New System.IO.FileInfo(Request.PhysicalPath)
            dateString = objFile.LastWriteTime.ToLongDateString()

Doesn't return the value of the physical file - I can't seem to work out how to refer to it now..
3/2/2006 8:55:00 AM #
Check out my URL Rewriting code via Global.asax. There are also functions on my page written in C# for determining the rewritten URL during postbacks.

www.willasrari.com/.../00043.aspx

www.willasrari.com/.../00015.aspx
3/20/2006 4:28:00 PM #
the code was easy to implement, thanks
china
china
3/23/2006 8:44:00 PM #
I fall accross a question,when I debug at zhe development environment ,zhe urlmapping can be run,but when I publish at zhe IIS,It has no effect.Why?
could you help me? thank you!
4/24/2006 4:58:00 AM #
I have had problems in using the various HttpHandler/ Http Module URL Rewritting solutions floating around the Internet when the destination page is in a sub-folder and is a page derived from a ASP.NET V2 Master Page.  The master page and other user controls can not be found.

Have you seen such a problem ?... and can you suggest an effective workaround ?
5/19/2006 10:34:00 PM #
Its cool.. But if it is in C#, it will be more helpful
Hosam
Hosam
6/16/2006 2:47:00 AM #
good work, i have a question how can i support subdomains rewriting example
http://subdomain.domain.com to ~default.aspx?subDomain = subdomain
Hosam
Hosam
6/16/2006 2:48:00 AM #
good work, i have a question how can i support subdomains rewriting example
http://subdomain.domain.com to ~default.aspx?subDomain = subdomain
7/6/2006 4:43:00 AM #
hi
does subdomain mapping to one of your application subdirectory works with URL mapping

thanks
Helen C
Helen C
7/7/2006 12:40:00 PM #
Ive used the code supplied above and although Im not getting any error messages, its not making the slightest bit of difference. Can anybody suggest a reason why? Im using master pages - does that make a difference? And if so, what do I do to implement it with Master pages. Im nearly pulling my hair out at this stahe, any help would be appreciated
Helen C
Helen C
7/7/2006 1:35:00 PM #
Hi, Ive used the above code and although it doesnt give any errors, it makes no difference to the url displayed. Can anyone suggest a reason why?
Kyle Shea
Kyle Shea
10/23/2006 5:24:00 PM #
Any idea how to modify the code to support a "dynamic" list of replacement URL's? Namely - I'd like to take a URL, i.e. "Products/FooProducts.aspx", and do a run-time looking in the database to determine the unique identifier of the "FooProducts" product category. Then I can dynmaically replace the URL with "Products.aspx?category={query result}. Would this make sense to do?  I have a lengthy list of product categories that change constantly so doing rewriting dynamically is a must..  What do you guys think?
Ken
Ken
11/10/2006 10:01:00 PM #
Kyle
if you look to the code you find this function...

Change it to get "the Dynamically created url from whatever source - for example your Database of Products " I am doing exactly this - as I have over 10,000 products to list.
''' Get the matching "mapped Url" from the web.config file if there is one.

Friend Function MappedUrl(ByVal url As String) As String

Dim x As XmlNode

Dim oReg As Regex

For Each x In _Section.ChildNodes

oReg = New Regex(x.Attributes("url").Value.ToLower)

If oReg.Match(url).Success Then

Return oReg.Replace(url, x.Attributes("mappedUrl").Value.ToLower)

End If

Next

Return ""

End Function

Rico
Rico
1/1/2007 3:19:00 PM #
Ken, is there any chance that you could post your code up?
PQP
PQP
1/2/2007 6:14:00 PM #
I use Url Mapping module in WebPart Website, so I want to personalization pages but I can't.
Ex I map: ~/(.*)/default.aspx -> default.aspx?ChannelID=$1. When I query 1/default.aspx then make personalization and then query 2/default.aspx, it's same previous page.
How can I do it now?
1/11/2007 3:38:00 AM #
Yeah, it sounds logical that it might work that way, but it doesn’t. I actually tried the same thing myself. The issue is the WebParts see the same page being executed no matter what URL is being mapped to it (and it’s logical that it works this way), however to get it to work the way you want you have to write your own WebPartManager for that page (I believe). I started working on this a few months ago, but I haven’t had time to finish figuring it out. Let me know if you make any progress.
8/11/2008 8:10:56 PM #
Pingback from webhostchat.co.uk

Write permissions on root folder - good or bad? - Web Host Chat
3/25/2009 3:04:11 PM #
Pingback from sharpertutorials.com

Clean URL Structure in ASP.Net - C# Tutorials
Comments are closed