public void Displayed(ShapeDisplayedContext context) { // TODO: Configure duration of sliding expiration var cacheContext = context.ShapeMetadata.Cache(); // If the shape is not cached, evaluate the ESIs if (cacheContext == null) { string content; using (var sw = new StringWriter()) { context.ChildContent.WriteTo(sw, HtmlEncoder.Default); content = sw.ToString(); } ProcessESIs(ref content, GetDistributedCache); context.ChildContent = new HtmlString(content); } else if (!_cached.Contains(cacheContext) && context.ChildContent != null) { var cacheEntries = GetCacheEntries(cacheContext).ToList(); string cacheKey = GetCacheKey(cacheContext.CacheId, cacheEntries); using (var sw = new StringWriter()) { context.ChildContent.WriteTo(sw, HtmlEncoder.Default); var content = sw.ToString(); _cached.Add(cacheContext); _cache[cacheKey] = content; var contexts = String.Join(ContextSeparator.ToString(), cacheContext.Contexts.ToArray()); context.ChildContent = new HtmlString($"[[cache id='{cacheContext.CacheId}' contexts='{contexts}']]"); var bytes = Encoding.UTF8.GetBytes(content); // Default duration is sliding expiration (permanent as long as it's used) DistributedCacheEntryOptions options = new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(0, 1, 0) }; // If a custom duration is specified, replace the default options if (cacheContext.Duration.HasValue) { options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheContext.Duration }; } _dynamicCache.SetAsync(cacheKey, bytes, options).Wait(); _tagCache.Tag(cacheKey, cacheContext.Tags.ToArray()); } } }
public void CacheItem(IContent contentItem, string displayType, ItemLevelCacheItem itemLevelCacheItem) { if (contentItem == null) { return; } var itemLevelCachePart = contentItem.As <ItemLevelCachePart>(); if (itemLevelCachePart == null || !itemLevelCachePart.ItemLevelCacheSettings.ContainsKey(displayType) || itemLevelCachePart.ItemLevelCacheSettings[displayType].Mode != ItemLevelCacheMode.CacheItem) { return; } var settings = itemLevelCachePart.ItemLevelCacheSettings[displayType]; var cacheKey = GetCacheKey(contentItem, displayType); var cachedItem = mOutputCacheStorageProvider.GetCacheItem(cacheKey); if (cachedItem == null || cachedItem.IsInGracePeriod(mClock.UtcNow)) // TODO: Should this method check the cache first, or just blindly insert? { var serializedCacheItem = mJsonConverter.Serialize(itemLevelCacheItem); var cacheItem = new CacheItem() { CachedOnUtc = mClock.UtcNow, Duration = settings.CacheDurationSeconds, GraceTime = settings.CacheGraceTimeSeconds, Output = mHttpContextAccessor.Current().Request.ContentEncoding.GetBytes(serializedCacheItem), Tags = new[] { ItemLevelCacheTag.GenericTag, ItemLevelCacheTag.For(contentItem), ItemLevelCacheTag.For(contentItem, displayType), ItemLevelCacheTag.For(contentItem.ContentItem.TypeDefinition) }, Tenant = mShellSettings.Name, CacheKey = cacheKey, InvariantCacheKey = cacheKey, Url = "" }; mOutputCacheStorageProvider.Set(cacheKey, cacheItem); // Also add the item tags to the tag cache. foreach (var tag in cacheItem.Tags) { mTagCache.Tag(tag, cacheKey); } } }
private dynamic BeginRenderItem(IContent content, string displayType, string groupId) { WorkContext workContext = _workContextAccessor.GetContext(); var output = _shapeDisplay.Display(BuildShape(content, displayType, groupId)); _cacheSettings = GetOutputCacheSettings(content); _cacheKey = ComputeCacheKey(content, displayType, groupId); _invariantCacheKey = string.Format("tenant={0};id={1};", _shellSettings.Name, content.ContentItem.Id); var cacheItem = new CacheItem() { CachedOnUtc = _now, ValidFor = _cacheSettings.CacheDuration, Output = _jsonConverter.Serialize(new OutputCacheItem { Id = content.ContentItem.Id, Output = output, Resources = _resourceManager.GetRequiredResources("script") .Concat(_resourceManager.GetRequiredResources("stylesheet")).Select(GetCacheItemResource).ToList() }), ContentType = content.ContentItem.ContentType, QueryString = workContext.HttpContext.Request.Url.Query, CacheKey = _cacheKey, InvariantCacheKey = _invariantCacheKey, Url = workContext.HttpContext.Request.Url.AbsolutePath, Tenant = _shellSettings.Name, StatusCode = workContext.HttpContext.Response.StatusCode, Tags = new[] { _invariantCacheKey, content.ContentItem.Id.ToString(CultureInfo.InvariantCulture) } }; _cacheStorageProvider.Remove(_cacheKey); _cacheStorageProvider.Set(_cacheKey, cacheItem); foreach (var tag in cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } return(ServeCachedItem(cacheItem, content, displayType, groupId)); }
public void OnResultExecuted(ResultExecutedContext filterContext) { string capturedResponse = null; if (_completeResponse != null) { capturedResponse = _completeResponse(filterContext); } var response = filterContext.HttpContext.Response; // ignore error results from cache if (response.StatusCode != (int)HttpStatusCode.OK || _transformRedirect) { // 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)); return; } if (capturedResponse == null) { return; } // check if there is a specific rule not to cache the whole route RouteConfiguration configuration = null; var configurations = _cacheService.GetRouteConfigurations(); if (configurations.Any()) { var route = filterContext.Controller.ControllerContext.RouteData.Route; var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route); configuration = configurations.FirstOrDefault(c => c.RouteKey == key); } // do not cache ? if (configuration != null && configuration.Duration == 0) { return; } // don't cache the result if there were some notifications if (_notificationManager.GetNotifications().Any()) { return; } // default duration of specific one ? var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration; // 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 = capturedResponse; _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 only the current version of the page _cacheService.RemoveByTag(_cacheKey); // add data to cache _cacheStorageProvider.Set(_cacheKey, _cacheItem); // add to the tags index foreach (var tag in _cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } }
public void OnResultExecuted(ResultExecutedContext filterContext) { var response = filterContext.HttpContext.Response; if (!_cacheControlStrategy.IsCacheable(filterContext.Result, response)) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } } // ignore error results from cache if (response.StatusCode != (int)HttpStatusCode.OK) { // Never cache non-200 responses. filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0)); _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // if the result of a POST is a Redirect, remove any Cache Item for this url // so that the redirected client gets a fresh result // also add a random token to the query string so that public cachers (IIS, proxies, ...) don't return cached content // i.e., Comment creation // ignore in admin if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } _workContext = _workContextAccessor.GetContext(); // ignore authenticated requests if (_workContext.CurrentUser != null) { _filter = null; if (_previousFilter != null) { response.Filter = _previousFilter; } return; } // save the result only if the content can be intercepted if (_filter == null) { return; } // flush here to force the Filter to get the rendered content if (response.IsClientConnected) { response.Flush(); } var output = _filter.GetContents(response.ContentEncoding); if (String.IsNullOrWhiteSpace(output)) { return; } response.Filter = null; response.Write(output); // check if there is a specific rule not to cache the whole route var configurations = _cacheService.GetRouteConfigurations(); var route = filterContext.Controller.ControllerContext.RouteData.Route; var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route); var configuration = configurations.FirstOrDefault(c => c.RouteKey == key); // do not cache ? if (configuration != null && configuration.Duration == 0) { return; } // don't cache the result of a POST redirection as it could contain notifications if (_transformRedirect) { return; } // don't cache the result if there were some notifications var messagesZone = _workContextAccessor.GetContext(filterContext).Layout.Zones["Messages"]; var hasNotifications = messagesZone != null && ((IEnumerable <dynamic>)messagesZone).Any(); if (hasNotifications) { return; } // default duration of specific one ? var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration; if (cacheDuration <= 0) { return; } // include each of the content item ids as tags for the cache entry var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); if (filterContext.HttpContext.Request.Url == null) { return; } _cacheItem.ContentType = response.ContentType; _cacheItem.StatusCode = response.StatusCode; _cacheItem.CachedOnUtc = _now; _cacheItem.ValidFor = cacheDuration; _cacheItem.QueryString = filterContext.HttpContext.Request.Url.Query; _cacheItem.Output = output; _cacheItem.CacheKey = _cacheKey; _cacheItem.InvariantCacheKey = _invariantCacheKey; _cacheItem.Tenant = _shellSettings.Name; _cacheItem.Url = filterContext.HttpContext.Request.Url.AbsolutePath; _cacheItem.Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray(); Logger.Debug("Cache item added: " + _cacheItem.CacheKey); // remove old cache data _cacheService.RemoveByTag(_invariantCacheKey); // add data to cache _cacheStorageProvider.Set(_cacheKey, _cacheItem); // add to the tags index foreach (var tag in _cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } }
public void 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 { 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 = _shellSettings.Name, StatusCode = response.StatusCode, Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray() }; // Write the rendered item to the cache. _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. 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(); } } }
public void OnResultExecuted(ResultExecutedContext filterContext) { var response = filterContext.HttpContext.Response; // save the result only if the content can be intercepted if (_filter == null) { return; } // 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); // 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); // do not cache ? if (configuration != null && configuration.Duration == 0) { return; } // default duration of specific one ? var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration; // include each of the content item ids as tags for the cache entry var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); _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); } }