8

I have an ajax call to MVC which returns a partialview. This is all fine until the session ends or the cookie expires. When I make the ajax call it displays the content inside a div that was meant to be for the partialview. How can I detect that my session has expired during the ajax call and redirect properly to a full screen/page

Arcadian
  • 4,312
  • 12
  • 64
  • 107

3 Answers3

5

I would recommend encapsulating all your requests into a wrapper element:

public class JsonResponse<T>
{
    public JsonResponse()
    {
    }

    public JsonResponse(T Data)
    {
        this.Data = Data;
    }

    public T Data { get; set; }
    public bool IsValid { get; set; }
    public string RedirectTo { get; set; }
}

What model you want to send to your client is in Data.

To get the RedirectTo populated, I use a GlobalAuthorize attribute in the Global.Asax and added a handle for HandleUnauthorizedRequests.

public sealed class GlobalAuthorize : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest
      (AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new JsonResult
            {
                Data = new JsonResponse<bool>
                       {
                           IsValid = false,
                           //RedirectTo = FormsAuthentication.LoginUrl
                           RedirectTo = "/"
                       },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    } 

Additionally, I've encapsulated all my Ajax requests into a single function which checks for the RedirectTo.

function global_getJsonResult(Controller, View, data, successCallback, completeCallback, methodType)
{
    if (IsString(Controller)
        && IsString(View)
        && !IsUndefinedOrNull(data))
    {
        var ajaxData;
        var ajaxType;

        if (typeof (data) == "string")
        {
            ajaxData = data;
            ajaxType = "application/x-www-form-urlencoded"
        }
        else
        {
            ajaxData = JSON.stringify(data);
            ajaxType = "application/json; charset=utf-8";
        }
        var method = 'POST';

        if (!IsUndefinedOrNull(methodType)) 
        {
            method = methodType;
        }

        var jqXHR = $.ajax({
            url: '/' + Controller + '/' + View,
            data: ajaxData,
            type: method,
            contentType: ajaxType,
            success: function(jsonResult)
            {
                if (!IsUndefinedOrNull(jsonResult)
                    && jsonResult.hasOwnProperty("RedirectTo")
                    && !IsUndefinedOrNull(jsonResult.RedirectTo)
                    && jsonResult.RedirectTo.length > 0)
                {
                    $.fn.notify('error', 'Login Expired', 'You have been inactive for a prolonged period of time, and have been logged out of the system.');
                    window.setTimeout(function() { window.location = jsonResult.RedirectTo }, 5000);
                }
                else if (IsFunction(successCallback))
                {
                    successCallback(jsonResult, Controller + '/' + View);
                }
            },
            error: function(jqXHR, textStatus, errorThrown)
            {
                if (errorThrown != 'abort')
                {
                    $.fn.notify('error', 'AJAX Connection Error', textStatus + ': ' + errorThrown);
                }

            },
            complete: function(jqXHR, textStatus)
            {
                if (IsFunction(completeCallback))
                {
                    completeCallback(jqXHR, textStatus, Controller + '/' + View);
                }
            }
        });

        return jqXHR;
    }
}
Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • I tried to using your way, my HandleUnauthorizedRequest works perfectly, but it never comes in a global_getJsonResult() method, can you give me an example of how you encapsulated your jquery method? – Bharat Oct 02 '17 at 05:48
  • Not sure what you mean. The example above has a method that encapsulates ajax requests and all requests use that method in my JS code. – Erik Philips Oct 02 '17 at 15:41
4

You could create a timer on the client with javascript that will show a dialog to the user when the session has timed out. You would just set the timer's value to whatever your session time out. Then on ajax request, it will reset the count down as well.

var g_sessionTimer = null;
function uiSessionInit() {
    id = "uiTimeout";
    timeout = 3600000 * 24; // 1 day timeout
    uiSessionSchedulePrompt(id, timeout);
    $('body').ajaxStart(function () {
        // reset timer on ajax request
        uiSessionSchedulePrompt(id, timeout);
    });
}
function uiSessionSchedulePrompt(id, timeout) {
    if (g_sessionTimer)
        clearTimeout(g_sessionTimer);
    g_sessionTimer = setTimeout(function () { uiSessionExpiring(id); }, timeout);
}
function uiSessionExpiring(id) {
    // create a dialog div and use this to show to the user
    var dialog = $('<div id="uiTimeout"></div>').text("Your session with has timed out. Please login again.");
    $('body').append(dialog);
    $('#uiTimeout').dialog({ 
           autoOpen: true, 
           modal: true, 
           title: 'Expired Session', 
           buttons: { 
                "OK": function(){
                    $(this).dialog('close'); 
                }
           },
           close: uiSessionDialogClose
     });
}

function uiSessionDialogClose(){
    // take user to sign in location
    location = 'http://www.mypage.com'; 
}
Gabe
  • 49,577
  • 28
  • 142
  • 181
0

It's been 8 years but I've just encountered a similar problem whereby an Ajax call to load a partial view into a modal, made after the user's authentication cookie had expired, would result in the full login page being loaded into the modal. This is how I got around it.

Firstly, I added a hidden control on my login page. The value isn't strictly required but it makes for more readable markup, I think:

<input id="isLoginPage" type="hidden" value="true" />

Then I modified the Ajax call like this. I'm using jQuery but the same principle will work in plain old Javascript too:

$.ajax({
    url: `/mypartialview`,
    type: "GET",
    success: function (data) {
        var $modal = $("#myModal");
        $modal.find(".modal-content").html(data);
        if ($modal.find("#isLoginPage").length > 0) {
            window.location.replace("/");
        }
        $modal.modal("show");
    }
});

This works on the basis that if the user's authentication has expired MVC's default behaviour is to return to the login page, so I "sniff" the view returned by the Ajax call for the isLoginPage hidden input and, if it's found, I know that the login page has been returned so I just redirect the user to the root of the application, which MVC will then redirect to the main login page.

If your application's root allows anonymous access you could replace window.location.replace("/") with the path to your application's login page, e.g. window.location.replace("/account/login").

With this working, I encapsulated the solution in a function to simplify things and avoid repeating myself:

function handleAjaxData($container, data) {
    $container.html(data);
    if ($container.find("#isLoginPage").length > 0) {
        window.location.replace("/");
    }
}

Which I can then use in my Ajax call like this:

$.ajax({
    url: `/mypartialview`,
    type: "GET",
    success: function (data) {
        var $modal = $("#myModal");
        handleAjaxData($modal.find(".modal-content"), data);
        $modal.modal("show");
    }
});

You could obviously go further and include the Ajax call itself in the function, but in my case that would have involved some complicated callbacks so I didn't bother.

Philip Stratford
  • 4,513
  • 4
  • 45
  • 71