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; } ); // 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; } ); CacheItem cacheItem = null; var queryString = filterContext.RequestContext.HttpContext.Request.QueryString; var parameters = new Dictionary<string, object>(filterContext.ActionParameters); foreach(var key in queryString.AllKeys) { 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, output); return; } // no cache content available, intercept the execution results for caching response.Filter = _filter = new CapturingResponseFilter(response.Filter); }
public void OnActionExecuted(ActionExecutedContext filterContext) { // ignore error results from cache if (filterContext.HttpContext.Response.StatusCode != (int)HttpStatusCode.OK) { _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; } 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); } }