Example #1
0
        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);
        }
Example #2
0
        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);
            }
        }