public void OnResultExecuted(ResultExecutedContext filterContext) { var response = filterContext.HttpContext.Response; if (!_cacheControlStrategy.IsCacheable(filterContext.Result, response)) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // ignore error results from cache if (response.StatusCode != (int)HttpStatusCode.OK) { // Never cache non-200 responses. filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0)); _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // if the result of a POST is a Redirect, remove any Cache Item for this url // so that the redirected client gets a fresh result // also add a random token to the query string so that public cachers (IIS, proxies, ...) don't return cached content // i.e., Comment creation // ignore in admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } _workContext = _workContextAccessor.GetContext(); // ignore authenticated requests if (_workContext.CurrentUser != null) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // save the result only if the content can be intercepted if (_filter == null) return; // flush here to force the Filter to get the rendered content if (response.IsClientConnected) response.Flush(); var output = _filter.GetContents(response.ContentEncoding); if (String.IsNullOrWhiteSpace(output)) { return; } response.Filter = null; response.Write(output); // check if there is a specific rule not to cache the whole route var configurations = _cacheService.GetRouteConfigurations(); var route = filterContext.Controller.ControllerContext.RouteData.Route; var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route); var configuration = configurations.FirstOrDefault(c => c.RouteKey == key); // do not cache ? if (configuration != null && configuration.Duration == 0) { return; } // don't cache the result of a POST redirection as it could contain notifications if (_transformRedirect) { return; } // don't cache the result if there were some notifications var messagesZone = _workContextAccessor.GetContext(filterContext).Layout.Zones["Messages"]; var hasNotifications = messagesZone != null && ((IEnumerable<dynamic>)messagesZone).Any(); if (hasNotifications) { return; } // default duration of specific one ? var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration; if (cacheDuration <= 0) { return; } // include each of the content item ids as tags for the cache entry var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); if (filterContext.HttpContext.Request.Url == null) { return; } _cacheItem.ContentType = response.ContentType; _cacheItem.StatusCode = response.StatusCode; _cacheItem.CachedOnUtc = _now; _cacheItem.ValidFor = cacheDuration; _cacheItem.QueryString = filterContext.HttpContext.Request.Url.Query; _cacheItem.Output = output; _cacheItem.CacheKey = _cacheKey; _cacheItem.InvariantCacheKey = _invariantCacheKey; _cacheItem.Tenant = _shellSettings.Name; _cacheItem.Url = filterContext.HttpContext.Request.Url.AbsolutePath; _cacheItem.Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray(); Logger.Debug("Cache item added: " + _cacheItem.CacheKey); // remove old cache data _cacheService.RemoveByTag(_invariantCacheKey); // add data to cache _cacheStorageProvider.Set(_cacheKey, _cacheItem); // add to the tags index foreach (var tag in _cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } }
public void OnActionExecuting(ActionExecutingContext filterContext) { // apply OutputCacheAttribute logic if defined var actionAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(OutputCacheAttribute), true); var controllerAttributes = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(OutputCacheAttribute), true); var outputCacheAttribute = actionAttributes.Concat(controllerAttributes).Cast<OutputCacheAttribute>().FirstOrDefault(); if (outputCacheAttribute != null) { if (outputCacheAttribute.Duration <= 0 || outputCacheAttribute.NoStore) { Logger.Debug("Request ignored based on OutputCache attribute"); return; } } // saving the current datetime _now = _clock.UtcNow; // before executing an action, we check if a valid cached result is already // existing for this context (url, theme, culture, tenant) Logger.Debug("Request on: " + filterContext.RequestContext.HttpContext.Request.RawUrl); // don't cache POST requests if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) { Logger.Debug("Request ignored on POST"); return; } // don't cache the admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { Logger.Debug("Request ignored on Admin section"); return; } // ignore child actions, e.g. HomeController is using RenderAction() if (filterContext.IsChildAction) { Logger.Debug("Request ignored on Child actions"); return; } _workContext = _workContextAccessor.GetContext(); // don't return any cached content, or cache any content, if the user is authenticated if (_workContext.CurrentUser != null) { Logger.Debug("Request ignored on Authenticated user"); return; } // caches the default cache duration to prevent a query to the settings _cacheDuration = _cacheManager.Get("CacheSettingsPart.Duration", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultCacheDuration; } ); // caches the default max age duration to prevent a query to the settings _maxAge = _cacheManager.Get("CacheSettingsPart.MaxAge", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultMaxAge; } ); _varyQueryStringParameters = _cacheManager.Get("CacheSettingsPart.VaryQueryStringParameters", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); var varyQueryStringParameters = _workContext.CurrentSite.As<CacheSettingsPart>().VaryQueryStringParameters; return string.IsNullOrWhiteSpace(varyQueryStringParameters) ? null : varyQueryStringParameters.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } ); var varyRequestHeadersFromSettings = _cacheManager.Get("CacheSettingsPart.VaryRequestHeaders", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); var varyRequestHeaders = _workContext.CurrentSite.As<CacheSettingsPart>().VaryRequestHeaders; return string.IsNullOrWhiteSpace(varyRequestHeaders) ? null : varyRequestHeaders.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } ); _varyRequestHeaders = (varyRequestHeadersFromSettings == null) ? new HashSet<string>() : new HashSet<string>(varyRequestHeadersFromSettings); // different tenants with the same urls have different entries _varyRequestHeaders.Add("HOST"); // Set the Vary: Accept-Encoding response header. // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. // The correct version of the resource is delivered based on the client request header. // This is a good choice for applications that are singly homed and depend on public proxies for user locality. _varyRequestHeaders.Add("Accept-Encoding"); // caches the ignored urls to prevent a query to the settings _ignoredUrls = _cacheManager.Get("CacheSettingsPart.IgnoredUrls", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().IgnoredUrls; } ); // caches the culture setting _applyCulture = _cacheManager.Get("CacheSettingsPart.ApplyCulture", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().ApplyCulture; } ); // caches the debug mode _debugMode = _cacheManager.Get("CacheSettingsPart.DebugMode", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DebugMode; } ); // don't cache ignored url ? if (IsIgnoredUrl(filterContext.RequestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath, _ignoredUrls)) { return; } var queryString = filterContext.RequestContext.HttpContext.Request.QueryString; var requestHeaders = filterContext.RequestContext.HttpContext.Request.Headers; var parameters = new Dictionary<string, object>(filterContext.ActionParameters); foreach (var key in queryString.AllKeys) { if (key == null) continue; parameters[key] = queryString[key]; } foreach (var varyByRequestHeader in _varyRequestHeaders) { if (requestHeaders.AllKeys.Contains(varyByRequestHeader)) { parameters["HEADER:" + varyByRequestHeader] = requestHeaders[varyByRequestHeader]; } } // compute the cache key _cacheKey = ComputeCacheKey(filterContext, parameters); // create a tag which doesn't care about querystring _invariantCacheKey = ComputeCacheKey(filterContext, null); // don't retrieve cache content if refused // in this case the result of the action will update the current cached version if (filterContext.RequestContext.HttpContext.Request.Headers["Cache-Control"] != "no-cache") { // fetch cached data _cacheItem = _cacheStorageProvider.GetCacheItem(_cacheKey); if (_cacheItem == null) { Logger.Debug("Cached version not found"); } } else { Logger.Debug("Cache-Control = no-cache requested"); } var response = filterContext.HttpContext.Response; // render cached content if (_cacheItem != null) { Logger.Debug("Cache item found, expires on " + _cacheItem.ValidUntilUtc); var output = _cacheItem.Output; // adds some caching information to the output if requested if (_debugMode) { response.AddHeader("X-Cached-On", _cacheItem.CachedOnUtc.ToString("r")); response.AddHeader("X-Cached-Until", _cacheItem.ValidUntilUtc.ToString("r")); } // shorcut action execution filterContext.Result = new ContentResult { Content = output, ContentType = _cacheItem.ContentType }; response.StatusCode = _cacheItem.StatusCode; ApplyCacheControl(_cacheItem, response); return; } _cacheItem = new CacheItem(); // get contents ApplyCacheControl(_cacheItem, response); // no cache content available, intercept the execution results for caching _previousFilter = response.Filter; response.Filter = _filter = new CapturingResponseFilter(); }
public void OnActionExecuting(ActionExecutingContext filterContext) { // apply OutputCacheAttribute logic if defined var actionAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(OutputCacheAttribute), true); var controllerAttributes = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(OutputCacheAttribute), true); var outputCacheAttribute = actionAttributes.Concat(controllerAttributes).Cast <OutputCacheAttribute>().FirstOrDefault(); if (outputCacheAttribute != null) { if (outputCacheAttribute.Duration <= 0 || outputCacheAttribute.NoStore) { Logger.Debug("Request ignored based on OutputCache attribute"); return; } } // saving the current datetime _now = _clock.UtcNow; // before executing an action, we check if a valid cached result is already // existing for this context (url, theme, culture, tenant) Logger.Debug("Request on: " + filterContext.RequestContext.HttpContext.Request.RawUrl); // don't cache POST requests if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) { Logger.Debug("Request ignored on POST"); return; } // don't cache the admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { Logger.Debug("Request ignored on Admin section"); return; } // ignore child actions, e.g. HomeController is using RenderAction() if (filterContext.IsChildAction) { Logger.Debug("Request ignored on Child actions"); return; } _workContext = _workContextAccessor.GetContext(); // don't return any cached content, or cache any content, if the user is authenticated if (_workContext.CurrentUser != null) { Logger.Debug("Request ignored on Authenticated user"); return; } // caches the default cache duration to prevent a query to the settings _cacheDuration = _cacheManager.Get("CacheSettingsPart.Duration", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return(_workContext.CurrentSite.As <CacheSettingsPart>().DefaultCacheDuration); } ); // caches the default max age duration to prevent a query to the settings _maxAge = _cacheManager.Get("CacheSettingsPart.MaxAge", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return(_workContext.CurrentSite.As <CacheSettingsPart>().DefaultMaxAge); } ); _varyQueryStringParameters = _cacheManager.Get("CacheSettingsPart.VaryQueryStringParameters", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); var varyQueryStringParameters = _workContext.CurrentSite.As <CacheSettingsPart>().VaryQueryStringParameters; return(string.IsNullOrWhiteSpace(varyQueryStringParameters) ? null : varyQueryStringParameters.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray()); } ); var varyRequestHeadersFromSettings = _cacheManager.Get("CacheSettingsPart.VaryRequestHeaders", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); var varyRequestHeaders = _workContext.CurrentSite.As <CacheSettingsPart>().VaryRequestHeaders; return(string.IsNullOrWhiteSpace(varyRequestHeaders) ? null : varyRequestHeaders.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray()); } ); _varyRequestHeaders = (varyRequestHeadersFromSettings == null) ? new HashSet <string>() : new HashSet <string>(varyRequestHeadersFromSettings); // different tenants with the same urls have different entries _varyRequestHeaders.Add("HOST"); // Set the Vary: Accept-Encoding response header. // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. // The correct version of the resource is delivered based on the client request header. // This is a good choice for applications that are singly homed and depend on public proxies for user locality. _varyRequestHeaders.Add("Accept-Encoding"); // caches the ignored urls to prevent a query to the settings _ignoredUrls = _cacheManager.Get("CacheSettingsPart.IgnoredUrls", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return(_workContext.CurrentSite.As <CacheSettingsPart>().IgnoredUrls); } ); // caches the culture setting _applyCulture = _cacheManager.Get("CacheSettingsPart.ApplyCulture", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return(_workContext.CurrentSite.As <CacheSettingsPart>().ApplyCulture); } ); // caches the debug mode _debugMode = _cacheManager.Get("CacheSettingsPart.DebugMode", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return(_workContext.CurrentSite.As <CacheSettingsPart>().DebugMode); } ); // don't cache ignored url ? if (IsIgnoredUrl(filterContext.RequestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath, _ignoredUrls)) { return; } var queryString = filterContext.RequestContext.HttpContext.Request.QueryString; var requestHeaders = filterContext.RequestContext.HttpContext.Request.Headers; var parameters = new Dictionary <string, object>(filterContext.ActionParameters); foreach (var key in queryString.AllKeys) { if (key == null) { continue; } parameters[key] = queryString[key]; } foreach (var varyByRequestHeader in _varyRequestHeaders) { if (requestHeaders.AllKeys.Contains(varyByRequestHeader)) { parameters["HEADER:" + varyByRequestHeader] = requestHeaders[varyByRequestHeader]; } } // compute the cache key _cacheKey = ComputeCacheKey(filterContext, parameters); // create a tag which doesn't care about querystring _invariantCacheKey = ComputeCacheKey(filterContext, null); // don't retrieve cache content if refused // in this case the result of the action will update the current cached version if (filterContext.RequestContext.HttpContext.Request.Headers["Cache-Control"] != "no-cache") { // fetch cached data _cacheItem = _cacheStorageProvider.GetCacheItem(_cacheKey); if (_cacheItem == null) { Logger.Debug("Cached version not found"); } } else { Logger.Debug("Cache-Control = no-cache requested"); } var response = filterContext.HttpContext.Response; // render cached content if (_cacheItem != null) { Logger.Debug("Cache item found, expires on " + _cacheItem.ValidUntilUtc); var output = _cacheItem.Output; // adds some caching information to the output if requested if (_debugMode) { response.AddHeader("X-Cached-On", _cacheItem.CachedOnUtc.ToString("r")); response.AddHeader("X-Cached-Until", _cacheItem.ValidUntilUtc.ToString("r")); } // shorcut action execution filterContext.Result = new ContentResult { Content = output, ContentType = _cacheItem.ContentType }; response.StatusCode = _cacheItem.StatusCode; ApplyCacheControl(_cacheItem, response); return; } _cacheItem = new CacheItem(); // get contents ApplyCacheControl(_cacheItem, response); // no cache content available, intercept the execution results for caching _previousFilter = response.Filter; response.Filter = _filter = new CapturingResponseFilter(); }
public void OnResultExecuted(ResultExecutedContext filterContext) { var response = filterContext.HttpContext.Response; if (!_cacheControlStrategy.IsCacheable(filterContext.Result, response)) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } } // ignore error results from cache if (response.StatusCode != (int)HttpStatusCode.OK) { // Never cache non-200 responses. filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0)); _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // if the result of a POST is a Redirect, remove any Cache Item for this url // so that the redirected client gets a fresh result // also add a random token to the query string so that public cachers (IIS, proxies, ...) don't return cached content // i.e., Comment creation // ignore in admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } _workContext = _workContextAccessor.GetContext(); // ignore authenticated requests if (_workContext.CurrentUser != null) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // save the result only if the content can be intercepted if (_filter == null) { return; } // flush here to force the Filter to get the rendered content if (response.IsClientConnected) { response.Flush(); } var output = _filter.GetContents(response.ContentEncoding); if (String.IsNullOrWhiteSpace(output)) { return; } response.Filter = null; response.Write(output); // check if there is a specific rule not to cache the whole route var configurations = _cacheService.GetRouteConfigurations(); var route = filterContext.Controller.ControllerContext.RouteData.Route; var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route); var configuration = configurations.FirstOrDefault(c => c.RouteKey == key); // do not cache ? if (configuration != null && configuration.Duration == 0) { return; } // don't cache the result of a POST redirection as it could contain notifications if (_transformRedirect) { return; } // don't cache the result if there were some notifications var messagesZone = _workContextAccessor.GetContext(filterContext).Layout.Zones["Messages"]; var hasNotifications = messagesZone != null && ((IEnumerable <dynamic>)messagesZone).Any(); if (hasNotifications) { return; } // default duration of specific one ? var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration; if (cacheDuration <= 0) { return; } // include each of the content item ids as tags for the cache entry var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); if (filterContext.HttpContext.Request.Url == null) { return; } _cacheItem.ContentType = response.ContentType; _cacheItem.StatusCode = response.StatusCode; _cacheItem.CachedOnUtc = _now; _cacheItem.ValidFor = cacheDuration; _cacheItem.QueryString = filterContext.HttpContext.Request.Url.Query; _cacheItem.Output = output; _cacheItem.CacheKey = _cacheKey; _cacheItem.InvariantCacheKey = _invariantCacheKey; _cacheItem.Tenant = _shellSettings.Name; _cacheItem.Url = filterContext.HttpContext.Request.Url.AbsolutePath; _cacheItem.Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray(); Logger.Debug("Cache item added: " + _cacheItem.CacheKey); // remove old cache data _cacheService.RemoveByTag(_invariantCacheKey); // add data to cache _cacheStorageProvider.Set(_cacheKey, _cacheItem); // add to the tags index foreach (var tag in _cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } }
public void OnActionExecuted(ActionExecutedContext filterContext) { // only cache view results, but don't return already as we still need to process redirections if (!(filterContext.Result is ViewResultBase) && !( AuthorizedContentTypes.Contains(filterContext.HttpContext.Response.ContentType))) { _filter = null; } // ignore error results from cache if (filterContext.HttpContext.Response.StatusCode != (int)HttpStatusCode.OK) { // Never cache non-200 responses. filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0)); _filter = null; return; } // if the result of a POST is a Redirect, remove any Cache Item for this url // so that the redirected client gets a fresh result // also add a random token to the query string so that public cachers (IIS, proxies, ...) don't return cached content // i.e., Comment creation // ignore in admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { return; } _workContext = _workContextAccessor.GetContext(); // ignore authenticated requests if (_workContext.CurrentUser != null) { return; } // todo: look for RedirectToRoute to, or intercept 302s if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) && filterContext.Result is RedirectResult) { Logger.Debug("Redirect on POST"); var redirectUrl = ((RedirectResult)filterContext.Result).Url; if (!VirtualPathUtility.IsAbsolute(redirectUrl)) { var applicationRoot = filterContext.HttpContext.Request.ToRootUrlString(); if (redirectUrl.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) { redirectUrl = redirectUrl.Substring(applicationRoot.Length); } } // querystring invariant key var invariantCacheKey = ComputeCacheKey( _shellSettings.Name, redirectUrl, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(filterContext.RequestContext).Id, null ); _cacheService.RemoveByTag(invariantCacheKey); // adding a refresh key so that the next request will not be cached var epIndex = redirectUrl.IndexOf('?'); var qs = new NameValueCollection(); if (epIndex > 0) { qs = HttpUtility.ParseQueryString(redirectUrl.Substring(epIndex)); } var refresh = _now.Ticks; qs.Remove(RefreshKey); qs.Add(RefreshKey, refresh.ToString("x")); var querystring = "?" + string.Join("&", Array.ConvertAll(qs.AllKeys, k => string.Format("{0}={1}", HttpUtility.UrlEncode(k), HttpUtility.UrlEncode(qs[k])))); if (epIndex > 0) { redirectUrl = redirectUrl.Substring(0, epIndex) + querystring; } else { redirectUrl = redirectUrl + querystring; } filterContext.Result = new RedirectResult(redirectUrl, ((RedirectResult)filterContext.Result).Permanent); filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); } }
public void OnActionExecuted(ActionExecutedContext filterContext) { // only cache view results, but don't return already as we still need to process redirections if (!(filterContext.Result is ViewResultBase) && !(AuthorizedContentTypes.Contains(filterContext.HttpContext.Response.ContentType))) { _filter = null; } // ignore error results from cache if (filterContext.HttpContext.Response.StatusCode != (int)HttpStatusCode.OK) { // Never cache non-200 responses. filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0)); _filter = null; return; } // if the result of a POST is a Redirect, remove any Cache Item for this url // so that the redirected client gets a fresh result // also add a random token to the query string so that public cachers (IIS, proxies, ...) don't return cached content // i.e., Comment creation // ignore in admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { return; } _workContext = _workContextAccessor.GetContext(); // ignore authenticated requests if (_workContext.CurrentUser != null) { return; } // todo: look for RedirectToRoute to, or intercept 302s if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) && filterContext.Result is RedirectResult) { Logger.Debug("Redirect on POST"); var redirectUrl = ((RedirectResult)filterContext.Result).Url; if (!VirtualPathUtility.IsAbsolute(redirectUrl)) { var applicationRoot = filterContext.HttpContext.Request.ToRootUrlString(); if (redirectUrl.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) { redirectUrl = redirectUrl.Substring(applicationRoot.Length); } } // querystring invariant key var invariantCacheKey = ComputeCacheKey( _shellSettings.Name, redirectUrl, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(filterContext.RequestContext).Id, null ); _cacheService.RemoveByTag(invariantCacheKey); // adding a refresh key so that the next request will not be cached var epIndex = redirectUrl.IndexOf('?'); var qs = new NameValueCollection(); if (epIndex > 0) { qs = HttpUtility.ParseQueryString(redirectUrl.Substring(epIndex)); } var refresh = _now.Ticks; qs.Remove(RefreshKey); qs.Add(RefreshKey, refresh.ToString("x")); var querystring = "?" + string.Join("&", Array.ConvertAll(qs.AllKeys, k => string.Format("{0}={1}", HttpUtility.UrlEncode(k), HttpUtility.UrlEncode(qs[k])))); if (epIndex > 0) { redirectUrl = redirectUrl.Substring(0, epIndex) + querystring; } else { redirectUrl = redirectUrl + querystring; } filterContext.Result = new RedirectResult(redirectUrl, ((RedirectResult)filterContext.Result).Permanent); filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); } }
public void OnActionExecuting(ActionExecutingContext filterContext) { // use the action in the cacheKey so that the same route can't return cache for different actions _actionName = filterContext.ActionDescriptor.ActionName; // apply OutputCacheAttribute logic if defined var outputCacheAttribute = filterContext.ActionDescriptor.GetCustomAttributes(typeof (OutputCacheAttribute), true).Cast<OutputCacheAttribute>().FirstOrDefault() ; if(outputCacheAttribute != null) { if (outputCacheAttribute.Duration <= 0 || outputCacheAttribute.NoStore) { Logger.Debug("Request ignored based on OutputCache attribute"); return; } } // saving the current datetime _now = _clock.UtcNow; // before executing an action, we check if a valid cached result is already // existing for this context (url, theme, culture, tenant) Logger.Debug("Request on: " + filterContext.RequestContext.HttpContext.Request.RawUrl); // don't cache POST requests if(filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) ) { Logger.Debug("Request ignored on POST"); return; } // don't cache the admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { Logger.Debug("Request ignored on Admin section"); return; } // ignore child actions, e.g. HomeController is using RenderAction() if (filterContext.IsChildAction){ Logger.Debug("Request ignored on Child actions"); return; } _workContext = _workContextAccessor.GetContext(); // don't return any cached content, or cache any content, if the user is authenticated if (_workContext.CurrentUser != null) { Logger.Debug("Request ignored on Authenticated user"); return; } // caches the default cache duration to prevent a query to the settings _cacheDuration = _cacheManager.Get("CacheSettingsPart.Duration", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultCacheDuration; } ); // caches the default max age duration to prevent a query to the settings _maxAge = _cacheManager.Get("CacheSettingsPart.MaxAge", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultMaxAge; } ); _varyQueryStringParameters = _cacheManager.Get("CacheSettingsPart.VaryQueryStringParameters", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); var varyQueryStringParameters = _workContext.CurrentSite.As<CacheSettingsPart>().VaryQueryStringParameters; return string.IsNullOrWhiteSpace(varyQueryStringParameters) ? null : varyQueryStringParameters.Split(new[]{","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } ); // caches the ignored urls to prevent a query to the settings _ignoredUrls = _cacheManager.Get("CacheSettingsPart.IgnoredUrls", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().IgnoredUrls; } ); // caches the culture setting _applyCulture = _cacheManager.Get("CacheSettingsPart.ApplyCulture", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().ApplyCulture; } ); // caches the ignored urls to prevent a query to the settings _debugMode = _cacheManager.Get("CacheSettingsPart.DebugMode", context => { context.Monitor(_signals.When(CacheSettingsPart.CacheKey)); return _workContext.CurrentSite.As<CacheSettingsPart>().DebugMode; } ); var queryString = filterContext.RequestContext.HttpContext.Request.QueryString; var parameters = new Dictionary<string, object>(filterContext.ActionParameters); foreach(var key in queryString.AllKeys) { if (key == null) continue; parameters[key] = queryString[key]; } // compute the cache key _cacheKey = ComputeCacheKey(filterContext, parameters); // create a tag which doesn't care about querystring _invariantCacheKey = ComputeCacheKey(filterContext, null); // don't retrieve cache content if refused // in this case the result of the action will update the current cached version if (filterContext.RequestContext.HttpContext.Request.Headers["Cache-Control"] != "no-cache") { // fetch cached data _cacheItem = _cacheStorageProvider.GetCacheItem(_cacheKey); if (_cacheItem == null) { Logger.Debug("Cached version not found"); } } else { Logger.Debug("Cache-Control = no-cache requested"); } var response = filterContext.HttpContext.Response; // render cached content if (_cacheItem != null) { Logger.Debug("Cache item found, expires on " + _cacheItem.ValidUntilUtc); var output = _cacheItem.Output; /* * * There is no need to replace the AntiForgeryToken as it is not used for unauthenticated requests * and at this point, the request can't be authenticated * * // replace any anti forgery token with a fresh value if (output.Contains(AntiforgeryBeacon)) { var viewContext = new ViewContext { HttpContext = filterContext.HttpContext, Controller = filterContext.Controller }; var htmlHelper = new HtmlHelper(viewContext, new ViewDataContainer()); var siteSalt = _workContext.CurrentSite.SiteSalt; var token = htmlHelper.AntiForgeryToken(siteSalt); output = output.Replace(AntiforgeryBeacon, token.ToString()); } */ // adds some caching information to the output if requested if (_debugMode) { output += "\r\n<!-- Cached on " + _cacheItem.CachedOnUtc + " (UTC) until " + _cacheItem.ValidUntilUtc + " (UTC) -->"; response.AddHeader("X-Cached-On", _cacheItem.CachedOnUtc.ToString("r")); response.AddHeader("X-Cached-Until", _cacheItem.ValidUntilUtc.ToString("r")); } filterContext.Result = new ContentResult { Content = output, ContentType = _cacheItem.ContentType }; response.StatusCode = _cacheItem.StatusCode; ApplyCacheControl(_cacheItem, response); return; } _cacheItem = new CacheItem(); // get contents ApplyCacheControl(_cacheItem, response); // no cache content available, intercept the execution results for caching response.Filter = _filter = new CapturingResponseFilter(response.Filter); }