this article describes a complete solution for url rewriting in asp

Upload: smartigal

Post on 29-May-2018

224 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    1/15

    This article describes a complete solution for URL rewriting in ASP.NET 2.0. The solution uses regularexpressions to specify rewriting rules and resolves possible difficulties with postback from pages accessed viavirtual URLs.

    Why use URL rewriting?

    The two main reasons to incorporate URL rewriting capabilities into your ASP.NET applications are usabilityand maintainability.

    Usability

    It is well-known that users of web applications prefer short, neat URLs to monstrous addresses packed withdifficult to comprehend query string parameters. From time to time, being able to remember and type in aconcise URL is less time-consuming than adding the page to a browser's favorites only to access later. Again,when access to a browser's favorites is unavailable, it can be more convenient to type in the URL of a page onthe browser address bar, without having to remember a few keywords and type them into a search engine inorder to find the page.

    Compare the following two addresses and decide which one you like more:

    (1) http://www.somebloghost.com/Blogs/Posts.aspx?Year=2006&Month=12&Day=10(2) http://www. somebloghost.com/Blogs/2006/12/10/

    The first URL contains query string parameters to encode the date for which some blog engines should showavailable postings. The second URL contains this information in the address, giving the user a clear idea ofwhat he or she is going to see. The second address also allows the user to hack the URL to see all postingsavailable in December, simply by removing the text encoding the day '10':http://www.somehost.com/Blogs/2006/12/.

    Maintainability

    In large web applications, it is common for developers to move pages from one directory to another. Let ussuppose that support information was initially available at http://www.somebloghost.com/Info/Copyright.aspxand http://www.somebloghost.com/Support/Contacts.aspx, but at a later date the developers moved theCopyright.aspx and Contacts.aspx pages to a new folder called Help. Users who have bookmarked theold URLs need to be redirected to the new location. This issue can be resolved by adding simple dummy pagescontaining calls to Response.Redirect(new location). However, what if there are hundreds of moved pagesall over the application directory? The web project will soon contain too many useless pages that have the solepurpose of redirecting users to a new location.

    Enter URL rewriting, which allows a developer to move pages between virtual directories just by editing aconfiguration file. In this way, the developer can separate the physical structure of the website from the logicalstructure available to users via URLs.

    Native URL mapping in ASP.NET 2.0

    ASP.NET 2.0 provides an out-of-the-box solution for mapping static URLs within a web application. It ispossible to map old URLs to new ones in web.config without writing any lines of code. To use URL mapping,

    just create a new urlMappings section within the system.web section of yourweb.config file and add therequired mappings (the path ~/ points to the root directory of the web application):

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    2/15

    Thus, if a user types http://www.somebloghost.com/Support/Contacts.aspx, he can then see the page located

    at http://www.somebloghost.com/Help/Contacts.aspx, without even knowing the page had been moved.

    This solution is fine if you have only two pages that have been moved to other locations, but it is completelyunsuitable where there are dozens of re-located pages, or where a really neat URL needs to be created.

    Another possible disadvantage of the native URL mapping technique is that if the page Contacts.aspxcontains elements initiating postback to the server (which is most probable), then the user will be surprised thatthe URL http://www.somebloghost.com/Support/Contacts.aspxchanges tohttp://www.somebloghost.com/Help/Contacts.aspx. This happens because the ASP.NET engine fills the actionattribute of the form HTML tag with the actual path to a page. So the form renders like this:

    Thus, URL mapping available in ASP.NET 2.0 is almost always useless. It would be much better to be able tospecify a set of similar URLs in one mapping rule. The best solution is to use Regular Expressions (foroverview see Wikipedia and for implementation in .NET see MSDN), but an ASP.NET 2.0 mapping does notsupport regular expressions. We therefore need to develop a different solution to built-in URL mapping.

    The URL rewriting module

    The best way to implement a URL rewriting solution is to create reusable and easily configurable modules, sothe obvious decision is to create an HTTP Module (for details on HTTP Modules see MSDN Magazine) and

    implement it as an individual assembly. To make this assembly as easy to use as possible, we need toimplement the ability to configure the rewrite engine and specify rules in aweb.config file.

    During the development process we need to be able to turn the rewriting module on or off (for example if youhave a bug that is difficult to catch, and which may have been caused by incorrect rewriting rules). Thereshould, therefore, be an option in the rewriting module configuration section inweb.config to turn the moduleon or off. So, a sample configuration section withinweb.config can go like this:

    true

    This means that all requests that run like: http://localhost/Web/2006/12/10/should be internally redirected to thepage Posts.aspx with query string parameters.

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    3/15

    Please note thatweb.config is a well-formed XML file, and it is prohibited to use the symbol & in attributevalue strings. In this case, you should use & instead in the destination attribute of the rule element.

    To use the rewriteModule section in theweb.config file, you need to register a section name and asection handler for this section. To do this, add a configSections section toweb.config:

    This means you may use the following section below the configSections section:

    true

    Another thing we have to bear in mind during the development of the rewriting module is that it should bepossible to use 'virtual' URLs with query string parameters, as shown in the following:http://www.somebloghost.com/2006/12/10/?Sort=Desc&SortBy=Date. Thus we have to develop a solution thatcan detect parameters passed via query string and also via virtual URL in our web application.

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    4/15

    So, lets start by building a new Class Library. We need to add a reference to the System.Web assembly, aswe want this library to be used within an ASP.NET application and we also want to implement some web-specific functions at the same time. If we want our module to be able to readweb.config, we need to add areference to the System.Configuration assembly.

    Handling the configuration section

    To be able to read the configuration settings specified inweb.config, we have to create a class thatimplements the IConfigurationSectionHandler interface (see MSDN for details). This can be seenbelow:

    using System;using System.Collections.Generic;using System.Text;using System.Configuration;using System.Web;using System.Xml;

    namespace RewriteModule{

    publicclassRewriteModuleSectionHandler : IConfigurationSectionHandler{

    privateXmlNode _XmlSection;privatestring _RewriteBase;privatebool _RewriteOn;

    publicXmlNode XmlSection{

    get { return _XmlSection; }}

    publicstring RewriteBase{

    get { return _RewriteBase; }}

    publicbool RewriteOn{

    get { return _RewriteOn; }}publicobject Create(object parent,

    object configContext,System.Xml.XmlNode section)

    {

    // set base path for rewriting module to// application root_RewriteBase = HttpContext.Current.Request.ApplicationPath + "/";

    // process configuration section// from web.configtry{

    _XmlSection = section;

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    5/15

    _RewriteOn = Convert.ToBoolean(section.SelectSingleNode("rewriteOn").InnerText);

    }catch (Exception ex){

    throw (newException("Error while processing RewriteModuleconfiguration section.", ex));

    }returnthis;

    }}

    }The Class RewriteModuleSectionHandler will be initialized by calling the Create method with therewriteModule section ofweb.config passed as XmlNode. The SelectSingleNode method of theXmlNode class is used to return values for module settings.

    Using parameters from rewritten URL

    When handling virtual URLS such as http://www. somebloghost.com/Blogs/gaidar/?Sort=Asc(that is, a virtualURL with query string parameters), it is important that you clearly distinguish parameters that were passed viaa query string from parameters that were passed as virtual directories. Using the rewriting rules specifiedbelow:

    ,

    you can use the following URL:

    http://www. somebloghost.com/gaidar/?Folder=Blogs

    and the result will be the same as if you used this URL:

    http://www. somebloghost.com/Blogs/gaidar/

    To resolve this issue, we have to create some kind of wrapper for 'virtual path parameters'. This could be acollection with a static method to access the current parameters set:

    using System;using System.Collections.Generic;using System.Text;using System.Collections.Specialized;using System.Web;

    namespace RewriteModule{

    publicclassRewriteContext{

    // returns actual RewriteContext instance for// current requestpublicstaticRewriteContext Current{

    get{

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    6/15

    // Look for RewriteContext instance in// current HttpContext. If there is no RewriteContextInfo// item then this means that rewrite module is turned offif(HttpContext.Current.Items.Contains("RewriteContextInfo"))

    return (RewriteContext)HttpContext.Current.Items["RewriteContextInfo"];

    elsereturnnewRewriteContext();

    }}

    public RewriteContext(){

    _Params = newNameValueCollection();_InitialUrl = String.Empty;

    }

    public RewriteContext(NameValueCollection param, string url){

    _InitialUrl = url;

    _Params = newNameValueCollection(param);

    }

    privateNameValueCollection _Params;

    publicNameValueCollection Params{

    get { return _Params; }set { _Params = value; }

    }

    privatestring _InitialUrl;

    publicstring InitialUrl{

    get { return _InitialUrl; }set { _InitialUrl = value; }

    }}

    }

    You can see from the above that it is possible to access 'virtual path parameters' via theRewriteContext.Current collection and be sure that those parameters were specified in the URL as virtualdirectories or pages names, and notas query string parameters.

    Rewriting URLsNow let's try some rewriting. First, we need to read rewriting rules from theweb.config file. Secondly, weneed to check the actual URL against the rules and, if necessary, do some rewriting so that the appropriatepage is executed.

    We create an HttpModule:

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    7/15

    classRewriteModule : IHttpModule{

    publicvoid Dispose() { }publicvoid Init(HttpApplication context){}

    }

    When adding the RewriteModule_BeginRequest method that will process the rules against the given URL,we need to check if the given URL has query string parameters and callHttpContext.Current.RewritePath to give control over to the appropriate ASP.NET page.

    using System;using System.Collections.Generic;using System.Text;using System.Web;using System.Configuration;using System.Xml;using System.Text.RegularExpressions;using System.Web.UI;

    using System.IO;using System.Collections.Specialized;

    namespace RewriteModule{

    classRewriteModule : IHttpModule{

    publicvoid Dispose() { }

    publicvoid Init(HttpApplication context){

    // it is necessary to

    context.BeginRequest += newEventHandler(RewriteModule_BeginRequest);}

    void RewriteModule_BeginRequest(object sender, EventArgs e){

    RewriteModuleSectionHandler cfg =(RewriteModuleSectionHandler)

    ConfigurationManager.GetSection("modulesSection/rewriteModule");

    // module is turned off in web.configif (!cfg.RewriteOn) return;

    string path = HttpContext.Current.Request.Path;

    // there us nothing to processif (path.Length == 0) return;

    // load rewriting rules from web.config// and loop through rules collection until first matchXmlNode rules = cfg.XmlSection.SelectSingleNode("rewriteRules");

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    8/15

    foreach (XmlNode xml in rules.SelectNodes("rule")){

    try{

    Regex re = newRegex(cfg.RewriteBase + xml.Attributes["source"].InnerText,RegexOptions.IgnoreCase);

    Match match = re.Match(path);if (match.Success){

    path = re.Replace(path,xml.Attributes["destination"].InnerText);

    if (path.Length != 0){

    // check for QueryString parameters

    if(HttpContext.Current.Request.QueryString.Count != 0){// if there are Query String papameters// then append them to current pathstring sign = (path.IndexOf('?') == -1) ? "?" : "&";path = path + sign +

    HttpContext.Current.Request.QueryString.ToString();}// new path to rewrite tostring rew = cfg.RewriteBase + path;// save original path to HttpContext for further useHttpContext.Current.Items.Add("OriginalUrl",HttpContext.Current.Request.RawUrl);

    // rewriteHttpContext.Current.RewritePath(rew);}return;

    }}catch (Exception ex){

    throw (newException("Incorrect rule.", ex));}

    }return;

    }

    }}

    We must then register this method:

    publicvoid Init(HttpApplication context){

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    9/15

    context.BeginRequest += newEventHandler(RewriteModule_BeginRequest);}But this is just half of the road we need to go down, because the rewriting module should handle a web form'spostbacks and populate a collection of 'virtual path parameters'. In the given code you will not find a part thatdoes this task. Let's put 'virtual path parameters' aside for a moment. The main thing here is to handlepostbacks correctly.

    If we run the code above and look through the HTML source of the ASP.NET page for an action attribute of theform tag, we find that even a virtual URL action attribute contains a path to an actual ASP.NET page. Forexample, if we are using the page ~/Posts.aspx to handle requests like http://www.somebloghost.com/Blogs/2006/12/10/Default.aspx, we find the action="/Posts.aspx". This means that the userwill be using not the virtual URL on postback, but the actual one: http://www. somebloghost.com/Blog.aspx.This is not what we want to use here! So, a few more lines of code are required to achieve the desired result.

    First, we must register and implement one more method in ourHttpModule:

    publicvoid Init(HttpApplication context){

    // it is necessary tocontext.BeginRequest += newEventHandler(RewriteModule_BeginRequest);

    context.PreRequestHandlerExecute += newEventHandler(RewriteModule_PreRequestHandlerExecute);

    }

    void RewriteModule_PreRequestHandlerExecute(object sender, EventArgs e){

    HttpApplication app = (HttpApplication)sender;if ((app.Context.CurrentHandler isPage) &&

    app.Context.CurrentHandler != null){

    Page pg = (Page)app.Context.CurrentHandler;

    pg.PreInit += newEventHandler(Page_PreInit);}

    }

    This method checks if the user requested a normal ASP.NET page and adds a handler for the PreInit eventof the page lifecycle. This is where RewriteContext will be populated with actual parameters and a secondURL rewriting will be performed. The second rewriting is necessary to make ASP.NET believe it wants to use avirtual path in the action attribute of an HTML form.

    void Page_PreInit(object sender, EventArgs e){

    // restore internal path to original// this is required to handle postbacks

    if (HttpContext.Current.Items.Contains("OriginalUrl")){string path = (string)HttpContext.Current.Items["OriginalUrl"];

    // save query string parameters to contextRewriteContext con = newRewriteContext(HttpContext.Current.Request.QueryString, path);

    HttpContext.Current.Items["RewriteContextInfo"] = con;

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    10/15

    if (path.IndexOf("?") == -1)path += "?";

    HttpContext.Current.RewritePath(path);}

    }

    Finally, we see three classes in ourRewriteModu

    le assembly:

    Registering RewriteModule in web.config

    To use RewriteModule in a web application, you should add a reference to the rewrite module assembly andregisterHttpModule in the web applicationweb.config file. To registerHttpModule, open the

    web.config file and add the following code into the system.web section :

    Using RewriteModule

    There are a few things you should bear in mind when using RewriteModule:

    y It is impossible to use special characters in a well-formed XML document which isweb.config in itsnature. You should therefore use HTML-encoded symbols instead. For example, use & instead of&.

    y To use relative paths in your ASPX pages, you should call the ResolveUrl method inside HTMLtags: . Note, that ~/ points to the rootdirectory of a web application.

    y Bear in mind the greediness of regular expressions and put rewriting rules toweb.config in order of

    their greediness, for instance:

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    11/15

    Source=$1&Year=$2&"/>

    y If you would like to use RewriteModule with pages other than .aspx, you should configure IIS to maprequests to pages with the desired extensions to ASP.NET runtime as described in the next section.

    IIS Configuration: using RewriteModule with extensions other than .aspx

    To use a rewriting module with extensions other than .aspx (for example, .html or .xml), you must configure IISso that these file extensions are mapped to the ASP.NET engine (ASP.NET ISAPI extension). Note that to doso, you have to be logged in as an Administrator.

    Open the IIS Administration console and select a virtual directory website for which you want to configuremappings.

    Windows XP (IIS 5)Virtual Directory "RW"

    Windows 2003 Server (IIS 6)Default Web Site

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    12/15

    Then click the Configuration button on the Virtual Directory tab (or the Home Directory tab if you areconfiguring mappings for the website).

    Windows XP (IIS 5)

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    13/15

    Windows 2003 Server (IIS 6)

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    14/15

    Next, click on the Addbutton and type in an extension. You also need to specify a path to an ASP.NET ISAPIExtension. Don't forget to uncheck the option Check that file exists.

    If you would like to map all extensions to ASP.NET, then for IIS 5 on Windows XP you have only to map .*extension to the ASP.NET ISAPI extension. But for IIS 6 on Windows 2003 you have to do it in a slightlydifferent way: click on the Insert button instead of the Add button, and specify a path to the ASP.NETISAPI extension.

  • 8/8/2019 This Article Describes a Complete Solution for URL Rewriting in ASP

    15/15

    Conclusions

    Now we have built a simple but very powerful rewriting module for ASP.NET that supports regular expressions-based URLs and page postbacks. This solution is easily implemented and gives users the ability to use short,neat URLs free of bulky Query String parameters. To start using the module, you simply have to add a

    reference to the RewriteModule assembly in your web application and add a few lines of code to theweb.config file, whereupon you have all the power of regular expressions at your disposal to override URLs.

    The rewrite module is easily maintainable, because to change any 'virtual' URL you only need to edit theweb.config file. If you need to test your application without the module, you can turn it off inweb.config

    without modifying any code.

    To gain a deeper insight into the rewriting module, take a look through the source code and example attachedto this article. I believe you'll find using the rewriting module a far more pleasant experience, than using thenative URL mapping in ASP.NET 2.0.