A while back I posted an code review on handling ajax and redirects when a server side error occurs.Wrapper for jquery ajax to ensure redirects occur on clientsideSince that post I've been using this module extensively (in MVC.net projects) and it works really well in conjuction with some serverside code to help implement the redirects (especially unauthorized).  I thought I would re-post to get some feedback and help improve as it has changed a wee bit.BackgroundI often had issues with ajax requests when server side errors occurred, or especially when the user was logged out and an Ajax event fired.  I would eitherget the resultant login page in my view (if I was rendering to the view) or I might not get anything.  Hence I wanted a way that I would be redirected if this eventoccurs much like it does on the serverside already when a user is not authenticated.SetupIn my latest MVC project I've used it by:Adding via nuget the AspNetHaack projectAdding global filter to supply redirect URLs back to the JavaScript (HttpAjaxRedirectFilter)Adding a spinner icon for the spinner version (ajaxWithSpinner)The javascript itself (ajaxWithRedirect and subsequent wrappers)Some css to work with the spinner optionServerside codepublic class HttpAjaxRedirectFilter : IActionFilter{    public void OnActionExecuting(ActionExecutingContext filterContext)    {        var modelState = filterContext.Controller.ViewData.ModelState;        if (!modelState.IsValid            && IsPost(filterContext.RequestContext.HttpContext)            && filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())        {            // If the model state is not valid for ajax requests then we don't even want to carry on with the action. In theory all of our forms            // should be using clientside validation so it should all be caught there so any posts that make it through are via some other mechanism            filterContext.HttpContext.Response.StatusCode = 400;            filterContext.Result = NewtonSoftJson(new            {                Message = The data supplied was invalid,                Data = modelState            });        }    }    public void OnActionExecuted(ActionExecutedContext filterContext)    {        var redirectResult = filterContext.Result as RedirectToRouteResult;        if (redirectResult != null &&            !filterContext.Canceled &&            filterContext.HttpContext.Request.IsAjaxRequest())        {            var values = redirectResult.RouteValues.Values.ToList();            string actionName = values[0] as string;            string controllerName = values[1] as string;            UrlHelper urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext);            filterContext.Result = JsonRedirectToAction(                                        urlHelper,                                         actionName,                                         controllerName,                                         redirectResult.RouteValues);        }    }    protected bool IsPost(HttpContextBase httpContext)    {        return httpContext.Request.HttpMethod == POST;    }    protected JsonResult NewtonSoftJson(object obj, JsonRequestBehavior behaviour = JsonRequestBehavior.AllowGet)    {        return new NetwonSoftJsonActionResult        {            Data = obj,            JsonRequestBehavior = behaviour        };    }    protected JsonResult JsonRedirectToAction(                             UrlHelper urlHelper,                              string actionName,                              string controllerName,                              RouteValueDictionary routeValues)    {        return NewtonSoftJson(new        {            RedirectUrl = urlHelper.Action(actionName, controllerName, routeValues)        });    }}public class NetwonSoftJsonActionResult : System.Web.Mvc.JsonResult{    public NetwonSoftJsonActionResult()    {        JsonRequestBehavior = JsonRequestBehavior.AllowGet;    }    public NetwonSoftJsonActionResult(object data)        : this()    {        Data = data;    }    public override void ExecuteResult(System.Web.Mvc.ControllerContext context)    {        if (context == null)        {            throw new ArgumentNullException(context);        }        if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&            String.Equals(context.HttpContext.Request.HttpMethod,                GET, StringComparison.OrdinalIgnoreCase))        {            throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);        }        HttpResponseBase response = context.HttpContext.Response;        if (!String.IsNullOrEmpty(ContentType))        {            response.ContentType = ContentType;        }        else        {            response.ContentType = application/json;        }        if (ContentEncoding != null)        {            response.ContentEncoding = ContentEncoding;        }        if (Data != null)        {            var serializer = new Newtonsoft.Json.JsonSerializer();            var txr = new Newtonsoft.Json.JsonTextWriter(context.HttpContext.Response.Output);            {                serializer.Serialize(txr, this.Data);            }        }    }}Clientside module// base namespacevar site = site || {};var Domain = Domain || { HomeUrl: / };if (typeof String.prototype.startsWith != 'function') {    // see below for better implementation!    String.prototype.startsWith = function (str) {        return this.indexOf(str) == 0;    };}site.core = {    toggleOverlay: function ($element, overlayId, showOverlay) {        showOverlay ? this.addOverlay($element, overlayId) : this.removeOverlay(# + overlayId);    },    addSpinnerOverlay: function ($element, overlayId) {        // If the overlay already exists we don't need to re-add it        if ($element.children(overlayId).length) return;        var $overlay = this.addOverlay($element, overlayId);        $overlay.html('<div class=spinner >');    },    addOverlay: function ($element, overlayId) {        var pos = $.extend({            width: $element.outerWidth(),            height: $element.outerHeight()        }, $element.position());        var $overlay = $('<div>', {            id: overlayId,            css: {                position: 'absolute',                top: pos.top,                left: pos.left,                width: $element.outerWidth(),                height: $element.outerHeight(),                backgroundColor: '#fff',                opacity: 0.50            }        });        $overlay.appendTo($element);        return $overlay;    },    removeOverlay: function (overlayId) {        $(overlayId).remove();    },    redirect: function (url) {        window.location = url;    },};$.extend({    // $.ajaxWithRedirect    //    // Provides a transparent implemention of the jQuery $.ajax method with the additional alternative of being    // able to automatically redirect when a Json object is returned with the RedirectUrl parameter.    //    // If the caller wants to perform or cancel the redirect they can provide a beforeRedirect function    // and return false.            ajaxWithRedirect: function (options) {        use strict;        var deferred = $.Deferred(), successHandler, errorHandler, xhr;        // force no-cache        options.cache = false;        // get a copy of the success handler...        successHandler = options.success;        errorHandler = options.error;        options.error = function (data) {            //supress the callers error callback, since the options.statusCode below will redirect anyway, or, a status of zero indicates the window is navigating away to another page.            if (data.status == 0 || data.status == 401 || options.status == 403) {                return;            }            if (typeof errorHandler === 'function') {                errorHandler.apply(null, arguments);                deferred.resolve.apply(null, arguments);            } else {                alert('Unfortunately something went wrong.  Please contact us for more information.');            }        };        // ... and replace it with this one        options.success = function (data) {            var contentType = xhr.getResponseHeader(Content-Type),                performRedirect = true,                redirectUrl = null,                args = [].slice.call(arguments, 0) || {},                json;            // If response isn't there or isn't json,            // skip all the redirect logic            if (data && (/json/i).test(contentType)) {                // If json was requested, and json received, jQuery will                // have parsed it already. Otherwise, we'll have to do it                if (options.dataType === 'json' || (data && data.RedirectUrl)) {                    redirectUrl = data.RedirectUrl;                } else {                    try {                        json = $.parseJSON(data);                        redirectUrl = json.RedirectUrl;                    } catch (e) {                        // not json                    }                }                // check the redirect url                if (redirectUrl && typeof redirectUrl === 'string') {                    // Is there a beforeRedirect handler?                    if (typeof options.beforeRedirect === 'function') {                        // pass all the arguments to the beforeRedirect handler                        performRedirect = options.beforeRedirect.apply(null, args);                    }                    // unless strictly false, go ahead with the redirect                    if (performRedirect !== false) {                        site.core.redirect(redirectUrl);                        // and stop here. No success and/or deferred handlers                        // will be called since we're redirecting anyway                        return;                    }                }            }            // no redirect; forward everything to the success handler(s)            if (typeof successHandler === 'function') {                successHandler.apply(null, args);                deferred.resolve.apply(null, args);            }        };        // Handling when the user is currently not authorized but they have tried to implement an ajax related action        options.statusCode =        {            401: function (data) {                var redirectUrl = (data && data.responseText && data.responseText.length > 0) ? data.responseText : Domain.HomeUrl;                site.core.redirect(redirectUrl);            },            403: function (data) {                site.core.redirect(/);            }        };        // Make the request        xhr = $.ajax(options);        // Forward the deferred promise method(s)        xhr.fail(deferred.reject);        xhr.progress(deferred.notify);        // Replace the ones already on the xhr obj        deferred.promise(xhr);        return xhr;    },    // $.ajaxUnobtrusive    //    // Uses the ajaxWithRedirect to provide a transparent interface by which we can make ajax    // requests and have any returned html automatically ensure the elements are bound using jQuery validation.    //    // Otherwise the elements will not validate when they are added to the dom    //    // returns the xhr provided by the underlying $.ajax method    ajaxUnobtrusive: function (options) {        use strict;        // copy the actual success handler that has been defined        var successHandler = options.success;        options.success = function () {            // forward to event success handler prior to validation to ensure            // any html is loaded as necessary            if (typeof successHandler === 'function') {                successHandler.apply(null, [].slice.call(arguments, 0));                // rebind the newly loaded validation elements                       if (typeof options.dynamicValidation != 'undefined') {                    // NOTE:  This method is defined in another file that extends validator                    $.validator.unobtrusive.parseDynamicContent(options.dynamicValidation);                }            }        };        // fall through to our redirect ajax method        return $.ajaxWithRedirect(options);    },    ajaxLoad: function (element$, options) {        var successHandler;        successHandler = options.success;        options.success = function (html) {            element$.html(html);            if (typeof successHandler === 'function') {                successHandler.apply(null, arguments);            }        };        $.ajaxWithSpinner(options);    },    ajaxWithSpinner: function (options) {        //var dataType = options.dataType || 'html';        var containerid = options.spinnerOverlay || 'body';        var overlayid = containerid.startsWith('.') || containerid.startsWith('#') ? containerid.substring(1) : containerid;        //var type = options.type || 'GET';        var completeHandler = options.complete;        var successHandler = options.success;        var beforeSendHandler = options.beforeSend;        options.type = options.type || 'GET';        options.dataType = options.dataType || 'html';        options.traditional = options.traditional || false;        options.success = function (data) {            if (typeof successHandler === 'function') {                successHandler.apply(null, arguments);            }        };        options.complete = function () {            if (containerid) {                site.core.removeOverlay('#' + overlayid + '-spinner-overlay');            }            if (typeof completeHandler === 'function') {                completeHandler.apply(null, arguments);            }        };        options.beforeSend = function () {            if (containerid) {                site.core.addSpinnerOverlay($(containerid), overlayid + '-spinner-overlay');            }            if (typeof beforeSendHandler === 'function') {                beforeSendHandler.beforeSend();            }        };        return $.ajaxUnobtrusive(options);    },    ///    /// jQuery plugin for stopping cross-site request forgery (CSRF)    ///    getAntiForgeryToken: function (tokenWindow, appPath) {        // HtmlHelper.AntiForgeryToken() must be invoked to print the token.        tokenWindow = tokenWindow && typeof tokenWindow === typeof window ? tokenWindow : window;        appPath = appPath && typeof appPath === string ? _ + appPath.toString() : ;        // The name attribute is either __RequestVerificationToken,        // or __RequestVerificationToken_{appPath}.        var tokenName = __RequestVerificationToken + appPath;        // Finds the <input type=hidden name={tokenName} value=... /> from the specified window.            var inputElements = tokenWindow.document.getElementsByTagName(input);        for (var i = 0; i < inputElements.length; i++) {            var inputElement = inputElements[i];            if (inputElement.type === hidden && inputElement.name === tokenName) {                return {                    name: tokenName,                    value: inputElement.value                };            }        }        return null;    },    appendAntiForgeryToken: function (settings) {        // Gets token from current window by default.        var token = $.getAntiForgeryToken();        if (token) {            var headers = {};            headers[token.name] = token.value;            settings.headers = headers;        }        return settings;    },    // Wraps $.post(url, data, callback, type) for most common scenarios.    postAntiForgery: function (options) {        return $.ajaxWithSpinner($.appendAntiForgeryToken(options));    }});CSS.spinner {    background: url(../Images/waiting.gif) no-repeat center;    bottom: 0;    left: 0;    margin: auto;    position: absolute;    right: 0;    top: 0;}
Wrapper for jQuery Ajax to ensure redirects occur on clientside - Round 2
c#;javascript;jquery;css;asp.net mvc