diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Web.config b/src/Orchard.Azure/Orchard.Azure.Web/Web.config index a9c88f401..2e1e38ea0 100644 --- a/src/Orchard.Azure/Orchard.Azure.Web/Web.config +++ b/src/Orchard.Azure/Orchard.Azure.Web/Web.config @@ -111,7 +111,9 @@ - + + + diff --git a/src/Orchard.Tests.Modules/DesignerTools/Services/ObjectDumperTests.cs b/src/Orchard.Tests.Modules/DesignerTools/Services/ObjectDumperTests.cs index 7612bd0db..e0ea0bfa4 100644 --- a/src/Orchard.Tests.Modules/DesignerTools/Services/ObjectDumperTests.cs +++ b/src/Orchard.Tests.Modules/DesignerTools/Services/ObjectDumperTests.cs @@ -273,7 +273,11 @@ namespace Orchard.Tests.Modules.DesignerTools.Services new JProperty("value", "null")), new JObject( new JProperty("name", "TypePartDefinition"), - new JProperty("value", "ContentTypePartDefinition")), + new JProperty("value", "ContentTypePartDefinition"), + new JProperty("children", new JArray( + new JObject( + new JProperty("name", "ContentTypeDefinition"), + new JProperty("value", "null"))))), new JObject( new JProperty("name", "PartDefinition"), new JProperty("value", "ContentPartDefinition"), @@ -349,7 +353,10 @@ namespace Orchard.Tests.Modules.DesignerTools.Services new JProperty("value", "SettingsDictionary"))))), new JObject( new JProperty("name", "Settings"), - new JProperty("value", "SettingsDictionary"))))), + new JProperty("value", "SettingsDictionary")), + new JObject( + new JProperty("name", "ContentTypeDefinition"), + new JProperty("value", "null"))))), new JObject( new JProperty("name", "PartDefinition"), new JProperty("value", "ContentPartDefinition"), diff --git a/src/Orchard.Tests.Modules/Widgets/RuleEngine/UrlRuleProviderTest.cs b/src/Orchard.Tests.Modules/Widgets/RuleEngine/UrlRuleProviderTest.cs index 68026c556..f70dd00b6 100644 --- a/src/Orchard.Tests.Modules/Widgets/RuleEngine/UrlRuleProviderTest.cs +++ b/src/Orchard.Tests.Modules/Widgets/RuleEngine/UrlRuleProviderTest.cs @@ -1,4 +1,5 @@ -using Autofac; +using System; +using Autofac; using NUnit.Framework; using Orchard.Environment.Configuration; using Orchard.Mvc; @@ -17,7 +18,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [SetUp] public void Init() { var builder = new ContainerBuilder(); - _shellSettings = new ShellSettings {RequestUrlPrefix = string.Empty}; + _shellSettings = new ShellSettings { RequestUrlPrefix = String.Empty }; builder.RegisterType().As(); builder.RegisterInstance(_shellSettings); _stubContextAccessor = new StubHttpContextAccessor(); @@ -28,15 +29,15 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForHomePageMatchesHomePagePath() { - _stubContextAccessor.StubContext = new StubHttpContext("~/"); - var context = new RuleContext {FunctionName = "url", Arguments = new[] {"~/"}}; + _stubContextAccessor.Set(new StubHttpContext("~/")); + var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.True); } [Test] public void UrlForAboutPageMatchesAboutPagePath() { - _stubContextAccessor.StubContext = new StubHttpContext("~/about"); + _stubContextAccessor.Set(new StubHttpContext("~/about")); var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/about" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.True); @@ -44,7 +45,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForBlogWithEndingWildcardMatchesBlogPostPageInSaidBlog() { - _stubContextAccessor.StubContext = new StubHttpContext("~/my-blog/my-blog-post"); + _stubContextAccessor.Set(new StubHttpContext("~/my-blog/my-blog-post")); var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/my-blog/*" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.True); @@ -52,7 +53,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForHomePageDoesNotMatchAboutPagePath() { - _stubContextAccessor.StubContext = new StubHttpContext("~/about"); + _stubContextAccessor.Set(new StubHttpContext("~/about")); var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.False); @@ -60,7 +61,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForAboutPageMatchesDifferentCasedAboutPagePath() { - _stubContextAccessor.StubContext = new StubHttpContext("~/About"); + _stubContextAccessor.Set(new StubHttpContext("~/About")); var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/about" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.True); @@ -68,7 +69,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForAboutPageWithEndingSlashMatchesAboutPagePath() { - _stubContextAccessor.StubContext = new StubHttpContext("~/About/"); + _stubContextAccessor.Set(new StubHttpContext("~/About/")); var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/about" } }; _urlRuleProvider.Process(context); Assert.That(context.Result, Is.True); @@ -76,7 +77,7 @@ namespace Orchard.Tests.Modules.Widgets.RuleEngine { [Test] public void UrlForHomePageMatchesHomePagePathWithUrlPrefix() { - _stubContextAccessor.StubContext = new StubHttpContext("~/site1"); + _stubContextAccessor.Set(new StubHttpContext("~/site1")); _shellSettings.RequestUrlPrefix = "site1"; var context = new RuleContext { FunctionName = "url", Arguments = new[] { "~/" } }; _urlRuleProvider.Process(context); diff --git a/src/Orchard.Tests/Localization/CultureManagerTests.cs b/src/Orchard.Tests/Localization/CultureManagerTests.cs index 01d9e1ff2..1b00df833 100644 --- a/src/Orchard.Tests/Localization/CultureManagerTests.cs +++ b/src/Orchard.Tests/Localization/CultureManagerTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Web; using Autofac; using Moq; using NHibernate; @@ -17,7 +16,6 @@ using Orchard.DisplayManagement.Implementation; using Orchard.Environment; using Orchard.Localization.Records; using Orchard.Localization.Services; -using Orchard.Mvc; using Orchard.Security; using Orchard.Tests.ContentManagement; using Orchard.Tests.Stubs; @@ -31,10 +29,11 @@ namespace Orchard.Tests.Localization { private ISessionFactory _sessionFactory; private ISession _session; private string _databaseFileName; + private StubWorkContext _stubWorkContext; [TestFixtureSetUp] public void InitFixture() { - _databaseFileName = System.IO.Path.GetTempFileName(); + _databaseFileName = Path.GetTempFileName(); _sessionFactory = DataUtility.CreateSessionFactory( _databaseFileName, typeof(CultureRecord)); @@ -43,6 +42,7 @@ namespace Orchard.Tests.Localization { [SetUp] public void Init() { var builder = new ContainerBuilder(); + _stubWorkContext = new StubWorkContext(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); @@ -50,12 +50,11 @@ namespace Orchard.Tests.Localization { builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); - builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterInstance(_stubWorkContext); + builder.RegisterInstance(new StubWorkContextAccessor(_stubWorkContext)).As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); @@ -106,15 +105,9 @@ namespace Orchard.Tests.Localization { } [Test] - public void CultureManagerReturnsCultureFromSelector() { - Assert.That(_cultureManager.GetCurrentCulture(null), Is.EqualTo("en-US")); + public void CultureManagerReturnsCultureFromWorkContext() { + _stubWorkContext.CultureName = "nl-NL"; + Assert.That(_cultureManager.GetCurrentCulture(null), Is.EqualTo("nl-NL")); } } - - public class TestCultureSelector : ICultureSelector { - public CultureSelectorResult GetCulture(HttpContextBase context) { - return new CultureSelectorResult { Priority = 1, CultureName = "en-US" }; - } - } -} - +} \ No newline at end of file diff --git a/src/Orchard.Tests/Localization/CurrentCultureWorkContextTests.cs b/src/Orchard.Tests/Localization/CurrentCultureWorkContextTests.cs new file mode 100644 index 000000000..053595295 --- /dev/null +++ b/src/Orchard.Tests/Localization/CurrentCultureWorkContextTests.cs @@ -0,0 +1,34 @@ +using Autofac; +using NUnit.Framework; +using Orchard.Localization.Services; +using Orchard.Mvc; +using Orchard.Tests.Stubs; + +namespace Orchard.Tests.Localization { + [TestFixture] + public class CurrentCultureWorkContextTests { + private IContainer _container; + private IWorkContextStateProvider _currentCultureStateProvider; + private WorkContext _workContext; + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + _workContext = new StubWorkContext(); + builder.RegisterInstance(new StubCultureSelector("or-CH")).As(); + builder.RegisterInstance(new StubHttpContext("~/")); + builder.RegisterInstance(_workContext); + builder.RegisterType().As(); + builder.RegisterType().As(); + _container = builder.Build(); + _currentCultureStateProvider = _container.Resolve(); + } + + [Test] + public void CultureManagerReturnsCultureFromSelectors() { + var actualCulture = _currentCultureStateProvider.Get("CurrentCulture")(_workContext); + var expectedCulture = "or-CH"; + Assert.That(actualCulture, Is.EqualTo(expectedCulture)); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Localization/TestHelpers.cs b/src/Orchard.Tests/Localization/TestHelpers.cs index 3c3ed038d..d9d268762 100644 --- a/src/Orchard.Tests/Localization/TestHelpers.cs +++ b/src/Orchard.Tests/Localization/TestHelpers.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Web; using Autofac; using Moq; using Orchard.Localization.Services; @@ -28,16 +25,19 @@ namespace Orchard.Tests.Localization { internal class StubWorkContext : WorkContext { - private string _cultureName; - private string _calendarName; - private TimeZoneInfo _timeZone; + public StubWorkContext() { + } public StubWorkContext(string cultureName, string calendarName, TimeZoneInfo timeZone) { - _cultureName = cultureName; - _calendarName = calendarName; - _timeZone = timeZone; + CultureName = cultureName; + CalendarName = calendarName; + TimeZone = timeZone; } + public string CultureName { get; set; } + public string CalendarName { get; set; } + public TimeZoneInfo TimeZone { get; set; } + public override T Resolve() { throw new NotImplementedException(); } @@ -47,9 +47,9 @@ namespace Orchard.Tests.Localization { } public override T GetState(string name) { - if (name == "CurrentCulture") return (T)((object)_cultureName); - if (name == "CurrentCalendar") return (T)((object)_calendarName); - if (name == "CurrentTimeZone") return (T)((object)_timeZone); + if (name == "CurrentCulture") return (T)((object)CultureName); + if (name == "CurrentCalendar") return (T)((object)CalendarName); + if (name == "CurrentTimeZone") return (T)((object)TimeZone); throw new NotImplementedException(String.Format("Property '{0}' is not implemented.", name)); } @@ -60,17 +60,17 @@ namespace Orchard.Tests.Localization { internal class StubWorkContextAccessor : IWorkContextAccessor { - private WorkContext _workContext; + private readonly WorkContext _workContext; public StubWorkContextAccessor(WorkContext workContext) { _workContext = workContext; } - public WorkContext GetContext(System.Web.HttpContextBase httpContext) { + public WorkContext GetContext(HttpContextBase httpContext) { throw new NotImplementedException(); } - public IWorkContextScope CreateWorkContextScope(System.Web.HttpContextBase httpContext) { + public IWorkContextScope CreateWorkContextScope(HttpContextBase httpContext) { throw new NotImplementedException(); } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index f3bcce026..8f62cd7b0 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -259,10 +259,12 @@ + + diff --git a/src/Orchard.Tests/Stubs/StubCultureSelector.cs b/src/Orchard.Tests/Stubs/StubCultureSelector.cs new file mode 100644 index 000000000..493576576 --- /dev/null +++ b/src/Orchard.Tests/Stubs/StubCultureSelector.cs @@ -0,0 +1,16 @@ +using System.Web; +using Orchard.Localization.Services; + +namespace Orchard.Tests.Stubs { + public class StubCultureSelector : ICultureSelector { + private readonly string _cultureName; + + public StubCultureSelector(string cultureName) { + _cultureName = cultureName; + } + + public CultureSelectorResult GetCulture(HttpContextBase context) { + return new CultureSelectorResult { Priority = 1, CultureName = _cultureName }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Stubs/StubHttpContextAccessor.cs b/src/Orchard.Tests/Stubs/StubHttpContextAccessor.cs index 347599686..892707b2f 100644 --- a/src/Orchard.Tests/Stubs/StubHttpContextAccessor.cs +++ b/src/Orchard.Tests/Stubs/StubHttpContextAccessor.cs @@ -5,16 +5,19 @@ namespace Orchard.Tests.Stubs { public class StubHttpContextAccessor : IHttpContextAccessor { private HttpContextBase _httpContext; - public HttpContextBase StubContext { - set { _httpContext = value; } + public StubHttpContextAccessor() { + } + + public StubHttpContextAccessor(HttpContextBase httpContext) { + _httpContext = httpContext; } public HttpContextBase Current() { return _httpContext; } - public void Set(HttpContextBase stub) { - _httpContext = stub; + public void Set(HttpContextBase httpContext) { + _httpContext = httpContext; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutDecoder.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutDecoder.js index 4338cc231..620d84576 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutDecoder.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutDecoder.js @@ -1,34 +1,10 @@ var LayoutEditor; (function ($, LayoutEditor) { - var decode = function(value) { + var decode = function (value) { return !!value ? decodeURIComponent(value.replace(/\+/g, "%20")) : null; }; - var decodeGraph = function (graph) { - - if (!!graph.html) { - graph.html = decode(graph.html); - } - - if (!!graph.data) { - var items = $.deserialize(graph.data); - - for (var i = 0; i < items.length; i++) { - items[i] = decode(items[i]); - } - - graph.data = $.param(items); - } - - if (!!graph.children) { - for (var i = 0; i < graph.children.length; i++) { - decodeGraph(graph.children[i]); - } - } - }; - LayoutEditor.decode = decode; - LayoutEditor.decodeLayoutGraph = decodeGraph; })(jQuery, LayoutEditor || (LayoutEditor = {})); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Services/ContentDisplayBase.cs b/src/Orchard.Web/Modules/Orchard.Layouts/Services/ContentDisplayBase.cs index fd5622254..1e15899f7 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Services/ContentDisplayBase.cs +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Services/ContentDisplayBase.cs @@ -13,14 +13,14 @@ namespace Orchard.Layouts.Services { // Consider combining this class with DefaultContentDisplay to reuse shared code, for example by inheriting from a common base class. public abstract class ContentDisplayBase : Component { private readonly IShapeFactory _shapeFactory; - private readonly Lazy _shapeTableLocator; + private readonly Lazy _shapeTableLocator; private readonly RequestContext _requestContext; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IWorkContextAccessor _workContextAccessor; protected ContentDisplayBase( IShapeFactory shapeFactory, - Lazy shapeTableLocator, + Lazy shapeTableLocator, RequestContext requestContext, IVirtualPathProvider virtualPathProvider, IWorkContextAccessor workContextAccessor) { @@ -109,9 +109,9 @@ namespace Orchard.Layouts.Services { private void BindPlacement(BuildShapeContext context, string displayType, string stereotype) { context.FindPlacement = (partShapeType, differentiator, defaultLocation) => { var workContext = _workContextAccessor.GetContext(_requestContext.HttpContext); - var theme = workContext.CurrentTheme; - var shapeTable = _shapeTableLocator.Value.Lookup(theme.Id); - var request = _requestContext.HttpContext.Request; + var shapeTable = workContext.HttpContext != null + ? _shapeTableLocator.Value.Lookup(workContext.CurrentTheme.Id) + : _shapeTableLocator.Value.Lookup(null); ShapeDescriptor descriptor; if (shapeTable.Descriptors.TryGetValue(partShapeType, out descriptor)) { diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Views/EditorTemplates/LayoutEditor.cshtml b/src/Orchard.Web/Modules/Orchard.Layouts/Views/EditorTemplates/LayoutEditor.cshtml index fa9f76cc4..b81e2bb6f 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Views/EditorTemplates/LayoutEditor.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Views/EditorTemplates/LayoutEditor.cshtml @@ -43,10 +43,9 @@ } }); (function() { - var editorConfig = @Html.Raw(Model.ConfigurationData); - var editorCanvasData = @Html.Raw(Model.Data); + var editorConfig = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.ConfigurationData))")); + var editorCanvasData = JSON.parse(LayoutEditor.decode("@Html.Raw(Url.Encode(Model.Data))")); - LayoutEditor.decodeLayoutGraph(editorCanvasData); window.layoutEditor = new LayoutEditor.Editor(editorConfig, editorCanvasData); })(jQuery); diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Views/ElementEditor.cshtml b/src/Orchard.Web/Modules/Orchard.Layouts/Views/ElementEditor.cshtml index aa80bdee5..4a1fab303 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Views/ElementEditor.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Views/ElementEditor.cshtml @@ -43,11 +43,9 @@ html: LayoutEditor.decode("@Url.Encode(Model.ElementHtml)"), isTemplated: false }, - elementEditorModel: @Html.Raw(Model.ElementEditorModel.ToJson()) + elementEditorModel: LayoutEditor.decode("@Html.Raw(Url.Encode(Model.ElementEditorModel.ToJson()))") }; - LayoutEditor.decodeLayoutGraph(payload.element); - LayoutEditor.decodeLayoutGraph(payload.elementEditorModel); window.parent.currentDialog.trigger("command", payload); }); diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/DatabaseOutputCacheMigrations.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/DatabaseOutputCacheMigrations.cs index b2cadf867..19b439882 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/DatabaseOutputCacheMigrations.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/DatabaseOutputCacheMigrations.cs @@ -16,7 +16,7 @@ namespace Contrib.Cache.Database { .Column("GraceTime", c => c.Nullable()) .Column("ValidUntilUtc") .Column("StoredUntilUtc") - .Column("Output", column => column.Unlimited()) + .Column("Output", column => column.Unlimited().WithType(DbType.Binary)) .Column("ContentType") .Column("QueryString", column => column.WithLength(2048)) .Column("CacheKey", column => column.WithLength(2048)) @@ -31,7 +31,7 @@ namespace Contrib.Cache.Database { .CreateIndex("IDX_CacheItemRecord_CacheKey", "CacheKey") ); - return 2; + return 3; } public int UpdateFrom1() { @@ -45,5 +45,14 @@ namespace Contrib.Cache.Database { return 2; } + + public int UpdateFrom2() { + SchemaBuilder.AlterTable("CacheItemRecord", + table => { + table.AlterColumn("Output", c => c.Unlimited().WithType(DbType.Binary)); + }); + + return 3; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CaptureStream.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CaptureStream.cs new file mode 100644 index 000000000..3f2c3addd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CaptureStream.cs @@ -0,0 +1,75 @@ +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.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, offset, count); + _innerStream.Write(buffer, offset, count); + } + + public event Action Captured; + + protected virtual void OnCaptured() { + Captured(_captureStream.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs index 8a8871eef..f93e4c855 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs @@ -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,12 +77,19 @@ namespace Orchard.OutputCache.Filters { private string _cacheKey; private string _invariantCacheKey; private bool _transformRedirect; - private Func _completeResponseFunc; + private bool _isCachingRequest; public void OnActionExecuting(ActionExecutingContext filterContext) { Logger.Debug("Incoming request for URL '{0}'.", filterContext.RequestContext.HttpContext.Request.RawUrl); + // This filter is not reentrant (multiple executions within the same request are + // not supported) so child actions are ignored completely. + if (filterContext.IsChildAction) { + Logger.Debug("Action '{0}' ignored because it's a child action.", filterContext.ActionDescriptor.ActionName); + return; + } + _now = _clock.UtcNow; _workContext = _workContextAccessor.GetContext(); _cacheKey = ComputeCacheKey(filterContext, GetCacheKeyParameters(filterContext)); @@ -167,16 +173,14 @@ namespace Orchard.OutputCache.Filters { public void OnResultExecuted(ResultExecutedContext filterContext) { + var captureHandlerIsAttached = false; + try { - string renderedOutput = null; - if (_completeResponseFunc != null) { - renderedOutput = _completeResponseFunc(filterContext); - } - - if (renderedOutput == null) { + // 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); @@ -203,42 +207,52 @@ 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(); + // Capture the response output using a custom filter stream. 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() + 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(); + } }; - // 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); - } + captureHandlerIsAttached = true; } 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); - } - } + // 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(); } } @@ -267,12 +281,6 @@ namespace Orchard.OutputCache.Filters { return false; } - // Ignore child actions, e.g. HomeController is using RenderAction() - if (filterContext.IsChildAction) { - Logger.Debug("Request for item '{0}' ignored because it's a child action.", _cacheKey); - return false; - } - // Ignore authenticated requests unless the setting to cache them is true. if (_workContext.CurrentUser != null && !CacheSettings.CacheAuthenticatedRequests) { Logger.Debug("Request for item '{0}' ignored because user is authenticated.", _cacheKey); @@ -329,7 +337,7 @@ namespace Orchard.OutputCache.Filters { // Vary by action parameters. foreach (var p in filterContext.ActionParameters) - result.Add(p.Key, p.Value); + result.Add("PARAM:" + p.Key, p.Value); // Vary by theme. result.Add("theme", _themeManager.GetRequestTheme(filterContext.RequestContext).Id.ToLowerInvariant()); @@ -438,7 +446,6 @@ namespace Orchard.OutputCache.Filters { private void ServeCachedItem(ActionExecutingContext filterContext, CacheItem cacheItem) { var response = filterContext.HttpContext.Response; - var output = cacheItem.Output; // Adds some caching information to the output if requested. if (CacheSettings.DebugMode) { @@ -447,10 +454,7 @@ namespace Orchard.OutputCache.Filters { } // Shorcut action execution. - filterContext.Result = new ContentResult { - Content = output, - ContentType = cacheItem.ContentType - }; + filterContext.Result = new FileContentResult(cacheItem.Output, cacheItem.ContentType); response.StatusCode = cacheItem.StatusCode; @@ -463,18 +467,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; + // Remember that we should intercept the rendered response output. + _isCachingRequest = true; } private void ApplyCacheControl(HttpResponseBase response) { @@ -488,9 +482,8 @@ namespace Orchard.OutputCache.Filters { response.Cache.SetMaxAge(maxAge); } - response.DisableUserCache(); - // Keeping this example for later usage. + // response.DisableUserCache(); // response.DisableKernelCache(); // response.Cache.SetOmitVaryStar(true); @@ -517,6 +510,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 ignoredUrls) { if (ignoredUrls == null || !ignoredUrls.Any()) { return false; @@ -566,17 +569,4 @@ namespace Orchard.OutputCache.Filters { public class ViewDataContainer : IViewDataContainer { public ViewDataDictionary ViewData { get; set; } } - - public sealed class StringWriterWithEncoding : StringWriter { - private readonly Encoding encoding; - - public StringWriterWithEncoding(Encoding encoding, IFormatProvider formatProvider) - : base(formatProvider) { - this.encoding = encoding; - } - - public override Encoding Encoding { - get { return encoding; } - } - } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs index ce111c219..dd0c1d0b3 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs @@ -6,7 +6,7 @@ namespace Orchard.OutputCache.Models { public DateTime CachedOnUtc { get; set; } public int Duration { get; set; } public int GraceTime { get; set; } - public string Output { get; set; } + public byte[] Output { get; set; } public string ContentType { get; set; } public string QueryString { get; set; } public string CacheKey { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItemRecord.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItemRecord.cs index 6894f266c..6cd345e8b 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItemRecord.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItemRecord.cs @@ -12,7 +12,7 @@ namespace Orchard.OutputCache.Models { public virtual int GraceTime { get; set; } public virtual DateTime ValidUntilUtc { get; set; } public virtual DateTime StoredUntilUtc { get; set; } - [StringLengthMax] public virtual string Output { get; set; } + public virtual byte[] Output { get; set; } public virtual string ContentType { get; set; } [StringLength(2048)] public virtual string QueryString { get; set; } [StringLength(2048)] public virtual string CacheKey { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj b/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj index f390913e9..41e412c22 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj @@ -97,6 +97,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Filters/SecureSocketsLayersFilter.cs b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Filters/SecureSocketsLayersFilter.cs index 2d760b52c..111e584fa 100644 --- a/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Filters/SecureSocketsLayersFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.SecureSocketsLayer/Filters/SecureSocketsLayersFilter.cs @@ -27,7 +27,7 @@ namespace Orchard.SecureSocketsLayer.Filters { public void OnActionExecuting(ActionExecutingContext filterContext) { var settings = _sslService.GetSettings(); - if (!settings.Enabled) { + if (filterContext.IsChildAction || !settings.Enabled) { return; } diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 4282cbc12..e794f9546 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -91,9 +91,10 @@ - + + diff --git a/src/Orchard/ContentManagement/DefaultContentDisplay.cs b/src/Orchard/ContentManagement/DefaultContentDisplay.cs index 1a949f275..c803b649e 100644 --- a/src/Orchard/ContentManagement/DefaultContentDisplay.cs +++ b/src/Orchard/ContentManagement/DefaultContentDisplay.cs @@ -13,7 +13,7 @@ namespace Orchard.ContentManagement { public class DefaultContentDisplay : IContentDisplay { private readonly Lazy> _handlers; private readonly IShapeFactory _shapeFactory; - private readonly Lazy _shapeTableLocator; + private readonly Lazy _shapeTableLocator; private readonly RequestContext _requestContext; private readonly IVirtualPathProvider _virtualPathProvider; @@ -22,7 +22,7 @@ namespace Orchard.ContentManagement { public DefaultContentDisplay( Lazy> handlers, IShapeFactory shapeFactory, - Lazy shapeTableLocator, + Lazy shapeTableLocator, RequestContext requestContext, IVirtualPathProvider virtualPathProvider, IWorkContextAccessor workContextAccessor) { @@ -74,13 +74,13 @@ namespace Orchard.ContentManagement { // adding an alternate for [Stereotype]_Edit__[ContentType] e.g. Content-Menu.Edit ((IShape)itemShape).Metadata.Alternates.Add(actualShapeType + "__" + content.ContentItem.ContentType); - + var context = new BuildEditorContext(itemShape, content, groupId, _shapeFactory); BindPlacement(context, null, stereotype); _handlers.Value.Invoke(handler => handler.BuildEditor(context), Logger); - + return context.Shape; } @@ -107,7 +107,7 @@ namespace Orchard.ContentManagement { BindPlacement(context, null, stereotype); _handlers.Value.Invoke(handler => handler.UpdateEditor(context), Logger); - + return context.Shape; } @@ -131,7 +131,7 @@ namespace Orchard.ContentManagement { Stereotype = stereotype, DisplayType = displayType, Differentiator = differentiator, - Path = GetPath() + Path = GetPath() }; // define which location should be used if none placement is hit @@ -154,8 +154,7 @@ namespace Orchard.ContentManagement { /// /// Gets the current app-relative path, i.e. ~/my-blog/foo. /// - private string GetPath() - { + private string GetPath() { return VirtualPathUtility.AppendTrailingSlash(_virtualPathProvider.ToAppRelative(_requestContext.HttpContext.Request.Path)); } } diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs index df94dd45c..5b04c9358 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeTemplateStrategy/ShapeTemplateBindingStrategy.cs @@ -169,6 +169,8 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy { context.ViewContext.ViewData = new ViewDataDictionary(context.Value); context.ViewContext.TempData = new TempDataDictionary(); context.ViewContext.View = viewResult.View; + context.ViewContext.RouteData = controllerContext.RouteData; + context.ViewContext.RequestContext.RouteData = controllerContext.RouteData; viewResult.View.Render(context.ViewContext, sw); viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View); return new HtmlString(sw.GetStringBuilder().ToString()); diff --git a/src/Orchard/Localization/Services/CurrentCultureWorkContext.cs b/src/Orchard/Localization/Services/CurrentCultureWorkContext.cs index ebee7f059..de7cd0223 100644 --- a/src/Orchard/Localization/Services/CurrentCultureWorkContext.cs +++ b/src/Orchard/Localization/Services/CurrentCultureWorkContext.cs @@ -29,9 +29,9 @@ namespace Orchard.Localization.Services { .Select(c => c.GetCulture(httpContext)) .Where(c => c != null) .OrderByDescending(c => c.Priority) - .FirstOrDefault(c => !string.IsNullOrEmpty(c.CultureName)); + .FirstOrDefault(c => !String.IsNullOrEmpty(c.CultureName)); - return culture == null ? string.Empty : culture.CultureName; + return culture == null ? String.Empty : culture.CultureName; } } } diff --git a/src/Orchard/Localization/Services/DefaultCultureManager.cs b/src/Orchard/Localization/Services/DefaultCultureManager.cs index 6f0f030c7..9026e4acb 100644 --- a/src/Orchard/Localization/Services/DefaultCultureManager.cs +++ b/src/Orchard/Localization/Services/DefaultCultureManager.cs @@ -9,18 +9,17 @@ using Orchard.Localization.Records; namespace Orchard.Localization.Services { public class DefaultCultureManager : ICultureManager { private readonly IRepository _cultureRepository; - private readonly IEnumerable _cultureSelectors; private readonly ISignals _signals; private readonly IWorkContextAccessor _workContextAccessor; private readonly ICacheManager _cacheManager; - public DefaultCultureManager(IRepository cultureRepository, - IEnumerable cultureSelectors, - ISignals signals, - IWorkContextAccessor workContextAccessor, - ICacheManager cacheManager) { + public DefaultCultureManager( + IRepository cultureRepository, + ISignals signals, + IWorkContextAccessor workContextAccessor, + ICacheManager cacheManager) { + _cultureRepository = cultureRepository; - _cultureSelectors = cultureSelectors; _signals = signals; _workContextAccessor = workContextAccessor; _cacheManager = cacheManager; @@ -38,12 +37,12 @@ namespace Orchard.Localization.Services { if (!IsValidCulture(cultureName)) { throw new ArgumentException("cultureName"); } - + if (ListCultures().Any(culture => culture == cultureName)) { return; } - _cultureRepository.Create(new CultureRecord {Culture = cultureName}); + _cultureRepository.Create(new CultureRecord { Culture = cultureName }); _signals.Trigger("culturesChanged"); } @@ -81,7 +80,7 @@ namespace Orchard.Localization.Services { public bool IsValidCulture(string cultureName) { var segments = cultureName.Split('-'); - if(segments.Length == 0) { + if (segments.Length == 0) { return false; } @@ -92,7 +91,7 @@ namespace Orchard.Localization.Services { if (segments.Any(s => s.Length < 2)) { return false; } - + return true; } } diff --git a/src/Orchard/Mvc/IHttpContextAccessor.cs b/src/Orchard/Mvc/IHttpContextAccessor.cs index f7df5cb97..91208d314 100644 --- a/src/Orchard/Mvc/IHttpContextAccessor.cs +++ b/src/Orchard/Mvc/IHttpContextAccessor.cs @@ -4,19 +4,19 @@ using System.Web; namespace Orchard.Mvc { public interface IHttpContextAccessor { HttpContextBase Current(); - void Set(HttpContextBase stub); + void Set(HttpContextBase httpContext); } public class HttpContextAccessor : IHttpContextAccessor { - private HttpContextBase _stub; + private HttpContextBase _httpContext; public HttpContextBase Current() { var httpContext = GetStaticProperty(); - return httpContext != null ? new HttpContextWrapper(httpContext) : _stub; + return httpContext != null ? new HttpContextWrapper(httpContext) : _httpContext; } - public void Set(HttpContextBase stub) { - _stub = stub; + public void Set(HttpContextBase httpContext) { + _httpContext = httpContext; } private HttpContext GetStaticProperty() { diff --git a/src/Orchard/Mvc/MvcModule.cs b/src/Orchard/Mvc/MvcModule.cs index 47e04b8e8..1e0c49ce6 100644 --- a/src/Orchard/Mvc/MvcModule.cs +++ b/src/Orchard/Mvc/MvcModule.cs @@ -83,7 +83,7 @@ namespace Orchard.Mvc { } /// - /// standin context for background tasks. + /// Standin context for background tasks. /// class HttpContextPlaceholder : HttpContextBase { private readonly Lazy _baseUrl; @@ -124,6 +124,12 @@ namespace Orchard.Mvc { public override string ApplyAppPathModifier(string virtualPath) { return virtualPath; } + + public override HttpCookieCollection Cookies { + get { + return new HttpCookieCollection(); + } + } } /// @@ -158,7 +164,7 @@ namespace Orchard.Mvc { public override NameValueCollection Headers { get { - return new NameValueCollection {{"Host", _uri.Authority}}; + return new NameValueCollection { { "Host", _uri.Authority } }; } } @@ -184,10 +190,44 @@ namespace Orchard.Mvc { } } + public override HttpCookieCollection Cookies { + get { + return new HttpCookieCollection(); + } + } + public override bool IsLocal { get { return true; } } + public override string Path { + get { return "/"; } + } + + public override string UserAgent { + get { + return "Placeholder"; + } + } + + public override HttpBrowserCapabilitiesBase Browser { + get { + return new HttpBrowserCapabilitiesPlaceholder(); + } + } + } + + class HttpBrowserCapabilitiesPlaceholder : HttpBrowserCapabilitiesBase { + public override string this[string key] { + get { + return ""; + } + } + + public override bool IsMobileDevice { get { return false; } } + public override string Browser { get { return "Placeholder"; } } + public override bool Cookies { get { return true; } } + public override ArrayList Browsers { get { return new ArrayList(); } } } } } \ No newline at end of file diff --git a/src/Orchard/WorkContext.cs b/src/Orchard/WorkContext.cs index 458ecf263..412278e2a 100644 --- a/src/Orchard/WorkContext.cs +++ b/src/Orchard/WorkContext.cs @@ -24,7 +24,7 @@ namespace Orchard { /// True if the dependency could be resolved, false otherwise public abstract bool TryResolve(out T service); - public abstract T GetState(string name); + public abstract T GetState(string name); public abstract void SetState(string name, T value); ///