From 186e8fc58b7551379eaa953709a28e6355c7e4e9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 14 Feb 2019 12:11:06 +0100 Subject: [PATCH] Introduce IUmbracoContextFactory --- .../Cache/DistributedCacheBinderTests.cs | 15 +- src/Umbraco.Tests/IO/ShadowFileSystemTests.cs | 2 +- .../Integration/ContentEventsTests.cs | 3 +- .../Routing/UmbracoModuleTests.cs | 3 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 8 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../Scoping/ScopedRepositoryTests.cs | 7 +- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 5 +- .../TestControllerActivatorBase.cs | 15 +- .../TestHelpers/TestObjects-Mocks.cs | 16 +- ...RenderIndexActionSelectorAttributeTests.cs | 75 +++--- .../Web/Mvc/SurfaceControllerTests.cs | 68 +++--- .../Web/TemplateUtilitiesTests.cs | 19 +- .../Cache/DistributedCacheBinder.cs | 8 +- src/Umbraco.Web/Composing/Current.cs | 17 +- .../Runtime/WebRuntimeComponent.cs | 19 +- .../Scheduling/ScheduledPublishing.cs | 8 +- .../Scheduling/SchedulerComponent.cs | 8 +- src/Umbraco.Web/UmbracoContext.cs | 215 +++++++++--------- src/Umbraco.Web/UmbracoInjectedModule.cs | 21 +- 20 files changed, 285 insertions(+), 249 deletions(-) diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index e9448bd0fc..64bf6c3c55 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -5,11 +5,14 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Tests.Testing; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -149,8 +152,18 @@ namespace Umbraco.Tests.Cache }; + var umbracoContextFactory = new UmbracoContextFactory( + new TestUmbracoContextAccessor(), + Mock.Of(), + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + TestObjects.GetGlobalSettings(), + Enumerable.Empty(), + Mock.Of()); + // just assert it does not throw - var refreshers = new DistributedCacheBinder(null, null); + var refreshers = new DistributedCacheBinder(null, umbracoContextFactory, null); refreshers.HandleEvents(definitions); } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 2244f9085d..8d9c1be40a 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -473,7 +473,7 @@ namespace Umbraco.Tests.IO scope.Dispose(); scopedFileSystems = false; Assert.IsTrue(phy.FileExists("sub/f5.txt")); - Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); } [Test] diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 7b22d282f0..c3708dd8f3 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Sync; using Umbraco.Tests.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Web; using Umbraco.Web.Cache; using static Umbraco.Tests.Cache.DistributedCache.DistributedCacheTests; @@ -32,7 +33,7 @@ namespace Umbraco.Tests.Integration { base.SetUp(); - _h1 = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _h1 = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _h1.BindEvents(true); _events = new List(); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index ff610cbc00..3e4c4f1ba9 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -45,7 +45,8 @@ namespace Umbraco.Tests.Routing runtime, logger, null, // FIXME: PublishedRouter complexities... - Mock.Of() + Mock.Of(), + Mock.Of() ); runtime.Level = RuntimeLevel.Run; diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 49bc8149a9..71f2fba18f 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -101,6 +101,7 @@ namespace Umbraco.Tests.Runtimes composition.WithCollectionBuilder().Append(); composition.RegisterUnique(); composition.RegisterUnique(f => ExamineManager.Instance); + composition.RegisterUnique(); // initialize some components only/individually composition.WithCollectionBuilder() @@ -179,8 +180,9 @@ namespace Umbraco.Tests.Runtimes // need an UmbracoCOntext to access the cache // FIXME: not exactly pretty, should not depend on HttpContext var httpContext = Mock.Of(); - var withUmbracoContext = UmbracoContext.EnsureContext(httpContext); - var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; + var umbracoContextFactory = factory.GetInstance(); + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(httpContext); + var umbracoContext = umbracoContextReference.UmbracoContext; // assert that there is no published document var pcontent = umbracoContext.ContentCache.GetById(content.Id); @@ -218,7 +220,7 @@ namespace Umbraco.Tests.Runtimes // and the published document has a url Assert.AreEqual("/test/", pcontent.GetUrl()); - withUmbracoContext.Dispose(); + umbracoContextReference.Dispose(); mainDom.Stop(); components.Terminate(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 2f0876b9ba..33f6af8638 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Scoping var umbracoContext = GetUmbracoContextNu("http://example.com/", setSingleton: true); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // create document type, document diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index b85a79f326..ad0f7142df 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -14,6 +14,7 @@ using Moq; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Sync; +using Umbraco.Web; namespace Umbraco.Tests.Scoping { @@ -73,7 +74,7 @@ namespace Umbraco.Tests.Scoping // get user again - else we'd modify the one that's in the cache user = service.GetUserById(user.Id); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); @@ -154,7 +155,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(lang.Id, globalCached.Id); Assert.AreEqual("fr-FR", globalCached.IsoCode); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); @@ -246,7 +247,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 1d07ec074f..c8dcdd7fd0 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Sync; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Scoping var item = new Content("name", -1, contentType); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // check xml in context = "before" @@ -204,7 +205,7 @@ namespace Umbraco.Tests.Scoping Current.Services.ContentTypeService.Save(contentType); // wire cache refresher - _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of(), Mock.Of()); _distributedCacheBinder.BindEvents(true); // check xml in context = "before" diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 4e352488be..1ad350ebf8 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -135,16 +135,17 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var umbracoContextAccessor = Umbraco.Web.Composing.Current.UmbracoContextAccessor; - var umbCtx = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( umbracoContextAccessor, - httpContext, publishedSnapshotService.Object, - webSecurity.Object, - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == UrlProviderMode.Auto.ToString())), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); //replace it + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbCtx = umbracoContextFactory.EnsureUmbracoContext(httpContext).UmbracoContext; var urlHelper = new Mock(); urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 660d0f201e..0f84319976 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -116,11 +116,21 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettings = GetUmbracoSettings(); var globalSettings = GetGlobalSettings(); - var webSecurity = new Mock(null, null, globalSettings).Object; var urlProviders = Enumerable.Empty(); if (accessor == null) accessor = new TestUmbracoContextAccessor(); - return UmbracoContext.EnsureContext(accessor, httpContext, publishedSnapshotService, webSecurity, umbracoSettings, urlProviders, globalSettings, new TestVariationContextAccessor(), true); + + var umbracoContextFactory = new UmbracoContextFactory( + accessor, + publishedSnapshotService, + new TestVariationContextAccessor(), + new TestDefaultCultureAccessor(), + umbracoSettings, + globalSettings, + urlProviders, + Mock.Of()); + + return umbracoContextFactory.EnsureUmbracoContext(httpContext).UmbracoContext; } public IUmbracoSettingsSection GetUmbracoSettings() @@ -143,7 +153,7 @@ namespace Umbraco.Tests.TestHelpers public IFileSystems GetFileSystemsMock() { var fileSystems = Mock.Of(); - + MockFs(fileSystems, x => x.MacroPartialsFileSystem); MockFs(fileSystems, x => x.MvcViewsFileSystem); MockFs(fileSystems, x => x.PartialViewsFileSystem); diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index 29deeb31ec..ce42b8d55f 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -63,19 +63,20 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - //var appCtx = new ApplicationContext( - // CacheHelper.CreateDisabledCacheHelper(), - // new ProfilingLogger(Mock.Of(), Mock.Of())); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + var ctrl = new MatchesDefaultIndexController { UmbracoContext = umbCtx }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, @@ -90,16 +91,20 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + var ctrl = new MatchesOverriddenIndexController { UmbracoContext = umbCtx }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, @@ -114,16 +119,20 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + var ctrl = new MatchesCustomIndexController { UmbracoContext = umbCtx }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, @@ -138,16 +147,20 @@ namespace Umbraco.Tests.Web.Mvc var globalSettings = TestObjects.GetGlobalSettings(); var attr = new RenderIndexActionSelectorAttribute(); var req = new RequestContext(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - Mock.Of(), Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; + var ctrl = new MatchesAsyncIndexController { UmbracoContext = umbCtx }; var controllerCtx = new ControllerContext(req, ctrl); var result = attr.IsValidForRequest(controllerCtx, diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index b3ae7e3dd6..e845b829f8 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -40,16 +40,19 @@ namespace Umbraco.Tests.Web.Mvc public void Can_Construct_And_Get_Result() { var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; var ctrl = new TestSurfaceController(umbracoContext); @@ -62,22 +65,25 @@ namespace Umbraco.Tests.Web.Mvc public void Umbraco_Context_Not_Null() { var globalSettings = TestObjects.GetGlobalSettings(); - var umbCtx = UmbracoContext.EnsureContext( + + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, Mock.Of(), - new Mock(null, null, globalSettings).Object, - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + TestObjects.GetUmbracoSettings(), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbCtx = umbracoContextReference.UmbracoContext; var ctrl = new TestSurfaceController(umbCtx); Assert.IsNotNull(ctrl.UmbracoContext); } - + [Test] public void Can_Lookup_Content() { @@ -88,16 +94,18 @@ namespace Umbraco.Tests.Web.Mvc var publishedSnapshotService = new Mock(); var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, publishedSnapshotService.Object, - new Mock(null, null, globalSettings).Object, - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; var helper = new UmbracoHelper( umbracoContext, @@ -121,16 +129,18 @@ namespace Umbraco.Tests.Web.Mvc var webRoutingSettings = Mock.Of(section => section.UrlProviderMode == "Auto"); var globalSettings = TestObjects.GetGlobalSettings(); - var umbracoContext = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Current.UmbracoContextAccessor, - new Mock().Object, Mock.Of(), - new Mock(null, null, globalSettings).Object, - Mock.Of(section => section.WebRouting == webRoutingSettings), - Enumerable.Empty(), - globalSettings, new TestVariationContextAccessor(), - true); + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == webRoutingSettings), + globalSettings, + Enumerable.Empty(), + Mock.Of()); + + var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of()); + var umbracoContext = umbracoContextReference.UmbracoContext; var content = Mock.Of(publishedContent => publishedContent.Id == 12345); diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index aa44e7d085..d4945dfc58 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -96,20 +96,19 @@ namespace Umbraco.Tests.Web var snapshotService = Mock.Of(); Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot); - using (var umbCtx = UmbracoContext.EnsureContext( + var umbracoContextFactory = new UmbracoContextFactory( Umbraco.Web.Composing.Current.UmbracoContextAccessor, - Mock.Of(), snapshotService, - new Mock(null, null, globalSettings).Object, - //setup a quick mock of the WebRouting section - Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), - //pass in the custom url provider - new[]{ testUrlProvider.Object }, - globalSettings, new TestVariationContextAccessor(), - true)) + new TestDefaultCultureAccessor(), + Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "Auto")), + globalSettings, + new[] { testUrlProvider.Object }, + Mock.Of()); + + using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of())) { - var output = TemplateUtilities.ParseInternalLinks(input, umbCtx.UrlProvider); + var output = TemplateUtilities.ParseInternalLinks(input, reference.UmbracoContext.UrlProvider); Assert.AreEqual(result, output); } diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs index c64951810a..92ed7de881 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs @@ -16,17 +16,17 @@ namespace Umbraco.Web.Cache { private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); private readonly DistributedCache _distributedCache; + private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// - /// - /// - public DistributedCacheBinder(DistributedCache distributedCache, ILogger logger) + public DistributedCacheBinder(DistributedCache distributedCache, IUmbracoContextFactory umbracoContextFactory, ILogger logger) { _distributedCache = distributedCache; _logger = logger; + _umbracoContextFactory = umbracoContextFactory; } // internal for tests @@ -64,7 +64,7 @@ namespace Umbraco.Web.Cache { // ensure we run with an UmbracoContext, because this may run in a background task, // yet developers may be using the 'current' UmbracoContext in the event handlers - using (UmbracoContext.EnsureContext()) + using (_umbracoContextFactory.EnsureUmbracoContext()) { foreach (var e in events) { diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index d4fe51b3f1..5dfd7251b8 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -44,7 +44,10 @@ namespace Umbraco.Web.Composing CoreCurrent.Resetted += (sender, args) => { if (_umbracoContextAccessor != null) - ClearUmbracoContext(); + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + umbracoContext?.Dispose(); + } _umbracoContextAccessor = null; }; } @@ -75,18 +78,6 @@ namespace Umbraco.Web.Composing set => _umbracoContextAccessor = value; // for tests } - // clears the "current" umbraco context - // at the moment the "current" umbraco context can end up being disposed and should get cleared - // in the accessor - this should be done differently but for the time being we have to support it - public static void ClearUmbracoContext() - { - lock (Locker) - { - UmbracoContextAccessor.UmbracoContext?.Dispose(); // dispose the one that is being cleared, if any - UmbracoContextAccessor.UmbracoContext = null; - } - } - #endregion #region Web Getters diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index a7de6ca842..ce34f707bb 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Runtime } public void Initialize() - { + { // setup mvc and webapi services SetupMvcAndWebApi(); @@ -88,23 +88,6 @@ namespace Umbraco.Web.Runtime // set routes CreateRoutes(_umbracoContextAccessor, _globalSettings, _surfaceControllerTypes, _apiControllerTypes); - // get an http context - // at that moment, HttpContext.Current != null but its .Request property is null - var httpContext = new HttpContextWrapper(HttpContext.Current); - - // ensure there is an UmbracoContext - // (also sets the accessor) - // this is a *temp* UmbracoContext - UmbracoContext.EnsureContext( - _umbracoContextAccessor, - new HttpContextWrapper(HttpContext.Current), - _publishedSnapshotService, - new WebSecurity(httpContext, _userService, _globalSettings), - _umbracoSettings, - _urlProviders, - _globalSettings, - _variationContextAccessor); - // ensure WebAPI is initialized, after everything GlobalConfiguration.Configuration.EnsureInitialized(); } diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 4326ac3287..2e79e40d7a 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -11,14 +11,16 @@ namespace Umbraco.Web.Scheduling { private readonly IRuntimeState _runtime; private readonly IContentService _contentService; + private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILogger _logger; public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IContentService contentService, ILogger logger) + IRuntimeState runtime, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { _runtime = runtime; _contentService = contentService; + _umbracoContextFactory = umbracoContextFactory; _logger = logger; } @@ -62,7 +64,7 @@ namespace Umbraco.Web.Scheduling // but then what should be its "scope"? could we attach it to scopes? // - and we should definitively *not* have to flush it here (should be auto) // - using (var tempContext = UmbracoContext.EnsureContext()) + using (var contextReference = _umbracoContextFactory.EnsureUmbracoContext()) { try { @@ -74,7 +76,7 @@ namespace Umbraco.Web.Scheduling finally { // if running on a temp context, we have to flush the messenger - if (tempContext != null && Composing.Current.ServerMessenger is BatchedDatabaseServerMessenger m) + if (contextReference.IsRoot && Composing.Current.ServerMessenger is BatchedDatabaseServerMessenger m) m.FlushBatch(); } } diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index 697c80e76d..d6b8d195f1 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Scheduling private readonly IScopeProvider _scopeProvider; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; + private readonly IUmbracoContextFactory _umbracoContextFactory; private BackgroundTaskRunner _keepAliveRunner; private BackgroundTaskRunner _publishingRunner; @@ -38,13 +39,14 @@ namespace Umbraco.Web.Scheduling public SchedulerComponent(IRuntimeState runtime, IContentService contentService, IAuditService auditService, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, - IScopeProvider scopeProvider, IProfilingLogger logger) + IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IProfilingLogger logger) { _runtime = runtime; _contentService = contentService; _auditService = auditService; _scopeProvider = scopeProvider; _logger = logger; + _umbracoContextFactory = umbracoContextFactory; _healthChecks = healthChecks; _notifications = notifications; @@ -114,11 +116,11 @@ namespace Umbraco.Web.Scheduling { // scheduled publishing/unpublishing // install on all, will only run on non-replica servers - var task = new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _contentService, _logger); + var task = new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _contentService, _umbracoContextFactory, _logger); _publishingRunner.TryAdd(task); return task; } - + private IBackgroundTask RegisterHealthCheckNotifier(IHealthChecks healthCheckConfig, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IProfilingLogger logger) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 7052fb7fb5..c089f386ab 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using System.Web; using System.Web.Hosting; using Umbraco.Core; @@ -8,6 +9,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Runtime; @@ -15,6 +17,117 @@ using Umbraco.Web.Security; namespace Umbraco.Web { + public interface IUmbracoContextFactory + { + UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null); + } + + public class UmbracoContextReference : IDisposable + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private bool _disposed; + + internal UmbracoContextReference(UmbracoContext umbracoContext, bool isRoot, IUmbracoContextAccessor umbracoContextAccessor) + { + UmbracoContext = umbracoContext; + IsRoot = isRoot; + + _umbracoContextAccessor = umbracoContextAccessor; + } + + public UmbracoContext UmbracoContext { get; } + + public bool IsRoot { get; } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + if (IsRoot) + { + UmbracoContext.Dispose(); + _umbracoContextAccessor.UmbracoContext = null; + } + + GC.SuppressFinalize(this); + } + } + + public class UmbracoContextFactory : IUmbracoContextFactory + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IDefaultCultureAccessor _defaultCultureAccessor; + + private readonly IUmbracoSettingsSection _umbracoSettings; + private readonly IGlobalSettings _globalSettings; + private readonly IEnumerable _urlProviders; + private readonly IUserService _userService; + + public UmbracoContextFactory(IUmbracoContextAccessor umbracoContextAccessor, IPublishedSnapshotService publishedSnapshotService, IVariationContextAccessor variationContextAccessor, IDefaultCultureAccessor defaultCultureAccessor, IUmbracoSettingsSection umbracoSettings, IGlobalSettings globalSettings, IEnumerable urlProviders, IUserService userService) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); + _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _defaultCultureAccessor = defaultCultureAccessor ?? throw new ArgumentNullException(nameof(defaultCultureAccessor)); + + _umbracoSettings = umbracoSettings ?? throw new ArgumentNullException(nameof(umbracoSettings)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _urlProviders = urlProviders ?? throw new ArgumentNullException(nameof(urlProviders)); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + } + + public UmbracoContext CreateUmbracoContext(HttpContextBase httpContext) + { + // make sure we have a variation context + if (_variationContextAccessor.VariationContext == null) + _variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture); + + var webSecurity = new WebSecurity(httpContext, _userService, _globalSettings); + return new UmbracoContext(httpContext, _publishedSnapshotService, webSecurity, _umbracoSettings, _urlProviders, _globalSettings, _variationContextAccessor); + } + + public UmbracoContext EnsureUmbracoContext___(HttpContextBase httpContext) + { + var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; + if (currentUmbracoContext != null) + { + currentUmbracoContext.Dispose(); + _umbracoContextAccessor.UmbracoContext = null; + } + + var umbracoContext = CreateUmbracoContext(httpContext); + _umbracoContextAccessor.UmbracoContext = umbracoContext; + return umbracoContext; + } + + public UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null) + { + var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; + if (currentUmbracoContext != null) return new UmbracoContextReference(currentUmbracoContext, false, _umbracoContextAccessor); + + httpContext = httpContext ?? new HttpContextWrapper(HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("nul.aspx", "", NulWriter.Instance))); + + var umbracoContext = CreateUmbracoContext(httpContext); + _umbracoContextAccessor.UmbracoContext = umbracoContext; + + return new UmbracoContextReference(umbracoContext, true, _umbracoContextAccessor); + } + + private class NulWriter : TextWriter + { + private NulWriter() + { } + + public static NulWriter Instance { get; } = new NulWriter(); + + public override Encoding Encoding => Encoding.UTF8; + } + } + /// /// Class that encapsulates Umbraco information of a specific HTTP request /// @@ -26,102 +139,6 @@ namespace Umbraco.Web private string _previewToken; private bool? _previewing; - #region Ensure Context - - /// - /// Ensures that there is a "current" UmbracoContext. - /// - /// - /// An http context. - /// A published snapshot service. - /// A security helper. - /// The umbraco settings. - /// Some url providers. - /// - /// A value indicating whether to replace the existing context. - /// The "current" UmbracoContext. - /// - /// TODO: this needs to be clarified - /// - /// If is true then the "current" UmbracoContext is replaced - /// with a new one even if there is one already. See . Has to do with - /// creating a context at startup and not being able to access httpContext.Request at that time, so - /// the OriginalRequestUrl remains unspecified until replaces the context. - /// - /// This *has* to be done differently! - /// - /// See http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - /// - // used by - // UmbracoModule BeginRequest (since it's a request it has an UmbracoContext) - // in BeginRequest so *late* ie *after* the HttpApplication has started (+ init? check!) - // WebRuntimeComponent (and I'm not quite sure why) - // -> because an UmbracoContext seems to be required by UrlProvider to get the "current" published snapshot? - // note: at startup not sure we have an HttpContext.Current - // at startup not sure we have an httpContext.Request => hard to tell "current" url - // should we have a post-boot event of some sort for ppl that *need* ?! - // can we have issues w/ routing context? - // and tests - // can .ContentRequest be null? of course! - public static UmbracoContext EnsureContext( - IUmbracoContextAccessor umbracoContextAccessor, - HttpContextBase httpContext, - IPublishedSnapshotService publishedSnapshotService, - WebSecurity webSecurity, - IUmbracoSettingsSection umbracoSettings, - IEnumerable urlProviders, - IGlobalSettings globalSettings, - IVariationContextAccessor variationContextAccessor, - bool replace = false) - { - if (umbracoContextAccessor == null) throw new ArgumentNullException(nameof(umbracoContextAccessor)); - if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); - if (webSecurity == null) throw new ArgumentNullException(nameof(webSecurity)); - if (umbracoSettings == null) throw new ArgumentNullException(nameof(umbracoSettings)); - if (urlProviders == null) throw new ArgumentNullException(nameof(urlProviders)); - if (globalSettings == null) throw new ArgumentNullException(nameof(globalSettings)); - - // if there is already a current context, return if not replacing - var current = umbracoContextAccessor.UmbracoContext; - if (current != null && replace == false) - return current; - - // create & assign to accessor, dispose existing if any - umbracoContextAccessor.UmbracoContext?.Dispose(); - return umbracoContextAccessor.UmbracoContext = new UmbracoContext(httpContext, publishedSnapshotService, webSecurity, umbracoSettings, urlProviders, globalSettings, variationContextAccessor); - } - - /// - /// Gets a disposable object representing the presence of a current UmbracoContext. - /// - /// - /// The disposable object should be used in a using block: using (UmbracoContext.EnsureContext()) { ... }. - /// If an actual current UmbracoContext is already present, the disposable object is null and this method does nothing. - /// Otherwise, a temporary, dummy UmbracoContext is created and registered in the accessor. And disposed and removed from the accessor. - /// - internal static IDisposable EnsureContext(HttpContextBase httpContext = null) // keep this internal for now! - { - if (Composing.Current.UmbracoContext != null) return null; - - httpContext = httpContext ?? new HttpContextWrapper(System.Web.HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); - - return EnsureContext( - Composing.Current.UmbracoContextAccessor, - httpContext, - Composing.Current.PublishedSnapshotService, - new WebSecurity(httpContext, Composing.Current.Services.UserService, Composing.Current.Configs.Global()), - Composing.Current.Configs.Settings(), - Composing.Current.UrlProviders, - Composing.Current.Configs.Global(), - Composing.Current.Factory.GetInstance(), - true); - - // when the context will be disposed, it will be removed from the accessor - // (see DisposeResources) - } - // initializes a new instance of the UmbracoContext class // internal for unit tests // otherwise it's used by EnsureContext above @@ -172,8 +189,6 @@ namespace Umbraco.Web UrlProvider = new UrlProvider(this, umbracoSettings.WebRouting, urlProviders, variationContextAccessor); } - #endregion - /// /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this /// object is instantiated which in the web site is created during the BeginRequest phase. @@ -339,10 +354,6 @@ namespace Umbraco.Web Security.DisposeIfDisposable(); - // reset - important when running outside of http context - // also takes care of the accessor - Composing.Current.ClearUmbracoContext(); - // help caches release resources // (but don't create caches just to dispose them) // context is not multi-threaded diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 1898309cf0..14ae45fe40 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -47,6 +47,7 @@ namespace Umbraco.Web private readonly ILogger _logger; private readonly IPublishedRouter _publishedRouter; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IUmbracoContextFactory _umbracoContextFactory; public UmbracoInjectedModule( IGlobalSettings globalSettings, @@ -57,7 +58,8 @@ namespace Umbraco.Web IRuntimeState runtime, ILogger logger, IPublishedRouter publishedRouter, - IVariationContextAccessor variationContextAccessor) + IVariationContextAccessor variationContextAccessor, + IUmbracoContextFactory umbracoContextFactory) { _combinedRouteCollection = new Lazy(CreateRouteCollection); @@ -70,6 +72,7 @@ namespace Umbraco.Web _logger = logger; _publishedRouter = publishedRouter; _variationContextAccessor = variationContextAccessor; + _umbracoContextFactory = umbracoContextFactory; } #region HttpModule event handlers @@ -92,18 +95,10 @@ namespace Umbraco.Web // ok, process - // create the UmbracoContext singleton, one per request, and assign - // replace existing if any (eg during app startup, a temp one is created) - UmbracoContext.EnsureContext( - _umbracoContextAccessor, - httpContext, - _publishedSnapshotService, - new WebSecurity(httpContext, _userService, _globalSettings), - Current.Configs.Settings(), - _urlProviders, - _globalSettings, - _variationContextAccessor, - true); + // ensure there's an UmbracoContext registered for the current request + // registers the context reference so its disposed at end of request + var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(httpContext); + httpContext.DisposeOnPipelineCompleted(umbracoContextReference); } ///