Firefox Doesn't Include Custom Request Headers With 302 Redirects.

Tonight I stayed up way too late trying to write a fix for an issue that plagued this website. CodeTunnel uses Progressive Enhancement which means that the site functions in the lowest of browsers, but then is slowly enhanced via CSS and Javascript if you use the site in newer browsers. Well javascript on CodeTunnel manually overrides many of the links on the site, does a cool animation, and loads the linked content via AJAX.

The Problem

The problem with this is that some of the links hit action methods on the server that issue a 302 redirect to a different action method. This works fine in normal page requests, but with jQuery AJAX a custom request header is sent with the request to identify that the request was made via AJAX. This is what enables you to use Request.IsAjaxRequest() on the server-side. In FireFox there is a bug that prevents the browser from including this custom request header in the redirect. You can see why this might be very annoying.

On CodeTunnel I check if the request is AJAX so that I can send back a full page of HTML or only a partial page. If this header is missing then I cannot tell if the request was made via AJAX and full pages are returned which completely screws up the interface.

The Fix

I managed to fix it with a simple hack. Here is the code:

public class BaseController : Controller
{
    private string _headerValue = "X-Requested-With";

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        var ajaxHeader = TempData[_headerValue] as string;
        if (!Request.IsAjaxRequest() && ajaxHeader != null)
            Request.Headers.Add(_headerValue, ajaxHeader);
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        if (Request.IsAjaxRequest() && IsRedirectResult(filterContext.Result))
            TempData[_headerValue] = Request.Headers[_headerValue];
    }

    private bool IsRedirectResult(ActionResult result)
    {
        return result.GetType().Name.ToLower().Contains("redirect");
    }
}

In your MVC application you will have to make your controllers inherit from this BaseController in order for this to work.

This code detects if a redirect was just issued. If it was then it adds a flag to the TempData dictionary. On the next request the controller will check TempData to see if there is a value. If there is a value then it knows the last request was an AJAX request even if the request header wasn't included. It manually adds the request header to the server's Request object. This enables future calls to Request.IsAjaxRequest() to function properly.

I was glad the fix was relatively simple, but this is still a hack. One problem is that it uses TempData which essentially uses Session to store it's values. Session is volatile storage, meaning that the contents could be erased at any time if the server needs to free up memory. This likely won't cause much of a headache but it's something to think about.

For more details see the question where I posted my answer.