Url Rewriting in ASP.Net

5/7/2008

I'm certain that if you've implemented a web site, you know that many of the pages we create are nothing more than copies of other pages. We can of course just use query strings and reuse a page, loading the content that we actually need, but this tends to cause ugly URLs to show up. At the same time, we don't want to copy the page multiple times and just change the few items that need changing. So how do we get around this issue? Simple, URL rewriting.

For all requests that come in, we check to see if it matches some regex or string or something and simply redirect the output to the appropriate file. For instance we might want it to look like we have a directory structure where each type of item we are selling has its own directory (electronics, jewelry, etc.) and each item in that category has its own page in that directory. So we might have /electronics/TV.aspx. When that request is made, we simply check our rules, find out that it matches, and redirect it to something like Default.aspx?Path=/electronics/tv.aspx. The code to do this is actually rather simple:

   1: #region Usings 
   2:  
   3: using System;
   4: using System.Collections.Specialized;
   5: using System.Text.RegularExpressions;
   6: using System.Web; 
   7:  
   8: #endregion 
   9:  
  10: namespace Site.HTTPModules
  11: {
  12:     /// <summary>
  13:     /// Does Rewriting of the urls specified in the settings
  14:     /// </summary>
  15:     public class UrlRewrite:IHttpModule
  16:     {
  17:         #region Variables
  18:         private static StringDictionary Settings = null;
  19:         #endregion 
  20:  
  21:         #region Public Functions
  22:         /// <summary>
  23:         /// Disposes of the object
  24:         /// </summary>
  25:         public void Dispose()
  26:         {
  27:         } 
  28:  
  29:         /// <summary>
  30:         /// Initializes the object
  31:         /// </summary>
  32:         /// <param name="context"></param>
  33:         public void Init(HttpApplication context)
  34:         {
  35:             Settings = //Load settings here (key is input, value is output)
  36:             context.BeginRequest += new EventHandler(context_BeginRequest);
  37:         }
  38:         #endregion 
  39:  
  40:         #region Events 
  41:  
  42:         /// <summary>
  43:         /// Checks the url vs the settings and reroutes the item if need be.
  44:         /// </summary>
  45:         /// <param name="sender"></param>
  46:         /// <param name="e"></param>
  47:         void context_BeginRequest(object sender, EventArgs e)
  48:         {
  49:             HttpContext Context = ((HttpApplication)sender).Context;
  50:             string CurrentPath = Context.Request.Path.ToLowerInvariant(); 
  51:  
  52:             foreach (string Key in Settings.Keys)
  53:             {
  54:                 Regex TempRegex = new Regex(Key);
  55:                 if (TempRegex.IsMatch(CurrentPath))
  56:                 {
  57:                     Context.RewritePath(Settings[Key] + "?Path=" + CurrentPath);
  58:                 }
  59:             }
  60:         }
  61:         #endregion
  62:     }
  63: }
   1: /// <summary>
   2: /// Form adapter to allow url rewriting
   3: /// </summary>
   4: public class FormAdapter : ControlAdapter
   5: {
   6:     protected override void Render(System.Web.UI.HtmlTextWriter writer)
   7:     {
   8:         base.Render(new RewriteFormTextWriter(writer));
   9:     }
  10: }
  11:  
  12: /// <summary>
  13: /// Text writer used by the form adapter
  14: /// </summary>
  15: public class RewriteFormTextWriter : HtmlTextWriter
  16: {
  17:     public RewriteFormTextWriter(HtmlTextWriter Writer)
  18:         : base(Writer)
  19:     {
  20:         this.InnerWriter = Writer.InnerWriter;
  21:     }
  22:  
  23:     public RewriteFormTextWriter(TextWriter Writer)
  24:         : base(Writer)
  25:     {
  26:         base.InnerWriter = Writer;
  27:     }
  28:  
  29:     public override void WriteAttribute(string name, string value, bool fEncode)
  30:     {
  31:         try
  32:         {
  33:             if (name.Equals("action", StringComparison.InvariantCultureIgnoreCase))
  34:             {
  35:                 HttpContext Context = HttpContext.Current;
  36:                 if (Context.Items["ActionAlreadyWritten"] == null)
  37:                 {
  38:                     value = Context.Request.RawUrl;
  39:                     Context.Items["ActionAlreadyWritten"] = true.ToString();
  40:                 }
  41:             }
  42:             base.WriteAttribute(name, value, fEncode);
  43:         }
  44:         catch { throw; }
  45:     }
  46: }

That's all there is to it. Well, not completely actually. On top of the HttpModule, you need the HtmlTextWriter class, and the ControlAdapter, and lastly one more thing: a .browser file in your web app's App_Browsers folder:

   1: <!--
   2:     You can find existing browser definitions at
   3:     <windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers
   4: -->
   5: <browsers>
   6:  
   7:     <browser refID="Default">
   8:         <controlAdapters>
   9:             <adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
  10:                      adapterType="FormAdapter" />
  11:         </controlAdapters>
  12:     </browser>
  13:  
  14: </browsers>

Just add that to your code base, add the httpmodule to your web.config file, and you're good to go. You do need to watch out for how you use images, files, links, etc. Using the tilde will usually cause errors, so keep that in mind. Anyway, try out the code, leave feedback, and happy coding.



Comments