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

        }
Exemple #2
0
        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);
            }
        }
Exemple #5
0
        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);
            }
        }
Exemple #6
0
        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);
        }