mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Fixing duplicated cached content when using HttpModule with specific envent
handlers --HG-- branch : 1.x
This commit is contained in:
@@ -11,7 +11,6 @@ using System.Web.Mvc;
|
|||||||
using System.Web.Routing;
|
using System.Web.Routing;
|
||||||
using Orchard.OutputCache.Models;
|
using Orchard.OutputCache.Models;
|
||||||
using Orchard.OutputCache.Services;
|
using Orchard.OutputCache.Services;
|
||||||
using Orchard;
|
|
||||||
using Orchard.Caching;
|
using Orchard.Caching;
|
||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
using Orchard.Environment.Configuration;
|
using Orchard.Environment.Configuration;
|
||||||
@@ -22,10 +21,8 @@ using Orchard.Themes;
|
|||||||
using Orchard.UI.Admin;
|
using Orchard.UI.Admin;
|
||||||
using Orchard.Utility.Extensions;
|
using Orchard.Utility.Extensions;
|
||||||
|
|
||||||
namespace Orchard.OutputCache.Filters
|
namespace Orchard.OutputCache.Filters {
|
||||||
{
|
public class OutputCacheFilter : FilterProvider, IActionFilter, IResultFilter {
|
||||||
public class OutputCacheFilter : FilterProvider, IActionFilter, IResultFilter
|
|
||||||
{
|
|
||||||
|
|
||||||
private readonly ICacheManager _cacheManager;
|
private readonly ICacheManager _cacheManager;
|
||||||
private readonly IOutputCacheStorageProvider _cacheStorageProvider;
|
private readonly IOutputCacheStorageProvider _cacheStorageProvider;
|
||||||
@@ -38,8 +35,6 @@ namespace Orchard.OutputCache.Filters
|
|||||||
private readonly ISignals _signals;
|
private readonly ISignals _signals;
|
||||||
private readonly ShellSettings _shellSettings;
|
private readonly ShellSettings _shellSettings;
|
||||||
|
|
||||||
private const string AntiforgeryBeacon = "<!--OutputCacheFilterAntiForgeryToken-->";
|
|
||||||
private const string AntiforgeryTag = "<input name=\"__RequestVerificationToken\" type=\"hidden\" value=\"";
|
|
||||||
private const string RefreshKey = "__r";
|
private const string RefreshKey = "__r";
|
||||||
|
|
||||||
public OutputCacheFilter(
|
public OutputCacheFilter(
|
||||||
@@ -52,8 +47,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
IClock clock,
|
IClock clock,
|
||||||
ICacheService cacheService,
|
ICacheService cacheService,
|
||||||
ISignals signals,
|
ISignals signals,
|
||||||
ShellSettings shellSettings)
|
ShellSettings shellSettings) {
|
||||||
{
|
|
||||||
_cacheManager = cacheManager;
|
_cacheManager = cacheManager;
|
||||||
_cacheStorageProvider = cacheStorageProvider;
|
_cacheStorageProvider = cacheStorageProvider;
|
||||||
_tagCache = tagCache;
|
_tagCache = tagCache;
|
||||||
@@ -87,15 +81,14 @@ namespace Orchard.OutputCache.Filters
|
|||||||
|
|
||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
|
|
||||||
public void OnActionExecuting(ActionExecutingContext filterContext)
|
public void OnActionExecuting(ActionExecutingContext filterContext) {
|
||||||
{
|
|
||||||
// use the action in the cacheKey so that the same route can't return cache for different actions
|
// use the action in the cacheKey so that the same route can't return cache for different actions
|
||||||
_actionName = filterContext.ActionDescriptor.ActionName;
|
_actionName = filterContext.ActionDescriptor.ActionName;
|
||||||
|
|
||||||
// apply OutputCacheAttribute logic if defined
|
// apply OutputCacheAttribute logic if defined
|
||||||
var outputCacheAttribute = filterContext.ActionDescriptor.GetCustomAttributes(typeof (OutputCacheAttribute), true).Cast<OutputCacheAttribute>().FirstOrDefault() ;
|
var outputCacheAttribute = filterContext.ActionDescriptor.GetCustomAttributes(typeof(OutputCacheAttribute), true).Cast<OutputCacheAttribute>().FirstOrDefault();
|
||||||
|
|
||||||
if(outputCacheAttribute != null) {
|
if (outputCacheAttribute != null) {
|
||||||
if (outputCacheAttribute.Duration <= 0 || outputCacheAttribute.NoStore) {
|
if (outputCacheAttribute.Duration <= 0 || outputCacheAttribute.NoStore) {
|
||||||
Logger.Debug("Request ignored based on OutputCache attribute");
|
Logger.Debug("Request ignored based on OutputCache attribute");
|
||||||
return;
|
return;
|
||||||
@@ -111,7 +104,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
Logger.Debug("Request on: " + filterContext.RequestContext.HttpContext.Request.RawUrl);
|
Logger.Debug("Request on: " + filterContext.RequestContext.HttpContext.Request.RawUrl);
|
||||||
|
|
||||||
// don't cache POST requests
|
// don't cache POST requests
|
||||||
if(filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) ) {
|
if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) {
|
||||||
Logger.Debug("Request ignored on POST");
|
Logger.Debug("Request ignored on POST");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -123,7 +116,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ignore child actions, e.g. HomeController is using RenderAction()
|
// ignore child actions, e.g. HomeController is using RenderAction()
|
||||||
if (filterContext.IsChildAction){
|
if (filterContext.IsChildAction) {
|
||||||
Logger.Debug("Request ignored on Child actions");
|
Logger.Debug("Request ignored on Child actions");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,9 +149,9 @@ namespace Orchard.OutputCache.Filters
|
|||||||
context => {
|
context => {
|
||||||
context.Monitor(_signals.When(CacheSettingsPart.CacheKey));
|
context.Monitor(_signals.When(CacheSettingsPart.CacheKey));
|
||||||
var varyQueryStringParameters = _workContext.CurrentSite.As<CacheSettingsPart>().VaryQueryStringParameters;
|
var varyQueryStringParameters = _workContext.CurrentSite.As<CacheSettingsPart>().VaryQueryStringParameters;
|
||||||
|
|
||||||
return string.IsNullOrWhiteSpace(varyQueryStringParameters) ? null
|
return string.IsNullOrWhiteSpace(varyQueryStringParameters) ? null
|
||||||
: varyQueryStringParameters.Split(new[]{","}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
: varyQueryStringParameters.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -189,7 +182,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
var queryString = filterContext.RequestContext.HttpContext.Request.QueryString;
|
var queryString = filterContext.RequestContext.HttpContext.Request.QueryString;
|
||||||
var parameters = new Dictionary<string, object>(filterContext.ActionParameters);
|
var parameters = new Dictionary<string, object>(filterContext.ActionParameters);
|
||||||
|
|
||||||
foreach(var key in queryString.AllKeys) {
|
foreach (var key in queryString.AllKeys) {
|
||||||
if (key == null) continue;
|
if (key == null) continue;
|
||||||
|
|
||||||
parameters[key] = queryString[key];
|
parameters[key] = queryString[key];
|
||||||
@@ -200,7 +193,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
|
|
||||||
// create a tag which doesn't care about querystring
|
// create a tag which doesn't care about querystring
|
||||||
_invariantCacheKey = ComputeCacheKey(filterContext, null);
|
_invariantCacheKey = ComputeCacheKey(filterContext, null);
|
||||||
|
|
||||||
// don't retrieve cache content if refused
|
// don't retrieve cache content if refused
|
||||||
// in this case the result of the action will update the current cached version
|
// in this case the result of the action will update the current cached version
|
||||||
if (filterContext.RequestContext.HttpContext.Request.Headers["Cache-Control"] != "no-cache") {
|
if (filterContext.RequestContext.HttpContext.Request.Headers["Cache-Control"] != "no-cache") {
|
||||||
@@ -219,46 +212,20 @@ namespace Orchard.OutputCache.Filters
|
|||||||
var response = filterContext.HttpContext.Response;
|
var response = filterContext.HttpContext.Response;
|
||||||
|
|
||||||
// render cached content
|
// render cached content
|
||||||
if (_cacheItem != null)
|
if (_cacheItem != null) {
|
||||||
{
|
|
||||||
Logger.Debug("Cache item found, expires on " + _cacheItem.ValidUntilUtc);
|
Logger.Debug("Cache item found, expires on " + _cacheItem.ValidUntilUtc);
|
||||||
|
|
||||||
var output = _cacheItem.Output;
|
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
|
// adds some caching information to the output if requested
|
||||||
if (_debugMode)
|
if (_debugMode) {
|
||||||
{
|
|
||||||
output += "\r\n<!-- Cached on " + _cacheItem.CachedOnUtc + " (UTC) until " + _cacheItem.ValidUntilUtc + " (UTC) -->";
|
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-On", _cacheItem.CachedOnUtc.ToString("r"));
|
||||||
response.AddHeader("X-Cached-Until", _cacheItem.ValidUntilUtc.ToString("r"));
|
response.AddHeader("X-Cached-Until", _cacheItem.ValidUntilUtc.ToString("r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
filterContext.Result = new ContentResult
|
// shorcut action execution
|
||||||
{
|
filterContext.Result = new ContentResult {
|
||||||
Content = output,
|
Content = output,
|
||||||
ContentType = _cacheItem.ContentType
|
ContentType = _cacheItem.ContentType
|
||||||
};
|
};
|
||||||
@@ -288,7 +255,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||||
filterContext.HttpContext.Response.Cache.SetNoStore();
|
filterContext.HttpContext.Response.Cache.SetNoStore();
|
||||||
filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0));
|
filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0));
|
||||||
|
|
||||||
_filter = null;
|
_filter = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -310,6 +277,8 @@ namespace Orchard.OutputCache.Filters
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: look for RedirectToRoute to, or intercept 302s
|
||||||
|
|
||||||
if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)
|
if (filterContext.HttpContext.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)
|
||||||
&& filterContext.Result is RedirectResult) {
|
&& filterContext.Result is RedirectResult) {
|
||||||
|
|
||||||
@@ -359,8 +328,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResultExecuted(ResultExecutedContext filterContext)
|
public void OnResultExecuted(ResultExecutedContext filterContext) {
|
||||||
{
|
|
||||||
var response = filterContext.HttpContext.Response;
|
var response = filterContext.HttpContext.Response;
|
||||||
|
|
||||||
// save the result only if the content can be intercepted
|
// save the result only if the content can be intercepted
|
||||||
@@ -379,40 +347,27 @@ namespace Orchard.OutputCache.Filters
|
|||||||
var configuration = configurations.FirstOrDefault(c => c.RouteKey == key);
|
var configuration = configurations.FirstOrDefault(c => c.RouteKey == key);
|
||||||
|
|
||||||
// do not cache ?
|
// do not cache ?
|
||||||
if (configuration != null && configuration.Duration == 0)
|
if (configuration != null && configuration.Duration == 0) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignored url ?
|
// ignored url ?
|
||||||
if (IsIgnoredUrl(filterContext.RequestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath, _ignoredUrls))
|
if (IsIgnoredUrl(filterContext.RequestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath, _ignoredUrls)) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(response.IsClientConnected)
|
// flush here to force the Filter to get the rendered content
|
||||||
|
if (response.IsClientConnected)
|
||||||
response.Flush();
|
response.Flush();
|
||||||
|
|
||||||
var output = _filter.GetContents(response.ContentEncoding);
|
var output = _filter.GetContents(response.ContentEncoding);
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(output))
|
if (String.IsNullOrWhiteSpace(output)) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenIndex = output.IndexOf(AntiforgeryTag, StringComparison.Ordinal);
|
response.Filter = null;
|
||||||
|
response.Write(output);
|
||||||
// substitute antiforgery token by a beacon
|
|
||||||
if (tokenIndex != -1)
|
|
||||||
{
|
|
||||||
var tokenEnd = output.IndexOf(">", tokenIndex, StringComparison.Ordinal);
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.Append(output.Substring(0, tokenIndex));
|
|
||||||
sb.Append(AntiforgeryBeacon);
|
|
||||||
sb.Append(output.Substring(tokenEnd + 1));
|
|
||||||
|
|
||||||
output = sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// default duration of specific one ?
|
// default duration of specific one ?
|
||||||
var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration;
|
var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : _cacheDuration;
|
||||||
@@ -442,13 +397,12 @@ namespace Orchard.OutputCache.Filters
|
|||||||
|
|
||||||
// add to the tags index
|
// add to the tags index
|
||||||
foreach (var tag in _cacheItem.Tags) {
|
foreach (var tag in _cacheItem.Tags) {
|
||||||
_tagCache.Tag(tag, _cacheKey);
|
_tagCache.Tag(tag, _cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResultExecuting(ResultExecutingContext filterContext)
|
public void OnResultExecuting(ResultExecutingContext filterContext) {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -468,7 +422,7 @@ namespace Orchard.OutputCache.Filters
|
|||||||
// an ETag is a string that uniquely identifies a specific version of a component.
|
// 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
|
// we use the cache item to detect if it's a new one
|
||||||
response.Cache.SetETag(cacheItem.GetHashCode().ToString(CultureInfo.InvariantCulture));
|
response.Cache.SetETag(cacheItem.GetHashCode().ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
response.Cache.SetOmitVaryStar(true);
|
response.Cache.SetOmitVaryStar(true);
|
||||||
|
|
||||||
if (_varyQueryStringParameters != null) {
|
if (_varyQueryStringParameters != null) {
|
||||||
@@ -489,25 +443,25 @@ namespace Orchard.OutputCache.Filters
|
|||||||
// create a unique cache per browser, in case a Theme is rendered differently (e.g., mobile)
|
// 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://msdn.microsoft.com/en-us/library/aa478965.aspx
|
||||||
// c.f. http://stackoverflow.com/questions/6007287/outputcache-varybyheader-user-agent-or-varybycustom-browser
|
// c.f. http://stackoverflow.com/questions/6007287/outputcache-varybyheader-user-agent-or-varybycustom-browser
|
||||||
response.Cache.SetVaryByCustom("browser");
|
response.Cache.SetVaryByCustom("browser");
|
||||||
|
|
||||||
// enabling this would create an entry for each different browser sub-version
|
// enabling this would create an entry for each different browser sub-version
|
||||||
// response.Cache.VaryByHeaders.UserAgent = true;
|
// response.Cache.VaryByHeaders.UserAgent = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ComputeCacheKey(ControllerContext controllerContext, IEnumerable<KeyValuePair<string, object>> parameters) {
|
private string ComputeCacheKey(ControllerContext controllerContext, IEnumerable<KeyValuePair<string, object>> parameters) {
|
||||||
var url = controllerContext.HttpContext.Request.RawUrl;
|
var url = controllerContext.HttpContext.Request.RawUrl;
|
||||||
if(!VirtualPathUtility.IsAbsolute(url)) {
|
if (!VirtualPathUtility.IsAbsolute(url)) {
|
||||||
var applicationRoot = controllerContext.HttpContext.Request.ToRootUrlString();
|
var applicationRoot = controllerContext.HttpContext.Request.ToRootUrlString();
|
||||||
if(url.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) {
|
if (url.StartsWith(applicationRoot, StringComparison.OrdinalIgnoreCase)) {
|
||||||
url = url.Substring(applicationRoot.Length);
|
url = url.Substring(applicationRoot.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ComputeCacheKey(_shellSettings.Name, url, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(controllerContext.RequestContext).Id, parameters);
|
return ComputeCacheKey(_shellSettings.Name, url, () => _workContext.CurrentCulture, _themeManager.GetRequestTheme(controllerContext.RequestContext).Id, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ComputeCacheKey(string tenant, string absoluteUrl, Func<string> culture, string theme, IEnumerable<KeyValuePair<string, object>> parameters){
|
private string ComputeCacheKey(string tenant, string absoluteUrl, Func<string> culture, string theme, IEnumerable<KeyValuePair<string, object>> parameters) {
|
||||||
var keyBuilder = new StringBuilder();
|
var keyBuilder = new StringBuilder();
|
||||||
|
|
||||||
keyBuilder.Append("tenant=").Append(tenant).Append(";");
|
keyBuilder.Append("tenant=").Append(tenant).Append(";");
|
||||||
@@ -537,43 +491,36 @@ namespace Orchard.OutputCache.Filters
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the given url should be ignored, as defined in the settings
|
/// Returns true if the given url should be ignored, as defined in the settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static bool IsIgnoredUrl(string url, string ignoredUrls)
|
private static bool IsIgnoredUrl(string url, string ignoredUrls) {
|
||||||
{
|
if (String.IsNullOrEmpty(ignoredUrls)) {
|
||||||
if(String.IsNullOrEmpty(ignoredUrls))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove ~ if present
|
// remove ~ if present
|
||||||
if(url.StartsWith("~")) {
|
if (url.StartsWith("~")) {
|
||||||
url = url.Substring(1);
|
url = url.Substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var urlReader = new StringReader(ignoredUrls))
|
using (var urlReader = new StringReader(ignoredUrls)) {
|
||||||
{
|
|
||||||
string relativePath;
|
string relativePath;
|
||||||
while (null != (relativePath = urlReader.ReadLine()))
|
while (null != (relativePath = urlReader.ReadLine())) {
|
||||||
{
|
|
||||||
// remove ~ if present
|
// remove ~ if present
|
||||||
if (relativePath.StartsWith("~")) {
|
if (relativePath.StartsWith("~")) {
|
||||||
relativePath = relativePath.Substring(1);
|
relativePath = relativePath.Substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(relativePath))
|
if (String.IsNullOrWhiteSpace(relativePath)) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
relativePath = relativePath.Trim();
|
relativePath = relativePath.Trim();
|
||||||
|
|
||||||
// ignore comments
|
// ignore comments
|
||||||
if(relativePath.StartsWith("#"))
|
if (relativePath.StartsWith("#")) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(String.Equals(relativePath, url, StringComparison.OrdinalIgnoreCase))
|
if (String.Equals(relativePath, url, StringComparison.OrdinalIgnoreCase)) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,92 +530,86 @@ namespace Orchard.OutputCache.Filters
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Captures the response stream while writing to it
|
/// Captures the response stream while writing to it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CapturingResponseFilter : Stream
|
public class CapturingResponseFilter : Stream {
|
||||||
{
|
// private readonly Stream _sink;
|
||||||
private readonly Stream _sink;
|
|
||||||
private readonly MemoryStream _mem;
|
private readonly MemoryStream _mem;
|
||||||
|
|
||||||
public CapturingResponseFilter(Stream sink)
|
public CapturingResponseFilter(Stream sink) {
|
||||||
{
|
// _sink = sink;
|
||||||
_sink = sink;
|
|
||||||
_mem = new MemoryStream();
|
_mem = new MemoryStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following members of Stream must be overriden.
|
// The following members of Stream must be overriden.
|
||||||
public override bool CanRead
|
public override bool CanRead {
|
||||||
{
|
|
||||||
get { return true; }
|
get { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanSeek
|
public override bool CanSeek {
|
||||||
{
|
|
||||||
get { return false; }
|
get { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite
|
public override bool CanWrite {
|
||||||
{
|
|
||||||
get { return false; }
|
get { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Length
|
public override long Length {
|
||||||
{
|
|
||||||
get { return 0; }
|
get { return 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Position { get; set; }
|
public override long Position { get; set; }
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin direction)
|
public override long Seek(long offset, SeekOrigin direction) {
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetLength(long length)
|
public override void SetLength(long length) {
|
||||||
{
|
// _sink.SetLength(length);
|
||||||
_sink.SetLength(length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Close()
|
public override void Close() {
|
||||||
{
|
// _sink.Close();
|
||||||
_sink.Close();
|
|
||||||
_mem.Close();
|
_mem.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush() {
|
||||||
{
|
// _sink.Flush();
|
||||||
_sink.Flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count) {
|
||||||
{
|
// return _sink.Read(buffer, offset, count);
|
||||||
return _sink.Read(buffer, offset, count);
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the Write method to filter Response to a file.
|
// Override the Write method to filter Response to a file.
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count) {
|
||||||
{
|
|
||||||
//Here we will not write to the sink b/c we want to capture
|
//Here we will not write to the sink b/c we want to capture
|
||||||
_sink.Write(buffer, offset, count);
|
// _sink.Write(buffer, offset, count);
|
||||||
|
|
||||||
//Write out the response to the file.
|
//Write out the response to the file.
|
||||||
_mem.Write(buffer, 0, count);
|
_mem.Write(buffer, 0, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetContents(Encoding enc)
|
public string GetContents(Encoding enc) {
|
||||||
{
|
|
||||||
var buffer = new byte[_mem.Length];
|
var buffer = new byte[_mem.Length];
|
||||||
_mem.Position = 0;
|
_mem.Position = 0;
|
||||||
_mem.Read(buffer, 0, buffer.Length);
|
_mem.Read(buffer, 0, buffer.Length);
|
||||||
return enc.GetString(buffer, 0, buffer.Length);
|
return enc.GetString(buffer, 0, buffer.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] GetContents() {
|
||||||
|
return _mem.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing) {
|
||||||
|
_mem.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ViewDataContainer : IViewDataContainer
|
public class ViewDataContainer : IViewDataContainer {
|
||||||
{
|
|
||||||
public ViewDataDictionary ViewData { get; set; }
|
public ViewDataDictionary ViewData { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -254,11 +254,11 @@
|
|||||||
<VisualStudio>
|
<VisualStudio>
|
||||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||||
<WebProjectProperties>
|
<WebProjectProperties>
|
||||||
<UseIIS>False</UseIIS>
|
<UseIIS>True</UseIIS>
|
||||||
<AutoAssignPort>False</AutoAssignPort>
|
<AutoAssignPort>False</AutoAssignPort>
|
||||||
<DevelopmentServerPort>30320</DevelopmentServerPort>
|
<DevelopmentServerPort>30321</DevelopmentServerPort>
|
||||||
<DevelopmentServerVPath>/OrchardLocal</DevelopmentServerVPath>
|
<DevelopmentServerVPath>/OrchardLocal</DevelopmentServerVPath>
|
||||||
<IISUrl>http://localhost:30320/OrchardLocal</IISUrl>
|
<IISUrl>http://localhost:30321/OrchardLocal</IISUrl>
|
||||||
<NTLMAuthentication>False</NTLMAuthentication>
|
<NTLMAuthentication>False</NTLMAuthentication>
|
||||||
<UseCustomServer>False</UseCustomServer>
|
<UseCustomServer>False</UseCustomServer>
|
||||||
<CustomServerUrl>
|
<CustomServerUrl>
|
||||||
|
Reference in New Issue
Block a user