Cookie Value OutputCache VaryByCustom

Is there a way to set the OutputCache value based on the cookie value?

For simplicity, this is my method.

[OutputCache(Duration = 600, VaryByParam = "None", VaryByCustom = "ztest")] public ViewResult Index() { return View(); } 

My Global.asax has this (to override the GetVaryByCustomString method

 public override string GetVaryByCustomString(HttpContext context, string custom) { if (custom == "ztest") { HttpCookie ztest = context.Request.Cookies["ztest"]; if (ztest != null) { return ztest.Value; } } return base.GetVaryByCustomString(context, custom); } 

I can verify that my browser has a ztest cookie, but when I debug the Index method, I hit a breakpoint every time (this means the cache is not working).

HttpResponse does not have outgoing cookies, so this item does not apply: https://msdn.microsoft.com/en-us/library/system.web.httpcookie.shareable(v=vs.110).aspx

If this HttpResponse contains one or more outgoing cookies for which the Shareable parameter is set to false (the default value), output caching will be suppressed for response. This prevents the caching of cookies that contain potentially sensitive information and is sent to multiple clients. To enable caching of a response containing cookies, usually configure caching for the response, for example, using the OutputCache directive or the MVC [OutputCache] attribute, and set all outgoing cookies to Shareable to true.

+7
c # caching cookies outputcache
source share
1 answer

The subtle answer is no.

The explained answer is as follows:

The reason that the output cache does not play good results with cookies

Therefore, the reason the output cache will not cache the response with cookies is because the cookie may be user-specific (e.g. authentication, analytic tracking, etc.). If one or more cookies with the property HttpCookie.Shareable = false , then the output cache considers the answer incompatible.

Decision:

There are several workarounds. The output cache caches response headers and content together and does not provide any intercepts to modify them before sending them back to the user. However, it is possible to provide the ability to modify the cached response headers before they are sent back to the user. One of them requires the Fasterflect nuget package.

I have an example code:

 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; using System.Web; using System.Web.Caching; using Fasterflect; namespace CustomOutputCache { /// <summary> /// An output cache provider that has ability to modify the http header collection before a cached response is served back to the user. /// </summary> public class HeaderModOutputCacheProvider : OutputCacheProvider { private static readonly Type OutputCacheEntryType, HttpCachePolicySettingsType; private static readonly Type[] ParameterTypes; public static event EventHandler<CachedRequestEventArgs> RequestServedFromCache; static HeaderModOutputCacheProvider() { var systemWeb = typeof(HttpContext).Assembly; OutputCacheEntryType = systemWeb.GetType("System.Web.Caching.OutputCacheEntry"); HttpCachePolicySettingsType = systemWeb.GetType("System.Web.HttpCachePolicySettings"); ParameterTypes = new[]{ typeof(Guid), HttpCachePolicySettingsType, typeof(string), typeof(string) , typeof(string[]), typeof(int), typeof(string), typeof(List<HeaderElement>), typeof(List<ResponseElement>) }; } private readonly ObjectCache _objectCache; public HeaderModOutputCacheProvider() { _objectCache = new MemoryCache("output-cache"); } #region OutputCacheProvider implementation public override object Get(string key) { var cachedValue = _objectCache.Get(key); if (cachedValue == null) return null; if (cachedValue.GetType() != OutputCacheEntryType) return cachedValue; var cloned = CloneOutputCacheEntry(cachedValue); if (RequestServedFromCache != null) { var args = new CachedRequestEventArgs(cloned.HeaderElements); RequestServedFromCache(this, args); } return cloned; } public override object Add(string key, object entry, DateTime utcExpiry) { _objectCache.Set(key, entry, new CacheItemPolicy { AbsoluteExpiration = utcExpiry }); return entry; } public override void Set(string key, object entry, DateTime utcExpiry) { _objectCache.Set(key, entry, new CacheItemPolicy { AbsoluteExpiration = utcExpiry }); } public override void Remove(string key) { _objectCache.Remove(key); } #endregion private IOutputCacheEntry CloneOutputCacheEntry(object toClone) { var parameterValues = new[] { toClone.GetFieldValue("_cachedVaryId", Flags.InstancePrivate), toClone.GetFieldValue("_settings", Flags.InstancePrivate), toClone.GetFieldValue("_kernelCacheUrl", Flags.InstancePrivate), toClone.GetFieldValue("_dependenciesKey", Flags.InstancePrivate), toClone.GetFieldValue("_dependencies", Flags.InstancePrivate), toClone.GetFieldValue("_statusCode", Flags.InstancePrivate), toClone.GetFieldValue("_statusDescription", Flags.InstancePrivate), CloneHeaders((List<HeaderElement>)toClone.GetFieldValue("_headerElements", Flags.InstancePrivate)), toClone.GetFieldValue("_responseElements", Flags.InstancePrivate) }; return (IOutputCacheEntry)OutputCacheEntryType.CreateInstance( parameterTypes: ParameterTypes, parameters: parameterValues ); } private List<HeaderElement> CloneHeaders(List<HeaderElement> toClone) { return new List<HeaderElement>(toClone); } } public class CachedRequestEventArgs : EventArgs { public CachedRequestEventArgs(List<HeaderElement> headers) { Headers = headers; } public List<HeaderElement> Headers { get; private set; } public void AddCookies(HttpCookieCollection cookies) { foreach (var cookie in cookies.AllKeys.Select(c => cookies[c])) { //more reflection unpleasantness :( var header = cookie.CallMethod("GetSetCookieHeader", Flags.InstanceAnyVisibility, HttpContext.Current); Headers.Add(new HeaderElement((string)header.GetPropertyValue("Name"), (string)header.GetPropertyValue("Value"))); } } } } 

Connect it like this:

 <system.web> <caching> <outputCache defaultProvider="HeaderModOutputCacheProvider"> <providers> <add name="HeaderModOutputCacheProvider" type="CustomOutputCache.HeaderModOutputCacheProvider"/> </providers> </outputCache> </caching> </system.web> 

And use it like this:

 HeaderModOutputCacheProvider.RequestServedFromCache += RequestServedFromCache; HeaderModOutputCacheProvider.RequestServedFromCache += (sender, e) => { e.AddCookies(new HttpCookieCollection { new HttpCookie("key", "value") }); }; 

I don’t know if he answers your question, but I hope this indicates the right direction.

+4
source share

All Articles