1

I've a ASP.NET MVC application in which I've implemeneted custom caching filter as below code:

public class NonAuthenticatedOnlyCacheAttribute : OutputCacheAttribute
{
    public NonAuthenticatedOnlyCacheAttribute()
    {
        Duration = 600;  /*default cache time*/
    }

    private bool _partialView;
    public bool PartialView
    {
        get { return _partialView; }
        set
        {
            _partialView = value;
            if (_partialView)
            {
                VaryByCustom = "user";
            }
        }
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (PartialView) OnCachePartialEnabled(filterContext);
        else OnCacheEnabled(filterContext);

        base.OnResultExecuting(filterContext);
    }

    private OutputCacheLocation? originalLocation;
    private int? _prevDuration;

    protected void OnCachePartialEnabled(ResultExecutingContext filterContext)
    {
        var httpContext = filterContext.HttpContext;

        if (!_prevDuration.HasValue) _prevDuration = Duration;
        Duration = httpContext.User.Identity.IsAuthenticated ? 1 : _prevDuration.Value;
    }

    protected void OnCacheEnabled(ResultExecutingContext filterContext)
    {
        var httpContext = filterContext.HttpContext;

        if (httpContext.User.Identity.IsAuthenticated)
        {
            // it's crucial not to cache Authenticated content
            originalLocation = originalLocation ?? Location;
            Location = OutputCacheLocation.None;
        }
        else
        {
            Location = originalLocation ?? Location;
        }

        // this smells a little but it works
        httpContext.Response.Cache.AddValidationCallback(IgnoreAuthenticated, null);
    }

    // This method is called each time when cached page is going to be
    // served and ensures that cache is ignored for authenticated users.
    private void IgnoreAuthenticated(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        validationStatus = context.User.Identity.IsAuthenticated
          ? HttpValidationStatus.IgnoreThisRequest
          : HttpValidationStatus.Valid;
    }
}

The problem I'm facing is, even though I'm not caching the page for authenticated user, sometimes I'm seeing a cached page of some other authenticated user. This happens rarely and randomly.

If I debug through this, it works perfectly fine but not when it occurs on LIVE site. What I'm also seeing is, a cookie (".AspNet.ApplicationCookie") of the authenticated user gets created in browser as well. (Does it mean that output cache is also caching response cookies as well?) If I delete this cookie then user gets logged out which is obvious.

In Global.asax, I have following code:

public override string GetVaryByCustomString(HttpContext context, string value)
    {
        if (value.Equals("culture") || value.Equals("user"))
        {
            var customString = Thread.CurrentThread.CurrentUICulture.Name;
            if (context.User.Identity.IsAuthenticated)
            {
                customString = $"{context.User.Identity.Name}-{customString}";
            }
            return customString;
        }
        return base.GetVaryByCustomString(context, value);
    }

And on controller, below attribute is cached

[NonAuthenticatedOnlyCacheAttribute(Location = System.Web.UI.OutputCacheLocation.Server, Duration = 600, VaryByCustom = "user")]
Hiren Desai
  • 941
  • 1
  • 9
  • 33

1 Answers1

1

I think the problem may be that you did not tell ASP.NET what you mean with:

VaryByCustom = "user"

Maybe you did that, but the code is not included in your post. In your Global.asax.cs you will have something like:

public override string GetVaryByCustomString(HttpContext context, string arg) 
{ 
    if (arg.Equals("User", StringComparison.InvariantCultureIgnoreCase)) 
    {
        var user = context.User.Identity.Name; // TODO here you have to pick an unique identifier from the current user identity
        return string.Format("{0}@{1}", userIdentifier.Name, userIdentifier.Container); 
    }

    return base.GetVaryByCustomString(context, arg); 
}

You can find more information on this here and here.

johey
  • 1,139
  • 1
  • 9
  • 25
  • The code in your global.asax.cs to take into account the user seems incorrect. You return "Thread.CurrentThread.CurrentUICulture.Name" which is the culture code. I think you need to return something that identifies the user, as in the above code example and as explained in the articles I referred to. – johey Jan 29 '20 at 08:22
  • I'll give it a try and confirm. I think I understood your point though! – Hiren Desai Feb 01 '20 at 11:14
  • I've implemented code and moved to production. I shall wait for few more days for this problem to reoccur otherwise I'll mark the above one as Correct answer. :) – Hiren Desai Feb 10 '20 at 08:47
  • Cool. Good luck! – johey Feb 10 '20 at 10:33
  • userIdentifier uses a deprecated package Microsoft.Identitymodel.Clients.ActiveDirectory – Kurkula Feb 08 '23 at 02:55