diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs index a48d5bad1..a6734cae7 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs @@ -21,6 +21,8 @@ using Orchard.Themes; using Orchard.UI.Admin; using Orchard.Utility.Extensions; using System.Collections.Specialized; +using Orchard.OutputCache.ViewModels; +using Orchard.UI.Admin.Notification; namespace Orchard.OutputCache.Filters { public class OutputCacheFilter : FilterProvider, IActionFilter, IResultFilter { @@ -36,7 +38,10 @@ namespace Orchard.OutputCache.Filters { private readonly ISignals _signals; private readonly ShellSettings _shellSettings; private readonly ICacheControlStrategy _cacheControlStrategy; - private Stream _previousFilter; + private readonly INotificationManager _notificationManager; + + TextWriter _originalWriter; + StringWriter _cachingWriter; private static string RefreshKey = "__r"; private static long Epoch = new DateTime(2014, DateTimeKind.Utc).Ticks; @@ -52,7 +57,8 @@ namespace Orchard.OutputCache.Filters { ICacheService cacheService, ISignals signals, ShellSettings shellSettings, - ICacheControlStrategy cacheControlStrategy + ICacheControlStrategy cacheControlStrategy, + INotificationManager notificationManager ) { _cacheManager = cacheManager; _cacheStorageProvider = cacheStorageProvider; @@ -65,6 +71,7 @@ namespace Orchard.OutputCache.Filters { _signals = signals; _shellSettings = shellSettings; _cacheControlStrategy = cacheControlStrategy; + _notificationManager = notificationManager; Logger = NullLogger.Instance; } @@ -83,8 +90,8 @@ namespace Orchard.OutputCache.Filters { private bool _transformRedirect; private WorkContext _workContext; - private CapturingResponseFilter _filter; private CacheItem _cacheItem; + private Func _completeResponse; public ILogger Logger { get; set; } @@ -285,8 +292,11 @@ namespace Orchard.OutputCache.Filters { ApplyCacheControl(_cacheItem, response); // no cache content available, intercept the execution results for caching - _previousFilter = response.Filter; - response.Filter = _filter = new CapturingResponseFilter(); + _cachingWriter = new StringWriter(CultureInfo.InvariantCulture); + _originalWriter = filterContext.HttpContext.Response.Output; + filterContext.HttpContext.Response.Output = _cachingWriter; + + _completeResponse = CaptureResponse; } public void OnActionExecuted(ActionExecutedContext filterContext) { @@ -297,123 +307,51 @@ namespace Orchard.OutputCache.Filters { public void OnResultExecuted(ResultExecutedContext filterContext) { - if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { - return; + string capturedResponse = null; + if (_completeResponse != null) { + capturedResponse = _completeResponse(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) { + 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)); - _filter = null; - if (_previousFilter != null) { - response.Filter = _previousFilter; - } - - // 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 - // i.e., Comment creation - if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) && - !String.IsNullOrWhiteSpace(filterContext.HttpContext.Response.RedirectLocation)) { - - var url = filterContext.HttpContext.Response.RedirectLocation; - if (!VirtualPathUtility.IsAbsolute(url)) { - var applicationRoot = new UrlHelper(filterContext.HttpContext.Request.RequestContext).MakeAbsolute("/"); - if (url.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) { - url = url.Substring(applicationRoot.Length); - } - } - - var redirectionInvariantCacheKey = ComputeCacheKey(_shellSettings.Name, url, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(filterContext.RequestContext).Id, null); - - _cacheService.RemoveByTag(redirectionInvariantCacheKey); - } - return; } - - // ignore in admin - if (AdminFilter.IsApplied(new RequestContext(filterContext.HttpContext, new RouteData()))) { - _filter = null; - if (_previousFilter != null) { - response.Filter = _previousFilter; - } + if (capturedResponse == null) { 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 + RouteConfiguration configuration = null; 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); - + 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 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)messagesZone).Any(); - if (hasNotifications) { + if (_notificationManager.GetNotifications().Any()) { 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(); @@ -426,7 +364,7 @@ namespace Orchard.OutputCache.Filters { _cacheItem.CachedOnUtc = _now; _cacheItem.ValidFor = cacheDuration; _cacheItem.QueryString = filterContext.HttpContext.Request.Url.Query; - _cacheItem.Output = output; + _cacheItem.Output = capturedResponse; _cacheItem.CacheKey = _cacheKey; _cacheItem.InvariantCacheKey = _invariantCacheKey; _cacheItem.Tenant = _shellSettings.Name; @@ -445,7 +383,14 @@ namespace Orchard.OutputCache.Filters { foreach (var tag in _cacheItem.Tags) { _tagCache.Tag(tag, _cacheKey); } + } + private string CaptureResponse(ControllerContext filterContext) { + filterContext.HttpContext.Response.Output = _originalWriter; + + string capturedText = _cachingWriter.ToString(); + filterContext.HttpContext.Response.Write(capturedText); + return capturedText; } private bool TransformRedirect(ActionExecutedContext filterContext) { @@ -469,12 +414,13 @@ namespace Orchard.OutputCache.Filters { } Logger.Debug("Redirect on POST"); - var redirectUrl = redirectResult.Url ; + var redirectUrl = redirectResult.Url; if (!VirtualPathUtility.IsAbsolute(redirectUrl)) { var applicationRoot = new UrlHelper(filterContext.HttpContext.Request.RequestContext).MakeAbsolute("/"); if (redirectUrl.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) { - redirectUrl = redirectUrl.Substring(applicationRoot.Length); + redirectUrl = "~/" + redirectUrl.Substring(applicationRoot.Length); + redirectUrl = VirtualPathUtility.ToAbsolute(redirectUrl); } } @@ -572,7 +518,8 @@ namespace Orchard.OutputCache.Filters { if (!VirtualPathUtility.IsAbsolute(url)) { var applicationRoot = new UrlHelper(controllerContext.HttpContext.Request.RequestContext).MakeAbsolute("/"); if (url.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) { - url = url.Substring(applicationRoot.Length); + url = "~/" + url.Substring(applicationRoot.Length); + url = VirtualPathUtility.ToAbsolute(url); } } return ComputeCacheKey(_shellSettings.Name, url, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(controllerContext.RequestContext).Id, parameters); @@ -652,82 +599,7 @@ namespace Orchard.OutputCache.Filters { ); } } - - /// - /// Captures the response stream while writing to it - /// - public class CapturingResponseFilter : Stream { - private readonly MemoryStream _mem; - - public CapturingResponseFilter() { - _mem = new MemoryStream(); - } - - // The following members of Stream must be overriden. - public override bool CanRead { - get { return true; } - } - - public override bool CanSeek { - get { return false; } - } - - public override bool CanWrite { - get { return false; } - } - - public override long Length { - get { return 0; } - } - - public override long Position { get; set; } - - public override long Seek(long offset, SeekOrigin direction) { - return 0; - } - - public override void SetLength(long length) { - } - - public override void Close() { - _mem.Close(); - } - - public override void Flush() { - } - - public override int Read(byte[] buffer, int offset, int count) { - return count; - } - - // Override the Write method to filter Response to a file. - public override void Write(byte[] buffer, int offset, int count) { - - //Write out the response to the file. - _mem.Write(buffer, 0, count); - } - - public string GetContents(Encoding enc) { - var buffer = new byte[_mem.Length]; - _mem.Position = 0; - _mem.Read(buffer, 0, buffer.Length); - return enc.GetString(buffer, 0, buffer.Length); - } - - public byte[] GetContents() { - return _mem.ToArray(); - } - - protected override void Dispose(bool disposing) { - if (disposing) { - _mem.Dispose(); - } - - base.Dispose(disposing); - } - - } - + public class ViewDataContainer : IViewDataContainer { public ViewDataDictionary ViewData { get; set; } } diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 0f76db44a..cc7422f09 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -99,7 +99,6 @@ -