public void Set(string key, CacheItem cacheItem) {
            if (cacheItem.ValidFor <= 0) {
                return;
            }

            var value = JsonConvert.SerializeObject(cacheItem);
            Database.StringSet(GetLocalizedKey(key), value, TimeSpan.FromSeconds(cacheItem.ValidFor));
        }
        public void Set(string key, CacheItem cacheItem) {
            if (cacheItem.ValidFor <= 0) {
                return;
            }

            Logger.Debug("Set() invoked with key='{0}' in region '{1}'.", key, _regionAlphaNumeric);
            _cache.Put(key, cacheItem, TimeSpan.FromSeconds(cacheItem.ValidFor), _regionAlphaNumeric);
        }
 public void Set(string key, CacheItem cacheItem) {
     _workContext.HttpContext.Cache.Add(
         key,
         cacheItem,
         null,
         cacheItem.ValidUntilUtc,
         System.Web.Caching.Cache.NoSlidingExpiration,
         System.Web.Caching.CacheItemPriority.Normal,
         null);
 }
 public void Set(string key, CacheItem cacheItem) {
     _workContext.HttpContext.Cache.Add(
         key,
         cacheItem,
         null,
         System.Web.Caching.Cache.NoAbsoluteExpiration,
         new TimeSpan(0, 0, cacheItem.ValidFor),
         System.Web.Caching.CacheItemPriority.Normal,
         null);
 }
        public void Set(string key, CacheItem cacheItem) {
            lock (String.Intern(key)) {
                var records = _repository.Table.Where(x => x.CacheKey == key).ToList();
                var record = records.FirstOrDefault();
                    
                if (record == null) {
                    record = new CacheItemRecord();
                    Convert(cacheItem, record);
                    _repository.Create(record);
                    return;
                }

                Convert(cacheItem, record);
            }
        }
        public void Set(string key, CacheItem cacheItem) {
            if(cacheItem == null) {
                throw new ArgumentNullException("cacheItem");
            }

            if (cacheItem.ValidFor <= 0) {
                return;
            }

            using (var decompressedStream = Serialize(cacheItem)) {
                using (var compressedStream = Compress(decompressedStream)) {
                    Database.StringSet(GetLocalizedKey(key), compressedStream.ToArray(), TimeSpan.FromSeconds(cacheItem.ValidFor));
                }
            }
        }
 private void Convert(CacheItem cacheItem, CacheItemRecord record) {
     record.CacheKey = cacheItem.CacheKey;
     record.CachedOnUtc = cacheItem.CachedOnUtc;
     record.Duration = cacheItem.Duration;
     record.GraceTime = cacheItem.GraceTime;
     record.ValidUntilUtc = cacheItem.ValidUntilUtc;
     record.StoredUntilUtc = cacheItem.StoredUntilUtc;
     record.ContentType = cacheItem.ContentType;
     record.InvariantCacheKey = cacheItem.InvariantCacheKey;
     record.Output = cacheItem.Output;
     record.QueryString = cacheItem.QueryString;
     record.StatusCode = cacheItem.StatusCode;
     record.Tags = String.Join(";", cacheItem.Tags);
     record.Tenant = cacheItem.Tenant;
     record.Url = cacheItem.Url;
 }
        private CacheItem Convert(CacheItemRecord record) {
            var cacheItem = new CacheItem();

            cacheItem.CacheKey = record.CacheKey;
            cacheItem.CachedOnUtc = record.CachedOnUtc;
            cacheItem.Duration = record.Duration;
            cacheItem.GraceTime = record.GraceTime;
            cacheItem.ContentType = record.ContentType;
            cacheItem.InvariantCacheKey = record.InvariantCacheKey;
            cacheItem.Output = record.Output;
            cacheItem.QueryString = record.QueryString;
            cacheItem.StatusCode = record.StatusCode;
            cacheItem.Tags = record.Tags.Split(';');
            cacheItem.Tenant = record.Tenant;
            cacheItem.Url = record.Url;

            return cacheItem;
        }
        private void ServeCachedItem(ActionExecutingContext filterContext, CacheItem cacheItem) {
            var response = filterContext.HttpContext.Response;

            // Fix for missing charset in response headers
            response.Charset = response.Charset; 

            // Adds some caching information to the output if requested.
            if (CacheSettings.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 FileContentResult(cacheItem.Output, cacheItem.ContentType);

            response.StatusCode = cacheItem.StatusCode;

            ApplyCacheControl(response);
        }
        public void OnResultExecuted(ResultExecutedContext filterContext) {

            var captureHandlerIsAttached = false;

            try {

                // This filter is not reentrant (multiple executions within the same request are
                // not supported) so child actions are ignored completely.
                if (filterContext.IsChildAction || !_isCachingRequest)
                    return;

                Logger.Debug("Item '{0}' was rendered.", _cacheKey);

                // Obtain individual route configuration, if any.
                CacheRouteConfig configuration = null;
                var configurations = _cacheService.GetRouteConfigs();
                if (configurations.Any()) {
                    var route = filterContext.Controller.ControllerContext.RouteData.Route;
                    var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route);
                    configuration = configurations.FirstOrDefault(c => c.RouteKey == key);
                }

                if (!ResponseIsCacheable(filterContext, configuration)) {
                    filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
                    filterContext.HttpContext.Response.Cache.SetNoStore();
                    filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0));
                    return;
                }

                // Determine duration and grace time.
                var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : CacheSettings.DefaultCacheDuration;
                var cacheGraceTime = configuration != null && configuration.GraceTime.HasValue ? configuration.GraceTime.Value : CacheSettings.DefaultCacheGraceTime;

                // Include each content item ID as tags for the cache entry.
                var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray();

                // Capture the response output using a custom filter stream.
                var response = filterContext.HttpContext.Response;
                var captureStream = new CaptureStream(response.Filter);
                response.Filter = captureStream;
                captureStream.Captured += (output) => {
                    try {
                        // Since this is a callback any call to injected dependencies can result in an Autofac exception: "Instances 
                        // cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed."
                        // To prevent access to the original lifetime scope a new work context scope should be created here and dependencies
                        // should be resolved from it.

                        using (var scope = _workContextAccessor.CreateWorkContextScope()) {
                            var cacheItem = new CacheItem() {
                                CachedOnUtc = _now,
                                Duration = cacheDuration,
                                GraceTime = cacheGraceTime,
                                Output = output,
                                ContentType = response.ContentType,
                                QueryString = filterContext.HttpContext.Request.Url.Query,
                                CacheKey = _cacheKey,
                                InvariantCacheKey = _invariantCacheKey,
                                Url = filterContext.HttpContext.Request.Url.AbsolutePath,
                                Tenant = scope.Resolve<ShellSettings>().Name,
                                StatusCode = response.StatusCode,
                                Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray()
                            };

                            // Write the rendered item to the cache.
                            var cacheStorageProvider = scope.Resolve<IOutputCacheStorageProvider>();
                            cacheStorageProvider.Remove(_cacheKey);
                            cacheStorageProvider.Set(_cacheKey, cacheItem);

                            Logger.Debug("Item '{0}' was written to cache.", _cacheKey);

                            // Also add the item tags to the tag cache.
                            var tagCache = scope.Resolve<ITagCache>();
                            foreach (var tag in cacheItem.Tags) {
                                tagCache.Tag(tag, _cacheKey);
                            }
                        }
                    }
                    finally {
                        // Always release the cache key lock when the request ends.
                        ReleaseCacheKeyLock();
                    }
                };

                captureHandlerIsAttached = true;
            }
            finally {
                // If the response filter stream capture handler was attached then we'll trust
                // it to release the cache key lock at some point in the future when the stream
                // is flushed; otherwise we'll make sure we'll release it here.
                if (!captureHandlerIsAttached)
                    ReleaseCacheKeyLock();
            }
        }
