asp.net-mvccustom-attributesajax-requestexpired-sessions

Asp.net MVC - How to check session expire for Ajax request


We are using Ajax call across the application- trying to find out a global solution to redirect to login page if session is already expired while trying to execute any Ajax request. I have coded following solution taking help from this post - Handling session timeout in ajax calls

NOT SURE WHY IN MY CARE EVENT "HandleUnauthorizedRequest" DOES NOT GET FIRED.

Custom Attribute:

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class CheckSessionExpireAttribute :AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                var url = new UrlHelper(filterContext.RequestContext);
                var loginUrl = url.Content("/Default.aspx");

                filterContext.HttpContext.Session.RemoveAll();
                filterContext.HttpContext.Response.StatusCode = 403;
                filterContext.HttpContext.Response.Redirect(loginUrl, false);
                filterContext.Result = new EmptyResult();
            }
            else
            {
                base.HandleUnauthorizedRequest(filterContext);
            }

        }

    }

Using Above custom attribute as follow in controller action:

 [NoCache]
 [CheckSessionExpire]
 public ActionResult GetSomething()
 {
  }

AJAX Call(JS part):

function GetSomething()
{
   $.ajax({
        cache: false,
        type: "GET",
        async: true,
        url: "/Customer/GetSomething",
        success: function (data) {

        },
        error: function (xhr, ajaxOptions, thrownError) {

        }
}

Web Config Authentication settings:

  <authentication mode="Forms">
      <forms loginUrl="default.aspx" protection="All" timeout="3000" slidingExpiration="true" />
    </authentication>

I am try to check it by deleting browser cooking before making ajax call but event "CheckSessionExpireAttribute " does not get fired- any idea please.

Thanks,

@Paul


Solution

  • If I got the question right (and even if I didn't, thanks anyway, helped me solve my own situation), what you wanted to avoid was having your login page to load inside an element which was supposed to display a different View via Ajax. That or get an exception/error status code during a Ajax form post.

    So, in short, the annotation class will need to override 2 methods, not just HandleUnauthorizedRequest, and it will redirect to a JsonResult Action that will generate the parameters for your Ajax function to know what to do.

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class SessionTimeoutAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            IPrincipal user = filterContext.HttpContext.User;
            base.OnAuthorization(filterContext);
            if (!user.Identity.IsAuthenticated) {
                HandleUnauthorizedRequest(filterContext);
            }
        }
    
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.Result = new RedirectToRouteResult(new
                RouteValueDictionary(new { controller = "AccountController", action = "Timeout" }));
            }
        }
    }
    

    Then set this annotation in your authentication Action, so every time it gets called, it will know where the request came from, and what kind of return it should give.

    [AllowAnonymous]
    [SessionTimeout]
    public ActionResult Login() { }
    

    Then your redirected Json Action:

    [AllowAnonymous]
    public JsonResult Timeout()
    {
        // For you to display an error message when the login page is loaded, in case you want it
        TempData["hasError"] = true;
        TempData["errorMessage"] = "Your session expired, please log-in again.";
    
        return Json(new
        {
            @timeout = true,
            url = Url.Content("~/AccountController/Login")
        }, JsonRequestBehavior.AllowGet);
    }
    

    Then in your client function (I took the privilege of writing it as $.get() instead of $.ajax():

    $(document).ready(function () {
        $("[data-ajax-render-html]").each(function () {
            var partial = $(this).attr("data-ajax-render-html");
            var obj = $(this);
    
            $.get(partial, function (data) {
                if (data.timeout) {
                    window.location.href = data.url;
                } else {
                    obj.replaceWith(data);
                }
            }).fail(function () {
                obj.replaceWith("Error: It wasn't possible to load the element");
            });
        });
    });
    

    This function replaces the html tag with this data-ajax-render-html attribute, which contains the View address you want to load, but you can set it to be loaded inside the tag by changing replaceWith for the html() property.