mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
#20881: Changed capture mechanism in Orchard.OutputCache to support any result type.
Instead of using a custom writer for the Response.Output property, the OutputCacheFilter now uses a specialized Stream class for the Response.Filter property, which captures anything written to it. This seems to work with any type of result, including FileContentResult. Work Item: 20881
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.OutputCache.Filters {
|
||||
public class CaptureStream : Stream {
|
||||
public CaptureStream(Stream innerStream) {
|
||||
_innerStream = innerStream;
|
||||
_captureStream = new MemoryStream();
|
||||
}
|
||||
|
||||
private readonly Stream _innerStream;
|
||||
private readonly MemoryStream _captureStream;
|
||||
|
||||
public override bool CanRead {
|
||||
get { return _innerStream.CanRead; }
|
||||
}
|
||||
|
||||
public override bool CanSeek {
|
||||
get { return _innerStream.CanSeek; }
|
||||
}
|
||||
|
||||
public override bool CanWrite {
|
||||
get { return _innerStream.CanWrite; }
|
||||
}
|
||||
|
||||
public override long Length {
|
||||
get { return _innerStream.Length; }
|
||||
}
|
||||
|
||||
public override long Position {
|
||||
get { return _innerStream.Position; }
|
||||
set { _innerStream.Position = value; }
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin direction) {
|
||||
return _innerStream.Seek(offset, direction);
|
||||
}
|
||||
|
||||
public override void SetLength(long length) {
|
||||
_innerStream.SetLength(length);
|
||||
}
|
||||
|
||||
public override void Close() {
|
||||
_innerStream.Close();
|
||||
}
|
||||
|
||||
public override void Flush() {
|
||||
if (_captureStream.Length > 0) {
|
||||
OnCaptured(_captureStream);
|
||||
_captureStream.SetLength(0);
|
||||
}
|
||||
|
||||
_innerStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) {
|
||||
return _innerStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) {
|
||||
_captureStream.Write(buffer, 0, count);
|
||||
_innerStream.Write(buffer, offset, buffer.Length);
|
||||
}
|
||||
|
||||
public event Action<string> Captured;
|
||||
|
||||
protected virtual void OnCaptured(MemoryStream ms) {
|
||||
string content = HttpContext.Current.Response.ContentEncoding.GetString(ms.ToArray());
|
||||
Captured(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ using Orchard.Mvc.Extensions;
|
||||
using Orchard.Mvc.Filters;
|
||||
using Orchard.OutputCache.Models;
|
||||
using Orchard.OutputCache.Services;
|
||||
using Orchard.OutputCache.ViewModels;
|
||||
using Orchard.Services;
|
||||
using Orchard.Themes;
|
||||
using Orchard.UI.Admin;
|
||||
@@ -78,7 +77,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
private string _cacheKey;
|
||||
private string _invariantCacheKey;
|
||||
private bool _transformRedirect;
|
||||
private Func<ControllerContext, string> _completeResponseFunc;
|
||||
private bool _isCachingRequest;
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext filterContext) {
|
||||
|
||||
@@ -166,18 +165,13 @@ namespace Orchard.OutputCache.Filters {
|
||||
}
|
||||
|
||||
public void OnResultExecuted(ResultExecutedContext filterContext) {
|
||||
var response = filterContext.HttpContext.Response;
|
||||
var captureStream = response.Filter as CaptureStream;
|
||||
if (!_isCachingRequest || captureStream == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
string renderedOutput = null;
|
||||
if (_completeResponseFunc != null) {
|
||||
renderedOutput = _completeResponseFunc(filterContext);
|
||||
}
|
||||
|
||||
if (renderedOutput == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug("Item '{0}' was rendered.", _cacheKey);
|
||||
|
||||
// Obtain individual route configuration, if any.
|
||||
@@ -203,42 +197,47 @@ namespace Orchard.OutputCache.Filters {
|
||||
// Include each content item ID as tags for the cache entry.
|
||||
var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray();
|
||||
|
||||
var response = filterContext.HttpContext.Response;
|
||||
var cacheItem = new CacheItem() {
|
||||
CachedOnUtc = _now,
|
||||
Duration = cacheDuration,
|
||||
GraceTime = cacheGraceTime,
|
||||
Output = renderedOutput,
|
||||
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()
|
||||
};
|
||||
captureStream.Captured += (content) => {
|
||||
try {
|
||||
var cacheItem = new CacheItem() {
|
||||
CachedOnUtc = _now,
|
||||
Duration = cacheDuration,
|
||||
GraceTime = cacheGraceTime,
|
||||
Output = content,
|
||||
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);
|
||||
// Write the rendered item to the cache.
|
||||
_cacheStorageProvider.Remove(_cacheKey);
|
||||
_cacheStorageProvider.Set(_cacheKey, cacheItem);
|
||||
|
||||
Logger.Debug("Item '{0}' was written to cache.", _cacheKey);
|
||||
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.
|
||||
if (_cacheKey != null) {
|
||||
object cacheKeyLock;
|
||||
if (_cacheKeyLocks.TryGetValue(_cacheKey, out cacheKeyLock) && Monitor.IsEntered(cacheKeyLock)) {
|
||||
Logger.Debug("Releasing cache key lock for item '{0}'.", _cacheKey);
|
||||
Monitor.Exit(cacheKeyLock);
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
catch {
|
||||
// If an exception is caught, it means we were never able to attach the Captured
|
||||
// event handler to the CaptureStream instance, because attaching that handler is
|
||||
// the very last statement in the try block. Hence we can't assume that event
|
||||
// handler to release the cache key lock, and we should do it here.
|
||||
ReleaseCacheKeyLock();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,17 +463,8 @@ namespace Orchard.OutputCache.Filters {
|
||||
ApplyCacheControl(response);
|
||||
|
||||
// Intercept the rendered response output.
|
||||
var originalWriter = response.Output;
|
||||
var cachingWriter = new StringWriterWithEncoding(originalWriter.Encoding, originalWriter.FormatProvider);
|
||||
_completeResponseFunc = (ctx) => {
|
||||
ctx.HttpContext.Response.Output = originalWriter;
|
||||
string capturedText = cachingWriter.ToString();
|
||||
cachingWriter.Dispose();
|
||||
ctx.HttpContext.Response.Write(capturedText);
|
||||
return capturedText;
|
||||
};
|
||||
|
||||
response.Output = cachingWriter;
|
||||
response.Filter = new CaptureStream(response.Filter);
|
||||
_isCachingRequest = true;
|
||||
}
|
||||
|
||||
private void ApplyCacheControl(HttpResponseBase response) {
|
||||
@@ -517,6 +507,16 @@ namespace Orchard.OutputCache.Filters {
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseCacheKeyLock() {
|
||||
if (_cacheKey != null) {
|
||||
object cacheKeyLock;
|
||||
if (_cacheKeyLocks.TryGetValue(_cacheKey, out cacheKeyLock) && Monitor.IsEntered(cacheKeyLock)) {
|
||||
Logger.Debug("Releasing cache key lock for item '{0}'.", _cacheKey);
|
||||
Monitor.Exit(cacheKeyLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool IsIgnoredUrl(string url, IEnumerable<string> ignoredUrls) {
|
||||
if (ignoredUrls == null || !ignoredUrls.Any()) {
|
||||
return false;
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Controllers\StatisticsController.cs" />
|
||||
<Compile Include="Filters\CaptureStream.cs" />
|
||||
<Compile Include="Filters\OutputCacheFilter.cs" />
|
||||
<Compile Include="Handlers\CacheSettingsPartHandler.cs" />
|
||||
<Compile Include="Handlers\DisplayedContentItemHandler.cs" />
|
||||
|
||||
Reference in New Issue
Block a user