Beispiel #11
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();
        }
Beispiel #12
0
        /// <summary>
        /// Define valid cache control values
        /// </summary>
        private void ApplyCacheControl(CacheItem cacheItem, HttpResponseBase response) {
            if (_maxAge > 0) {
                var maxAge = new TimeSpan(0, 0, 0, _maxAge); //cacheItem.ValidUntilUtc - _clock.UtcNow;
                if (maxAge.TotalMilliseconds < 0) {
                    maxAge = TimeSpan.FromSeconds(0);
                }

                response.Cache.SetCacheability(HttpCacheability.Public);
                response.Cache.SetMaxAge(maxAge);
            }

            // an ETag is a string that uniquely identifies a specific version of a component.
            // we use the cache item to detect if it's a new one
            if (HttpRuntime.UsingIntegratedPipeline) {
                if (response.Headers.Get("ETag") == null) {
                    response.Cache.SetETag(cacheItem.GetHashCode().ToString(CultureInfo.InvariantCulture));
                }
            }

            response.Cache.SetOmitVaryStar(true);

            if (_varyQueryStringParameters != null) {
                foreach (var queryStringParam in _varyQueryStringParameters) {
                    response.Cache.VaryByParams[queryStringParam] = true;
                }
            }

            foreach (var varyRequestHeader in _varyRequestHeaders) {
                response.Cache.VaryByHeaders[varyRequestHeader] = true;
            }

            // create a unique cache per browser, in case a Theme is rendered differently (e.g., mobile)
            // c.f. http://msdn.microsoft.com/en-us/library/aa478965.aspx
            // c.f. http://stackoverflow.com/questions/6007287/outputcache-varybyheader-user-agent-or-varybycustom-browser
            response.Cache.SetVaryByCustom("browser");

            // enabling this would create an entry for each different browser sub-version
            // response.Cache.VaryByHeaders.UserAgent = true;

        }
        private void ServeCachedItem(ActionExecutingContext filterContext, CacheItem cacheItem) {
            var response = filterContext.HttpContext.Response;
            var request = filterContext.HttpContext.Request;

            // Fix for missing charset in response headers
            response.Charset = response.Charset;

            // Adds some caching information to the output if requested.
            if (CacheSettings.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 FileContentResult(cacheItem.Output, cacheItem.ContentType);
            response.StatusCode = cacheItem.StatusCode;

            // Add ETag header
            if (HttpRuntime.UsingIntegratedPipeline && response.Headers.Get("ETag") == null && cacheItem.ETag != null) {
                response.Headers["ETag"] = cacheItem.ETag;
            }

            // Check ETag in request
            // https://www.w3.org/2005/MWI/BPWG/techs/CachingWithETag.html
            var etag = request.Headers["If-None-Match"];
            if (!String.IsNullOrEmpty(etag)) {
                if (String.Equals(etag, cacheItem.ETag, StringComparison.Ordinal)) {
                    // ETag matches the cached item, we return a 304
                    filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.NotModified);
                    return;
                }
            }

            ApplyCacheControl(response);
        }
 private static MemoryStream Serialize(CacheItem item) {
     BinaryFormatter binaryFormatter = new BinaryFormatter();
     var memoryStream = new MemoryStream();
     binaryFormatter.Serialize(memoryStream, item);
     memoryStream.Seek(0, SeekOrigin.Begin);
     return memoryStream;
 }
