Efficient MVC Redirects Using an HTTP Module

Redirects are a common necessity for websites operating in the real world.  Perhaps your marketing team wants a…

Redirects are a common necessity for websites operating in the real world.  Perhaps your marketing team wants a URL changed, or perhaps there has been an architectural change in your application that impacts its URL structure.  Maybe you’re just experimenting with SEO.  Regardless of the reason, there are a lot of ways to handle redirects in MVC.

The most common way I have seen people handing redirects is inside of an action method of their controllers.  This is probably the easiest and most straightforward way to do things.  You might see some code like this:

public ActionResult About()
{
    ViewBag.Message = "Your application description page.";

    return RedirectToAction("Contact");
}

public ActionResult Contact()
{
    ViewBag.Message = "Your contact page.";

    return View();
}

There is nothing really wrong with this strategy, especially if you’re okay with recompiling code.  However, this isn’t the most efficient way to handle redirects from a performance standpoint.  The reason for this is because ASP.NET still needs to process the entire MVC framework pipeline, just to have another request start up again.  This is not an article on the ASP.NET Request Life Cycle, but for those who are unaware, MVC Controller instantiation and Action Method invocation actually happen fairly late into the overall request pipeline.  The MVC framework is implemented through the MvcHttpHandler, which gets excuted late in the HttpApplication life cycle events.  We wanted to prevent this Handler from ever being executed.  To do this we can hook into an event that gets triggered before it and handle our redirect there.  You can see a complete list of the events at this link – we need our code to run before the PreRequestHandlerExecute event.  This is the event that begins execute of the MVC handler.

If you aren’t familiar with all of these life cycle events, the important take away is that we are preventing heavier amounts of code in the MVC framework from being processed by handling the redirect before it reaches MVC.

The image below shows everything we can eliminate by redirecting the request in a module that runs before the MVC framework is ever engaged through the MVC handler.

MVC Life Cycle Optimization

(picture edited from original at http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application)

So let’s look at a way to do redirects in an MVC application without ever involving the MVC framework.  This can be done through an HttpModule, which will intercept our request early in the Application Life Cycle and redirect it.  For this tutorial I have just created a generic MVC application in Visual Studio 2013 with no authentication.

Let’s set up our project so that we can control all of our redirects from the web.config file.  You could also apply the general concepts here to a database or some kind of CMS page, but I like doing it through the webconfig when it’s only for occasional use.

We want to be able to add as many redirects as we want, with each one as their own entry inside of a redirects Collection Configuration Section (say that 5 times fast).  The web config api has kind of strange terminology for things but I’ll try to be clear.  Also, please be aware that casing does matter for a lot of this, even for loosely typed strings, so if you run into problems make sure these are all consistent.

To begin, add a new top level <redirects> node to your webconfig file inside of the main <configurations> node.  Structure it to look something like the code below:

  </system.webServer>
  <redirects>
    <add Title="Marketing" Old="/Home/About" New="/Home/Contact" />
    <add Title="ContactChange" Old="/" New="/Home/Contact" />
  </redirects>
  <runtime>

Title will allow the user to give a brief description for the redirect, while “Old” and “New” will naturally map to the old url and the new url we want to redirect to.

Our application will not understand what this section means quite yet so we need to build some classes to fix this.  Inside your web application, add a top level folder (or create a separate class library) and call it Redirects.  Inside the folder, add a class called Redirect.cs and make sure it contains the following code:

public class Redirect : ConfigurationElement
    {
        [ConfigurationProperty("Old")]
        public string Old
        {
            get { return (string)this["Old"]; }
            set { this["Old"= value; }
        }

        [ConfigurationProperty("New")]
        public string New
        {
            get { return (string)this["New"]; }
            set { this["New"= value; }
        }

        [ConfigurationProperty("Title")]
        public string Title
        {
            get { return (string)this["Title"]; }
            set { this["Title"= value; }
        }
    }

This class is pretty straight forward.  We have a C# property for each attribute on our redirect <add> elements in our webconfig file.  The [ConfigurationProperty] attributes map the attribute names from our webconfig onto our C# class properties.  That’s all we have to do here!

Moving on, we now have to create a class that represents a collection of our Redirect items.  Add another class called RedirectCollection.cs to your folder and make sure it looks something like this:

public class RedirectCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new Redirect();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Redirect)element).Title;
        }
    }

We override two methods from the base class of ConfigurationElementCollection.  CreateNewElement is called whenever we need a new instance of our <add> element – in this case a Redirect, so we return a new Redirect object.  The second method just tells our collection what property to use when accessing the elements in the collection – I chose to use the Title attribute but you can include any kind of key, like an Id or something.  Just make sure to inlude it as an attribute to your <add> element in your <redirects> webconfig section.

The last class we need to create is called RedirectSection.  This represents the overall Redirects Configuration Section we added.  Here we only need one property, which we will call Redirects, that returns a RedirectCollection object.

public class RedirectSection : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(RedirectCollection))]
        public RedirectCollection Redirects
        {
            get { return (RedirectCollection)base[""]; }
        }
    }

After these three redirects are in place we need to add another entry into our webconfig.  At the top of the <configurations> node in our web.config, make sure you have a configSections node and add a new entry inside of it that maps our RedirectSection class to to the webconfig section.  Make sure you get the strongly typed name correct, it will be different depending on your project name or namespace name.

<configuration>
  <configSections>
    <section name="redirects" type="WebApplication4.Infrastructure.RedirectSection"/>
  </configSections>
  <appSettings>

The last thing we need to do is create our actual HTTP Module.  This part is pretty simple.  Add another class to your Redirects folder called RedirectModule and make sure it implements the IHttpModule interface.

public class RedirectModule : IHttpModule
    {
        private  HttpApplication _context;

        public void Dispose()
        {
            
        }

        public void Init(HttpApplication context)
        {
            _context = context;
            context.PostResolveRequestCache += Redirect;
        }

        public void Redirect(object src, EventArgs args)
        {
            RedirectSection section = (RedirectSection)WebConfigurationManager.GetWebApplicationSection("redirects");
            foreach(Redirect redirect in section.Redirects){
                if (redirect.Old == _context.Request.RequestContext.HttpContext.Request.RawUrl)
                {
                    _context.Response.Redirect(redirect.New);
                }
            }

        }
    }

All HttpModules implement the Init method, which is called when our module is registered.  We want to map an event handler to the HttpApplication’s PostResolveRequestCache method, which runs before ASP.NET maps the request to the MVC handler and begins the whole framework processing path.  The code here is fairly straight forward.  We ask the static WebConfigurationManager class for our redirects section and then access the Redirects property.  This gives us a collection of the redirects we added in our webconfig.  We can then loop through all of the redirects, and if the url of the current request matches any of them, it will redirect to the value of the new url!

The last step is to register our module in the web config like we would any other module.

  <system.webServer>
    <modules>
      <add name="MyModule" type="WebApplication4.Infrastructure.RedirectModule"/>
    </modules>
  </system.webServer>

If you run your application and navigate to either the root url (“/”)  or “/home/about”, you will now be redirect to the contact page!  Also, if you want to prove this is all happening without the MVC framework, place a break point in your module and a second break point in your About action method – the first break point in your module should hit but the about method won’t.

Again, the main benefit here is that we were able to entirely skip the MVC framework pipeline, such as locating our controller factory and activating various action methods, building an ActionResult, and other steps.  This is more performant, and also allows us to add and remove redirects in the web.config file without having to recompile our code.  I hope you see the benefits!

0 Shares:
You May Also Like