{"id":232,"date":"2007-03-01T00:00:00","date_gmt":"2007-03-01T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/a-complete-url-rewriting-solution-for-asp-net-2-0\/"},"modified":"2021-05-17T18:35:06","modified_gmt":"2021-05-17T18:35:06","slug":"a-complete-url-rewriting-solution-for-asp-net-2-0","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/a-complete-url-rewriting-solution-for-asp-net-2-0\/","title":{"rendered":"A Complete URL Rewriting Solution for ASP.NET 2.0"},"content":{"rendered":"<div id=\"pretty\">\n<p>This article describes a complete solution for URL rewriting in ASP.NET 2.0. The solution uses regular expressions to specify rewriting rules and resolves possible difficulties with postback from pages accessed via virtual URLs.<\/p>\n<h2>Why use URL rewriting?<\/h2>\n<p>The two main reasons to incorporate URL rewriting capabilities into your ASP.NET applications are usability and maintainability.<\/p>\n<h3>Usability<\/h3>\n<p>It is well-known that users of web applications prefer short, neat URLs to monstrous addresses packed with difficult to comprehend query string parameters. From time to time, being able to remember and type in a concise URL is less time-consuming than adding the page to a browser&#8217;s favorites to access later. Again, when access to a browser&#8217;s favorites is unavailable, it can be more convenient to type in the URL of a page on the browser address bar, without having to remember a few keywords and type them into a search engine in order to find the page.<\/p>\n<p>Compare the following two addresses and decide which one you like more:<\/p>\n<ol>\n<li><strong>http:\/\/www.somebloghost.com\/Blogs\/Posts.aspx?Year=2006&amp;Month=12&amp;Day=10<\/strong><\/li>\n<li><strong>http:\/\/www. somebloghost.com\/Blogs\/2006\/12\/10\/<\/strong><\/li>\n<\/ol>\n<p>The first URL contains query string parameters to encode the date for which some blog engines should show available postings. The second URL contains this information in the address, giving the user a clear idea of what he or she is going to see. The second address also allows the user to hack the URL to see all postings available in December, simply by removing the text encoding the day &#8217;10&#8217;: <strong>http:\/\/www.somehost.com\/Blogs\/2006\/12\/<\/strong>.<\/p>\n<h3>Maintainability<\/h3>\n<p>In large web applications, it is common for developers to move pages from one directory to another. Let us suppose that support information was initially available at <strong>http:\/\/www.somebloghost.com\/Info\/Copyright.aspx<\/strong> and <strong>http:\/\/www.somebloghost.com\/Support\/Contacts.aspx<\/strong>, but at a later date the developers moved the <span class=\"CodeInText\">Copyright.aspx<\/span> and <span class=\"CodeInText\">Contacts.aspx<\/span> pages to a new folder called <span class=\"CodeInText\">Help<\/span>. Users who have bookmarked the old URLs need to be redirected to the new location. This issue can be resolved by adding simple dummy pages containing calls to <span class=\"CodeInText\">Response.Redirect<\/span>(<i>new location<\/i>). However, what if there are hundreds of moved pages all over the application directory? The web project will soon contain too many useless pages that have the sole purpose of redirecting users to a new location.<\/p>\n<p>Enter URL rewriting, which allows a developer to move pages between virtual directories just by editing a configuration file. In this way, the developer can separate the physical structure of the website from the logical structure available to users via URLs.<\/p>\n<h2>Native URL mapping in ASP.NET 2.0<\/h2>\n<p>ASP.NET 2.0 provides an out-of-the-box solution for mapping static URLs within a web application. It is possible to map old URLs to new ones in web.config without writing any lines of code. To use URL mapping, just create a new <span class=\"CodeInText\">urlMappings<\/span> section within the <span class=\"CodeInText\">system.web<\/span> section of your <span class=\"CodeInText\">web.config<\/span> file and add the required mappings (the path <span class=\"CodeInText\">~\/<\/span> points to the root directory of the web application):<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;urlMappings enabled=\"true\"&gt;\r\n   &lt;add url=\"~\/Info\/Copyright.aspx\" mappedUrl=\"~\/Help\/Copyright.aspx\" \/&gt;\r\n   &lt;add url=\"~\/Support\/Contacts.aspx\" mappedUrl=\"~\/Help\/Contacts.aspx\" \/&gt;\r\n&lt;\/urlMappings&gt; \r\n<\/pre>\n<p>Thus, if a user types <strong>http:\/\/www.somebloghost.com\/Support\/Contacts.aspx<\/strong>, he can then see the page located at <strong>http:\/\/www.somebloghost.com\/Help\/Contacts.aspx<\/strong>, without even knowing the page had been moved.<\/p>\n<p>This solution is fine if you have only two pages that have been moved to other locations, but it is completely unsuitable where there are dozens of re-located pages, or where a really neat URL needs to be created.<\/p>\n<p>Another possible disadvantage of the native URL mapping technique is that if the page <span class=\"CodeInText\">Contacts.aspx<\/span> contains elements initiating postback to the server (which is most probable), then the user will be surprised that the URL <strong>http:\/\/www.somebloghost.com\/Support\/Contacts.aspx<\/strong> changes to <strong>http:\/\/www.somebloghost.com\/Help\/Contacts.aspx<\/strong>. This happens because the ASP.NET engine fills the <i>action<\/i> attribute of the <i>form<\/i> HTML tag with the actual path to a page. So the form renders like this:<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;form name=\"formTest\" method=\"post\"\r\naction=\"..\/Help\/Contacts.aspx\" id=\"formTest\"&gt;\r\n&lt;\/form&gt;\r\n<\/pre>\n<p>Thus, URL mapping available in ASP.NET 2.0 is almost always useless. It would be much better to be able to specify a set of similar URLs in one mapping rule. The best solution is to use Regular Expressions (for overview see <a href=\"http:\/\/en.wikipedia.org\/wiki\/Regular_expression\">Wikipedia<\/a> and for implementation in .NET see <a href=\"http:\/\/msdn2.microsoft.com\/en-us\/library\/ms972966.aspx\">MSDN<\/a>), but an ASP.NET 2.0 mapping does not support regular expressions. We therefore need to develop a different solution to built-in URL mapping.<\/p>\n<h2>The URL rewriting module<\/h2>\n<p>The best way to implement a URL rewriting solution is to create reusable and easily configurable modules, so the obvious decision is to create an HTTP Module (for details on HTTP Modules see <a href=\"http:\/\/msdn.microsoft.com\/msdnmag\/issues\/02\/05\/asp\/\">MSDN Magazine<\/a>) and implement it as an individual assembly. To make this assembly as easy to use as possible, we need to implement the ability to configure the rewrite engine and specify rules in a <span class=\"CodeInText\">web.config<\/span> file.<\/p>\n<p>During the development process we need to be able to turn the rewriting module on or off (for example if you have a bug that is difficult to catch, and which may have been caused by incorrect rewriting rules). There should, therefore, be an option in the rewriting module configuration section in <span class=\"CodeInText\">web.config<\/span> to turn the module on or off. So, a sample configuration section within <span class=\"CodeInText\">web.config<\/span> can go like this:<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;rewriteModule&gt;\r\n  &lt;rewriteOn&gt;true&lt;\/rewriteOn&gt;\r\n  &lt;rewriteRules&gt;\r\n      &lt;rule source=\"(\\d+)\/(\\d+)\/(\\d+)\/\" \r\n         destination=\"Posts.aspx?Year=$1&amp;amp;Month=$2&amp;amp;Day=$3\"\/&gt;\r\n      &lt;rule source=\"(.*)\/Default.aspx\" \r\n         destination=\"Default.aspx?Folder=$1\"\/&gt;\r\n  &lt;\/rewriteRules&gt;\r\n&lt;\/rewriteModule&gt;\r\n<\/pre>\n<p>This means that all requests that run like: <strong>http:\/\/localhost\/Web\/2006\/12\/10\/<\/strong> should be internally redirected to the page <span class=\"CodeInText\">Posts.aspx<\/span> with query string parameters.<\/p>\n<p>Please note that <span class=\"CodeInText\">web.config<\/span> is a well-formed XML file, and it is prohibited to use the symbol <b><span class=\"CodeInText\">&amp;<\/span><\/b> in attribute value strings. In this case, you should use <span class=\"CodeInText\">&amp;amp<\/span>; instead in the destination attribute of the rule element.<\/p>\n<p>To use the <span class=\"CodeInText\">rewriteModule<\/span> section in the <span class=\"CodeInText\">web.config<\/span> file, you need to register a section name and a section handler for this section. To do this, add a <span class=\"CodeInText\">configSections<\/span> section to <span class=\"CodeInText\">web.config<\/span>:<\/p>\n<pre class=\"lang:xhtml theme:github\"> &lt;configSections&gt;    &lt;sectionGroup name=\"modulesSection\"&gt;\r\n      &lt;section name=\"rewriteModule\" type=\"RewriteModule.\r\n          RewriteModuleSectionHandler, RewriteModule\"\/&gt;\r\n    &lt;\/sectionGroup&gt;\r\n  &lt;\/configSections&gt;\r\n<\/pre>\n<p>This means you may use the following section below the <span class=\"CodeInText\">configSections<\/span> section:<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;modulesSection&gt;\r\n    &lt;rewriteModule&gt;\r\n      &lt;rewriteOn&gt;true&lt;\/rewriteOn&gt;\r\n      &lt;rewriteRules&gt;\r\n              &lt;rule source=\"(\\d+)\/(\\d+)\/(\\d+)\/\"\r\ndestination=\"Post.aspx?Year=$1&amp;amp;Month=$2&amp;amp;Day=$3\"\/&gt;\r\n              &lt;rule source=\"(.*)\/Default.aspx\"\r\ndestination=\"Default.aspx?Folder=$1\"\/&gt;\r\n      &lt;\/rewriteRules&gt;\r\n    &lt;\/rewriteModule&gt;\r\n  &lt;\/modulesSection&gt;\r\n<\/pre>\n<p>Another thing we have to bear in mind during the development of the rewriting module is that it should be possible to use &#8216;virtual&#8217; URLs with query string parameters, as shown in the following: <strong>http:\/\/www.somebloghost.com\/2006\/12\/10\/?Sort=Desc&amp;SortBy=Date<\/strong>. Thus we have to develop a solution that can detect parameters passed via query string and also via virtual URL in our web application.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov-feb07.gif\" alt=\"357-G_Magdanurov-feb07.gif\" \/><\/p>\n<p>So, let&#8217;s start by building a new Class Library. We need to add a reference to the <span class=\"CodeInText\">System.Web<\/span> assembly, as we 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 read <span class=\"CodeInText\">web.config<\/span>, we need to add a reference to the <span class=\"CodeInText\">System.Configuration<\/span> assembly.<\/p>\n<h2>Handling the configuration section<\/h2>\n<p>To be able to read the configuration settings specified in <span class=\"CodeInText\">web.config<\/span>, we have to create a class that implements the <span class=\"CodeInText\">IConfigurationSectionHandler<\/span> interface (see <a href=\"http:\/\/search.msdn.microsoft.com\/search\/Redirect.aspx?title=IConfigurationSectionHandler%20Interface&amp;url=http:\/\/msdn.microsoft.com\/library\/en-us\/cpref\/html\/frlrfSystemConfigurationIConfigurationSectionHandlerClassTopic.asp\">MSDN<\/a> for details). This can be seen below:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\nusing System.Configuration;\r\nusing System.Web;\r\nusing System.Xml;\r\n \r\n \r\nnamespace RewriteModule\r\n{\r\n    public class RewriteModuleSectionHandler : IConfigurationSectionHandler\r\n    {\r\n \r\n        private XmlNode _XmlSection;\r\n        private string _RewriteBase;\r\n        private bool _RewriteOn;\r\n \r\n        public XmlNode XmlSection\r\n        {\r\n            get { return _XmlSection; }\r\n        }\r\n \r\n        public string RewriteBase\r\n        {\r\n            get { return _RewriteBase; }\r\n        }\r\n \r\n        public bool RewriteOn\r\n        {\r\n            get { return _RewriteOn; }\r\n        }\r\n        public object Create(object parent, \r\n                            object configContext,\r\n                            System.Xml.XmlNode section)\r\n        {\r\n            \/\/ set base path for rewriting module to\r\n            \/\/ application root\r\n            _RewriteBase = HttpContext.Current.Request.ApplicationPath + \"\/\";\r\n \r\n            \/\/ process configuration section \r\n            \/\/ from web.config\r\n            try\r\n            {\r\n                _XmlSection = section;\r\n                _RewriteOn = Convert.ToBoolean(\r\n                            section.SelectSingleNode(\"rewriteOn\").InnerText);\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                throw (new Exception(\"Error while processing RewriteModule\r\n                    configuration section.\", ex));\r\n            }\r\n            return this;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>The Class <span class=\"CodeInText\">RewriteModuleSectionHandler<\/span> will be initialized by calling the <span class=\"CodeInText\">Create<\/span> method with the <span class=\"CodeInText\">rewriteModule<\/span> section of <span class=\"CodeInText\">web.config<\/span> passed as <span class=\"CodeInText\">XmlNode<\/span>. The <span class=\"CodeInText\">SelectSingleNode<\/span> method of the <span class=\"CodeInText\">XmlNode<\/span> class is used to return values for module settings.<\/p>\n<h2>Using parameters from rewritten URL<\/h2>\n<p>When handling virtual URLS such as <strong>http:\/\/www. somebloghost.com\/Blogs\/gaidar\/?Sort=Asc<\/strong> (that is, a virtual URL with query string parameters), it is important that you clearly distinguish parameters that were passed via a query string from parameters that were passed as virtual directories. Using the rewriting rules specified below:<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;rule source=\"(.*)\/Default.aspx\" destination=\"Default.aspx?Folder=$1\"\/&gt;,\r\n<\/pre>\n<p>You can use the following URL:<\/p>\n<p><strong>http:\/\/www. somebloghost.com\/gaidar\/?Folder=Blogs<\/strong><\/p>\n<p>&#8230;and the result will be the same as if you used this URL:<\/p>\n<p><strong>http:\/\/www. somebloghost.com\/Blogs\/gaidar\/<\/strong><\/p>\n<p>To resolve this issue, we have to create some kind of wrapper for &#8216;virtual path parameters&#8217;. This could be a collection with a static method to access the current parameters set:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\nusing System.Collections.Specialized;\r\nusing System.Web;\r\n \r\nnamespace RewriteModule\r\n{\r\n \r\n    public class RewriteContext\r\n    {\r\n        \/\/ returns actual RewriteContext instance for\r\n        \/\/ current request\r\n        public static RewriteContext Current\r\n        {\r\n            get\r\n            {\r\n                \/\/ Look for RewriteContext instance in\r\n                \/\/ current HttpContext. If there is no RewriteContextInfo\r\n                \/\/ item then this means that rewrite module is turned off\r\n                if(HttpContext.Current.Items.Contains(\"RewriteContextInfo\"))\r\n                    return (RewriteContext)\r\nHttpContext.Current.Items[\"RewriteContextInfo\"];\r\n                else\r\n                    return new RewriteContext();\r\n            }\r\n        }\r\n \r\n        public RewriteContext()\r\n        {\r\n            _Params = new NameValueCollection();\r\n            _InitialUrl = String.Empty;\r\n        }\r\n \r\n        public RewriteContext(NameValueCollection param, string url)\r\n        {\r\n            _InitialUrl = url;\r\n            _Params = new NameValueCollection(param);\r\n            \r\n        }\r\n \r\n        private NameValueCollection _Params;\r\n \r\n        public NameValueCollection Params\r\n        {\r\n            get { return _Params; }\r\n            set { _Params = value; }\r\n        }\r\n \r\n        private string _InitialUrl;\r\n \r\n        public string InitialUrl\r\n        {\r\n            get { return _InitialUrl; }\r\n            set { _InitialUrl = value; }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>You can see from the above that it is possible to access &#8216;virtual path parameters&#8217; via the <span class=\"CodeInText\">RewriteContext.Current<\/span> collection and be sure that those parameters were specified in the URL as virtual directories or pages names, and <i>not<\/i> as query string parameters.<\/p>\n<h2>Rewriting URLs<\/h2>\n<p>Now let&#8217;s try some rewriting. First, we need to read rewriting rules from the <span class=\"CodeInText\">web.config<\/span> file. Secondly, we need to check the actual URL against the rules and, if necessary, do some rewriting so that the appropriate page is executed.<\/p>\n<p>We create an <span class=\"CodeInText\">HttpModule<\/span>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">class RewriteModule : IHttpModule\r\n{\r\npublic void Dispose() { }\r\npublic void Init(HttpApplication context)\r\n{}\r\n}\r\n<\/pre>\n<p>When adding the <span class=\"CodeInText\">RewriteModule_BeginRequest<\/span> method that will process the rules against the given URL, we need to check if the given URL has query string parameters and call <span class=\"CodeInText\">HttpContext.Current.RewritePath<\/span> to give control over to the appropriate ASP.NET page.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\nusing System.Web;\r\nusing System.Configuration;\r\nusing System.Xml;\r\nusing System.Text.RegularExpressions;\r\nusing System.Web.UI;\r\nusing System.IO;\r\nusing System.Collections.Specialized;\r\n \r\nnamespace RewriteModule\r\n{\r\n    class RewriteModule : IHttpModule\r\n    {\r\n \r\n        public void Dispose() { }\r\n \r\n        public void Init(HttpApplication context)\r\n        {\r\n            \/\/ it is necessary to \r\n            context.BeginRequest += new EventHandler(\r\n                 RewriteModule_BeginRequest);\r\n        }\r\n \r\n        void RewriteModule_BeginRequest(object sender, EventArgs e)\r\n        {\r\n \r\n            RewriteModuleSectionHandler cfg =\r\n       (RewriteModuleSectionHandler)\r\nConfigurationManager.GetSection\r\n         (\"modulesSection\/rewriteModule\");\r\n \r\n            \/\/ module is turned off in web.config\r\n            if (!cfg.RewriteOn) return; \r\n \r\n            string path = HttpContext.Current.Request.Path;\r\n \r\n            \/\/ there us nothing to process\r\n            if (path.Length == 0) return; \r\n \r\n            \/\/ load rewriting rules from web.config\r\n            \/\/ and loop through rules collection until first match\r\n            XmlNode rules = cfg.XmlSection.SelectSingleNode(\"rewriteRules\");\r\n            foreach (XmlNode xml in rules.SelectNodes(\"rule\"))\r\n            {\r\n                try\r\n                {\r\n                    Regex re = new Regex(\r\n                     cfg.RewriteBase + xml.Attributes[\"source\"].InnerText,\r\n                     RegexOptions.IgnoreCase);\r\n\r\n\r\n                    Match match = re.Match(path);\r\n                    if (match.Success)\r\n                    {\r\n                        path = re.Replace(\r\n                             path, \r\n                             xml.Attributes[\"destination\"].InnerText);\r\n\r\n\r\n                        if (path.Length != 0)\r\n                        {\r\n                            \/\/ check for QueryString parameters\r\n                       if(HttpContext.Current.Request.QueryString.Count != 0)\r\n                       {\r\n                       \/\/ if there are Query String papameters\r\n                       \/\/ then append them to current path\r\n                       string sign = (path.IndexOf('?') == -1) ? \"?\" : \"&amp;\";\r\n                       path = path + sign +\r\n                          HttpContext.Current.Request.QueryString.ToString();\r\n                       }\r\n                       \/\/ new path to rewrite to\r\n                       string rew = cfg.RewriteBase + path;\r\n                       \/\/ save original path to HttpContext for further use\r\n                       HttpContext.Current.Items.Add(\r\n                         \"OriginalUrl\", \r\n                         HttpContext.Current.Request.RawUrl);\r\n                       \/\/ rewrite\r\n                       HttpContext.Current.RewritePath(rew);\r\n                       }\r\n                       return;\r\n                    }\r\n                }\r\n                catch (Exception ex)\r\n                {\r\n                    throw (new Exception(\"Incorrect rule.\", ex));\r\n                }\r\n            }\r\n            return;\r\n        }\r\n \r\n    }\r\n}\r\n \r\n<\/pre>\n<p>We must then register this method:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public void Init(HttpApplication context)\r\n{\r\n context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);\r\n}\r\n<\/pre>\n<p>But this is just half of the road we need to go down, because the rewriting module should handle a web form&#8217;s postbacks and populate a collection of &#8216;virtual path parameters&#8217;. In the given code you will not find a part that does this task. Let&#8217;s put &#8216;virtual path parameters&#8217; aside for a moment. The main thing here is to handle postbacks correctly.<\/p>\n<p>If we run the code above and look through the HTML source of the ASP.NET page for an action attribute of the form tag, we find that even a virtual URL action attribute contains a path to an actual ASP.NET page. For example, if we are using the page <span class=\"CodeInText\">~\/Posts.aspx<\/span> to handle requests like:<strong><\/p>\n<p> http:\/\/www. somebloghost.com\/Blogs\/2006\/12\/10\/Default.aspx<\/strong>, <\/p>\n<p> &#8230;we find the action=&#8221;\/Posts.aspx&#8221;. This means that the user will be using not the virtual URL on postback, but the actual one: <strong>http:\/\/www. somebloghost.com\/Blog.aspx<\/strong>. This is not what we want to use here! So, a few more lines of code are required to achieve the desired result.<\/p>\n<p>First, we must register and implement one more method in our <span class=\"CodeInText\">HttpModule<\/span>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">        public void Init(HttpApplication context)\r\n        {\r\n            \/\/ it is necessary to \r\n            context.BeginRequest += new EventHandler(\r\n                 RewriteModule_BeginRequest);\r\n            context.PreRequestHandlerExecute += new EventHandler(\r\n                 RewriteModule_PreRequestHandlerExecute);\r\n        }\r\n \r\n      void RewriteModule_PreRequestHandlerExecute(object sender, EventArgs e)\r\n        {\r\n            HttpApplication app = (HttpApplication)sender;\r\n            if ((app.Context.CurrentHandler is Page) &amp;&amp; \r\n                 app.Context.CurrentHandler != null)\r\n            {\r\n                Page pg = (Page)app.Context.CurrentHandler;\r\n                pg.PreInit += new EventHandler(Page_PreInit);\r\n            }\r\n        }\r\n<\/pre>\n<p>This method checks if the user requested a normal ASP.NET page and adds a handler for the <span class=\"CodeInText\">PreInit<\/span> event of the page lifecycle. This is where <span class=\"CodeInText\">RewriteContext<\/span> will be populated with actual parameters and a second URL rewriting will be performed. The second rewriting is necessary to make ASP.NET believe it wants to use a virtual path in the action attribute of an HTML form.<\/p>\n<pre class=\"lang:c# theme:vs2012\">void Page_PreInit(object sender, EventArgs e)\r\n        {\r\n            \/\/ restore internal path to original\r\n            \/\/ this is required to handle postbacks\r\n            if (HttpContext.Current.Items.Contains(\"OriginalUrl\"))\r\n            {\r\n              string path = (string)HttpContext.Current.Items[\"OriginalUrl\"];\r\n \r\n              \/\/ save query string parameters to context\r\n              RewriteContext con = new RewriteContext(\r\n                HttpContext.Current.Request.QueryString, path);\r\n \r\n              HttpContext.Current.Items[\"RewriteContextInfo\"] =  con;\r\n \r\n              if (path.IndexOf(\"?\") == -1)\r\n                  path += \"?\";\r\n              HttpContext.Current.RewritePath(path);\r\n            }\r\n        }\r\n<\/pre>\n<p>Finally, we see three classes in our <span class=\"CodeInText\">RewriteModule<\/span> assembly:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%282%29-feb07%20%282%29.gif\" alt=\"357-G_Magdanurov%282%29-feb07%20%282%29.\" \/><\/p>\n<h2>Registering RewriteModule in web.config<\/h2>\n<p>To use <span class=\"CodeInText\">RewriteModule<\/span> in a web application, you should add a reference to the rewrite module assembly and register <span class=\"CodeInText\">HttpModule<\/span> in the web application <span class=\"CodeInText\">web.config<\/span> file. To register <span class=\"CodeInText\">HttpModule<\/span>, open the <span class=\"CodeInText\">web.config<\/span> file and add the following code into the <span class=\"CodeInText\">system.web<\/span> section:<\/p>\n<pre class=\"lang:xhtml theme:github\">&lt;httpModules&gt;\r\n&lt;add name=\"RewriteModule\" type=\"RewriteModule.RewriteModule, RewriteModule\"\/&gt;\r\n&lt;\/httpModules&gt;\r\n<\/pre>\n<h2>Using RewriteModule<\/h2>\n<p>There are a few things you should bear in mind when using <span class=\"CodeInText\">RewriteModule<\/span>:<\/p>\n<ul>\n<li>It is impossible to use special characters in a well-formed XML document which is <span class=\"CodeInText\">web.config<\/span> in its nature. You should therefore use HTML-encoded symbols instead. For example, use <span class=\"CodeInText\">&amp;amp<\/span>; instead of <span class=\"CodeInText\">&amp;<\/span>.<\/li>\n<li>To use relative paths in your ASPX pages, you should call the <span class=\"CodeInText\">ResolveUrl<\/span> method inside HTML tags: <span class=\"ImportantWords\">&lt;img src=&#8221;&lt;%=ResolveUrl(&#8220;~\/Images\/Test.jpg&#8221;)%&gt;&#8221; \/&gt;<\/span>. Note, that ~\/ points to the root directory of a web application.<\/li>\n<li>Bear in mind the greediness of regular expressions and put rewriting rules to <span class=\"CodeInText\">web.config<\/span> in order of their greediness, for instance<\/li>\n<\/ul>\n<pre class=\"lang:xhtml theme:github\">&lt;rule source=\"Directory\/(.*)\/(.*)\/(.*)\/(.*).aspx\"\r\n destination=\"Directory\/Item.aspx?\r\n Source=$1&amp;amp;Year=$2&amp;amp;ValidTill=$3&amp;amp;Sales=$4\"\/&gt;\r\n&lt;rule source=\"Directory\/(.*)\/(.*)\/(.*).aspx\"\r\n destination=\"Directory\/Items.aspx?\r\n Source=$1&amp;amp;Year=$2&amp;amp;ValidTill=$3\"\/&gt;\r\n&lt;rule source=\"Directory\/(.*)\/(.*).aspx\"\r\n destination=\"Directory\/SourceYear.aspx?\r\n Source=$1&amp;amp;Year=$2&amp;amp;\"\/&gt;\r\n&lt;rule source=\"Directory\/(.*).aspx\"\r\n destination=\"Directory\/Source.aspx?Source=$1\"\/&gt;<\/pre>\n<ul>\n<li>If you would like to use <span class=\"CodeInText\">RewriteModule<\/span> with pages other than .aspx, you should configure IIS to map requests to pages with the desired extensions to ASP.NET runtime as described in the next section.<\/li>\n<\/ul>\n<h2>IIS Configuration: using RewriteModule with extensions other than .aspx<\/h2>\n<p>To use a rewriting module with extensions other than .aspx (for example, .html or .xml), you must configure IIS so that these file extensions are mapped to the ASP.NET engine (ASP.NET ISAPI extension). Note that to do so, you have to be logged in as an Administrator.<\/p>\n<p>Open the IIS Administration console and select a virtual directory website for which you want to configure mappings.<\/p>\n<p><strong>Windows XP (IIS 5)<br \/>\n <\/strong><strong>Virtual Directory &#8220;RW&#8221; <\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%283%29-feb07.gif\" alt=\"357-G_Magdanurov%283%29-feb07.gif\" \/><\/p>\n<p><strong>Windows 2003 Server (IIS 6)<\/strong> <br \/>\n <strong>Default Web Site<\/strong><\/p>\n<p><strong><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%284%29-feb07.gif\" alt=\"357-G_Magdanurov%284%29-feb07.gif\" \/><\/strong><\/p>\n<p>Then click the <span class=\"CodeInText\">Configuration&#8230;<\/span> button on the Virtual Directory tab (or the Home Directory tab if you are configuring mappings for the website).<\/p>\n<p><strong>Windows XP (IIS 5) <\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%285%29-feb07.gif\" alt=\"357-G_Magdanurov%285%29-feb07.gif\" \/><\/p>\n<p><strong>Windows 2003 Server (IIS 6)<\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%286%29-feb07.gif\" alt=\"357-G_Magdanurov%286%29-feb07.gif\" \/><\/p>\n<p>Next, click on the <span class=\"CodeInText\">Add<\/span> button and type in an extension. You also need to specify a path to an ASP.NET ISAPI Extension. Don&#8217;t forget to uncheck the option <span class=\"CodeInText\">Check that file exists<\/span>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%287%29-feb07.gif\" alt=\"357-G_Magdanurov%287%29-feb07.gif\" \/><\/p>\n<p>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 slightly different way: click on the <span class=\"CodeInText\">Insert&#8230;<\/span> button instead of the <span class=\"CodeInText\">Add&#8230;<\/span> button, and specify a path to the ASP.NET ISAPI extension.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/357-G_Magdanurov%288%29-feb07.gif\" alt=\"357-G_Magdanurov%288%29-feb07.gif\" \/><\/p>\n<h2>Conclusions<\/h2>\n<p>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 <span class=\"CodeInText\">RewriteModule<\/span> assembly in your web application and add a few lines of code to the <span class=\"CodeInText\">web.config<\/span> 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 &#8216;virtual&#8217; URL you only need to edit the <span class=\"CodeInText\">web.config<\/span> file. If you need to test your application without the module, you can turn it off in <span class=\"CodeInText\">web.config<\/span> without modifying any code.<\/p>\n<p>To gain a deeper insight into the rewriting module, take a look through the source code and example attached to this article. I believe you&#8217;ll find using the rewriting module a far more pleasant experience, than using the native URL mapping in ASP.NET 2.0.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Ever wondered whether it&#8217;s possible to create neater URLS, free of bulky Query String parameters? Gaidar Magdanurov shows you how with a step-by-step guide to his rewriting solution for ASP.NET 2.0.&hellip;<\/p>\n","protected":false},"author":221819,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4158,4156,4157,4692,4693,4690,4691,4689,4687,4694,4688],"coauthors":[7977],"class_list":["post-232","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-2-0","tag-asp","tag-asp-net","tag-config","tag-configuration","tag-mapping","tag-native","tag-rewrite","tag-rewriting-module","tag-section","tag-url"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221819"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=232"}],"version-history":[{"count":10,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/232\/revisions"}],"predecessor-version":[{"id":74143,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/232\/revisions\/74143"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=232"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}