Beispiel #15
0
        /// <summary>
        /// Define valid cache control values
        /// </summary>
        private void ApplyCacheControl(CacheItem cacheItem, HttpResponseBase response) {
            if (_maxAge > 0) {
                var maxAge = new TimeSpan(0, 0, 0, _maxAge); //cacheItem.ValidUntilUtc - _clock.UtcNow;
                if (maxAge.TotalMilliseconds < 0) {
                    maxAge = TimeSpan.FromSeconds(0);
                }

                response.Cache.SetCacheability(HttpCacheability.Public);
                response.Cache.SetMaxAge(maxAge);
            }

            // an ETag is a string that uniquely identifies a specific version of a component.
            // we use the cache item to detect if it's a new one
            if (response.Headers.Get("ETag") == null) {
                response.Cache.SetETag(cacheItem.GetHashCode().ToString(CultureInfo.InvariantCulture));
            }

            response.Cache.SetOmitVaryStar(true);

            if (_varyQueryStringParameters != null) {
                foreach (var queryStringParam in _varyQueryStringParameters) {
                    response.Cache.VaryByParams[queryStringParam] = true;
                }
            }

            // different tenants with the same urls have different entries
            response.Cache.VaryByHeaders["HOST"] = true;

            // 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.
            response.Cache.VaryByHeaders["Accept-Encoding"] = true;

            // create a unique cache per browser, in case a Theme is rendered differently (e.g., mobile)
            // c.f. http://msdn.microsoft.com/en-us/library/aa478965.aspx
            // c.f. http://stackoverflow.com/questions/6007287/outputcache-varybyheader-user-agent-or-varybycustom-browser
            response.Cache.SetVaryByCustom("browser");

            // enabling this would create an entry for each different browser sub-version
            // response.Cache.VaryByHeaders.UserAgent = true;

        }
        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);
        }
        /// <summary>
        /// Define valid cache control values
        /// </summary>
        private void ApplyCacheControl(CacheItem cacheItem, HttpResponseBase response) {
            if (_maxAge > 0) {
                var maxAge = new TimeSpan(0, 0, 0, _maxAge); //cacheItem.ValidUntilUtc - _clock.UtcNow;
                if (maxAge.TotalMilliseconds < 0) {
                    maxAge = TimeSpan.FromSeconds(0);
                }
                
                response.Cache.SetCacheability(HttpCacheability.Public);
                response.Cache.SetMaxAge(maxAge);
            }

            response.Cache.VaryByParams["*"] = true;
            response.DisableUserCache();

            // keeping this examples for later usage
            // response.DisableKernelCache();
            // response.Cache.SetOmitVaryStar(true);

            // an ETag is a string that uniquely identifies a specific version of a component.
            // we use the cache item to detect if it's a new one
            if (HttpRuntime.UsingIntegratedPipeline) {
                if (response.Headers.Get("ETag") == null) {
                    response.Cache.SetETag(cacheItem.GetHashCode().ToString(CultureInfo.InvariantCulture));
                }
            }

            if (_varyQueryStringParameters != null) {
                foreach (var queryStringParam in _varyQueryStringParameters) {
                    response.Cache.VaryByParams[queryStringParam] = true;
                }
            }

            foreach (var varyRequestHeader in _varyRequestHeaders) {
                response.Cache.VaryByHeaders[varyRequestHeader] = true;
            }
        }