Search code examples
c#asp.netc#-4.0outputcache

Output cache code behind for non session users


Can I do output caching in pages that I can detect in code behind if Session["user"] is null or not, and cache the page for 5 minutes if it is not cached yet (output caching not expired) and and the Session["user"] == null.

I don't want to use custom output cache because it will also cache for log in users. This way only anonymous visitors will see a cached, page, and logged in users will get the non cached ones

I do this because some of my pages looks different when the user is logged in or not. I want crawlers and regular visitors to see the original page, not getting a private data from a user that triggered the caching by mistake.

How can I do this in code behind C#?


Solution

  • You should be able to create a custom OutputCacheProvider.

    In your global.asax:

        public override string GetOutputCacheProviderName(HttpContext context)
        {
            bool isInSession = true; // Determine here.
            if (isInSession)
            {
                return "CustomProvider";
            }
    
            // Or by page:
            if (context.Request.Path.EndsWith("MyPage.aspx"))
            {
                return "SomeOtherProvider";
            }
    
            return base.GetOutputCacheProviderName(context);
        }
    

    Then create your provider:

    public class SessionBasedCacheProvider : OutputCacheProvider
    {
        public override object Get(string key)
        {
            return null; // Do not cache.
        }
    
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            // Basically let it "fall through" since we don't want any caching.
            return entry;
        }
    
        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            // Basically let it "fall through" since we don't want any caching.            
        }
    
        public override void Remove(string key)
        {
            // Basically let it "fall through" since we don't want any caching.
        }
    }
    

    By returning null from Set, you will not cache the items.

    The key value that identifies the specified entry in the cache, or null if the specified entry is not in the cache.

    And add your provider to the config:

      <system.web>
        <caching>
          <outputCache defaultProvider="AspNetInternalProvider">
            <providers>
              <clear/>
              <add name="CustomProvider" type="YourNamespace.SessionBasedCacheProvider, YourNamespace, Version=1.0.0.0, Culture=neutral"/>
            </providers>
          </outputCache>
        </caching>
    </web>
    

    To enable it you should be able to use. Just set this to the top of the ASPX-page. Note the Location attribute, depending on where you want it to cache.

    <%@ OutputCache Duration="60" VaryByParam="None" Location="Server"  %>
    

    https://msdn.microsoft.com/en-us/library/hdxfb6cy(v=vs.85).aspx

    https://msdn.microsoft.com/en-us/magazine/gg650661.aspx

    Alternatively you can always use the same CacheProvider, and let it determine if the item should be cached or not.

    public class CustomOutputCacheProvider : OutputCacheProvider
    {
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            // Determine if in session.
            bool isInSession = true;
            if (isInSession)
            {
                return null;
            }
    
            // Do the same custom caching as you did in your 
            // CustomMemoryCache object
            var result = HttpContext.Current.Cache.Get(key);
    
            if (result != null)
            {
                return result;
            }
    
            HttpContext.Current.Cache.Add(key, entry, null, utcExpiry,
                System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
    
            return entry;
        }
    
        public override object Get(string key)
        {
            return HttpContext.Current.Cache.Get(key);
        }
    
        public override void Remove(string key)
        {
            HttpContext.Current.Cache.Remove(key);
        }
    
        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            HttpContext.Current.Cache.Add(key, entry, null, utcExpiry,
                System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
        }
    }
    

    Code from http://www.haneycodes.net/custom-output-caching-with-mvc3-and-net-4-0-done-right/

    Note, I've not tested this with Session, but in theory it should work. But I suggest that you really test it before releasing it. Caching is always much harder than one might think... :(

    UPDATE: Since Session isn't available you should be able to use a cookie instead.

    protected void Session_Start(object sender, EventArgs e)
    {
        Response.Cookies.Add(new HttpCookie("SessionCookie", "some_value"));
    }
    
    public override string GetOutputCacheProviderName(HttpContext context)
    {
        bool isInSession = true; // Determine here.
    
        if (context.Request.Cookies["SessionCookie"] != null)
        {
            // Use your CustomProvider
        }
    
        if (isInSession)
        {
            return "CustomProvider";
        }
    
        // Or by page:
        if (context.Request.Path.EndsWith("MyPage.aspx"))
        {
            return "SomeOtherProvider";
        }
    
        return base.GetOutputCacheProviderName(context);
    }
    

    Just remember to delete the cookie, or handle it in some way.