From edd457c24b8d25b858702029476695d91fd7befd Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 31 Aug 2020 11:31:56 +0200 Subject: [PATCH 01/14] Force Examine to run in sync for integration tests --- src/Umbraco.Core/Composing/TypeFinder.cs | 1 + .../UmbracoContentIndex.cs | 6 +++++- .../Controllers/ContentControllerTests.cs | 2 +- .../UmbracoTestServerTestBase.cs | 20 ++++++++++++++++++- .../Umbraco.Tests.Integration.csproj | 1 + 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 13c5e063be..3427c55731 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -156,6 +156,7 @@ namespace Umbraco.Core.Composing "MiniProfiler.", "Owin,", "SQLite", + "ReSharperTestRunner32" // used by resharper testrunner }; /// diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index dc2a5570a6..a4fa47009f 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -19,9 +19,13 @@ namespace Umbraco.Examine /// /// An indexer for Umbraco content and media /// - public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex + public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex, IDisposable { + void IDisposable.Dispose() + { + base.Dispose(); + } protected ILocalizationService LanguageService { get; } #region Constructors diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index a9a272ac59..57be7e2e05 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -18,7 +18,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Integration.TestServerTest.Controllers { [TestFixture] - [Explicit("Need to figure out whats wrong with these tests when executed all in one run.")] + //[Explicit("Need to figure out whats wrong with these tests when executed all in one run.")] public class ContentControllerTests : UmbracoTestServerTestBase { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b666cc40f2..c5f413e24f 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -3,18 +3,20 @@ using System; using System.Linq.Expressions; using System.Net.Http; using System.Reflection; +using Examine; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Composing; +using Umbraco.Core.Scoping; +using Umbraco.Examine; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Editors; namespace Umbraco.Tests.Integration.TestServerTest @@ -23,6 +25,7 @@ namespace Umbraco.Tests.Integration.TestServerTest [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = false)] public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest { + [SetUp] public void SetUp() { @@ -31,6 +34,21 @@ namespace Umbraco.Tests.Integration.TestServerTest AllowAutoRedirect = false }); LinkGenerator = Factory.Services.GetRequiredService(); + + ExecuteExamineIndexOperationsInSync(); + } + + private void ExecuteExamineIndexOperationsInSync() + { + var examineManager = Factory.Services.GetRequiredService(); + + foreach (var index in examineManager.Indexes) + { + if (index is UmbracoExamineIndex umbracoExamineIndex) + { + umbracoExamineIndex.ProcessNonAsync(); + } + } } /// diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index ba986ebd86..072f15bcd1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -14,6 +14,7 @@ + From f84a93ae20920976520b19d02833c10d7d985972 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Sep 2020 18:10:29 +1000 Subject: [PATCH 02/14] Adds IUmbracoBuilder to build the config so it's easier to split apart, ensures background tasks don't run in integration tests, uses a custom CoreRuntime to ensure we use SimpleMainDom, re-wires the UmbracoWebApplicationFactory so that duplicate Hosts are not running at the same time, fixes issues with overriding/shadowing, uses RAMDirectory in lucene for tests, fixes CoreRuntime's events that we're moved incorrectly and not backwards compat, adds new real non static events which we can use in integration tests to install the DB before the runtime state is calculated --- src/Umbraco.Core/Services/IRuntimeState.cs | 5 + src/Umbraco.Core/SimpleMainDom.cs | 22 +- .../ExamineLuceneComposer.cs | 1 + .../ILuceneDirectoryFactory.cs | 7 + .../LuceneFileSystemDirectoryFactory.cs | 69 ++++ .../LuceneIndexCreator.cs | 43 +-- .../LuceneRAMDirectoryFactory.cs | 24 ++ .../UmbracoIndexesCreator.cs | 12 +- .../CompositionExtensions_Essentials.cs | 1 + .../Runtime/CoreRuntime.cs | 100 ++--- .../Runtime/RuntimeEssentialsEventArgs.cs | 23 ++ src/Umbraco.Infrastructure/RuntimeOptions.cs | 10 +- src/Umbraco.Infrastructure/RuntimeState.cs | 39 +- .../Scheduling/SchedulerComponent.cs | 2 +- .../Search/BackgroundIndexRebuilder.cs | 4 +- src/Umbraco.Tests.Common/TestHelperBase.cs | 7 - .../ContainerTests.cs | 2 +- .../ApplicationBuilderExtensions.cs | 126 ------- .../Implementations/TestHelper.cs | 13 +- src/Umbraco.Tests.Integration/RuntimeTests.cs | 4 +- .../TestServerTest/TestAuthHandler.cs | 40 ++ .../UmbracoBuilderExtensions.cs | 40 ++ .../UmbracoTestServerTestBase.cs | 117 ++++-- .../UmbracoWebApplicationFactory.cs | 96 +---- .../Testing/IntegrationTestComponent.cs | 43 +++ .../Testing/IntegrationTestComposer.cs | 37 +- .../Testing/UmbracoIntegrationTest.cs | 348 ++++++++++++++---- .../Umbraco.Tests.Integration.csproj | 1 + .../Routing/UmbracoModuleTests.cs | 2 +- .../Runtimes/CoreRuntimeTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 2 - ...BackOfficeApplicationBuilderExtensions.cs} | 7 +- ... BackOfficeServiceCollectionExtensions.cs} | 48 +-- .../Extensions/UmbracoBuilderExtensions.cs | 28 ++ .../Umbraco.Web.BackOffice.csproj | 2 +- .../Builder/IUmbracoBuilder.cs | 18 + .../Builder/UmbracoBuilder.cs | 37 ++ .../Builder/UmbracoBuilderExtensions.cs | 70 ++++ .../UmbracoCoreServiceCollectionExtensions.cs | 29 +- .../Umbraco.Web.Common.csproj | 1 + src/Umbraco.Web.UI.NetCore/Startup.cs | 4 +- 42 files changed, 991 insertions(+), 499 deletions(-) create mode 100644 src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs create mode 100644 src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs create mode 100644 src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs delete mode 100644 src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs create mode 100644 src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs create mode 100644 src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs create mode 100644 src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs rename src/Umbraco.Web.BackOffice/Extensions/{UmbracoBackOfficeApplicationBuilderExtensions.cs => BackOfficeApplicationBuilderExtensions.cs} (79%) rename src/Umbraco.Web.BackOffice/Extensions/{UmbracoBackOfficeServiceCollectionExtensions.cs => BackOfficeServiceCollectionExtensions.cs} (64%) create mode 100644 src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs create mode 100644 src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs create mode 100644 src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs create mode 100644 src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index 4b57908ea7..fd817f1986 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -50,5 +50,10 @@ namespace Umbraco.Core /// BootFailedException BootFailedException { get; } + /// + /// Determines the runtime level. + /// + void DetermineRuntimeLevel(); + } } diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index e6bdda67d7..a2ef0b8d78 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -8,11 +8,12 @@ namespace Umbraco.Core /// /// Provides a simple implementation of . /// - public class SimpleMainDom : IMainDom + public class SimpleMainDom : IMainDom, IDisposable { private readonly object _locko = new object(); private readonly List> _callbacks = new List>(); private bool _isStopping; + private bool _disposedValue; /// public bool IsMainDom { get; private set; } = true; @@ -59,5 +60,24 @@ namespace Umbraco.Core IsMainDom = false; } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + Stop(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs index b7d9cde9d1..b1254a4beb 100644 --- a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs +++ b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs @@ -20,6 +20,7 @@ namespace Umbraco.Examine composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs new file mode 100644 index 0000000000..f4946d491e --- /dev/null +++ b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Examine +{ + public interface ILuceneDirectoryFactory + { + Lucene.Net.Store.Directory CreateDirectory(string indexName); + } +} diff --git a/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs new file mode 100644 index 0000000000..8f3bf8bf69 --- /dev/null +++ b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs @@ -0,0 +1,69 @@ +using Umbraco.Core.Configuration; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; +using Lucene.Net.Store; +using System.IO; +using System; +using Examine.LuceneEngine.Directories; + +namespace Umbraco.Examine +{ + + public class LuceneFileSystemDirectoryFactory : ILuceneDirectoryFactory + { + private readonly ITypeFinder _typeFinder; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIndexCreatorSettings _settings; + + public LuceneFileSystemDirectoryFactory(ITypeFinder typeFinder, IHostingEnvironment hostingEnvironment, IIndexCreatorSettings settings) + { + _typeFinder = typeFinder; + _hostingEnvironment = hostingEnvironment; + _settings = settings; + } + + public Lucene.Net.Store.Directory CreateDirectory(string indexName) => CreateFileSystemLuceneDirectory(indexName); + + /// + /// Creates a file system based Lucene with the correct locking guidelines for Umbraco + /// + /// + /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal) + /// + /// + public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) + { + + var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); + if (!dirInfo.Exists) + System.IO.Directory.CreateDirectory(dirInfo.FullName); + + //check if there's a configured directory factory, if so create it and use that to create the lucene dir + var configuredDirectoryFactory = _settings.LuceneDirectoryFactory; + + if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) + { + //this should be a fully qualified type + var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); + if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); + var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); + return directoryFactory.CreateDirectory(dirInfo); + } + + //no dir factory, just create a normal fs directory + + var luceneDir = new SimpleFSDirectory(dirInfo); + + //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain + //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock + //which simply checks the existence of the lock file + // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo) + // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally. + luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo)); + return luceneDir; + + + } + } +} diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs index 9efcdc4891..fe35e9ca0f 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs @@ -29,47 +29,6 @@ namespace Umbraco.Examine _settings = settings; } - public abstract IEnumerable Create(); - - /// - /// Creates a file system based Lucene with the correct locking guidelines for Umbraco - /// - /// - /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal) - /// - /// - public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) - { - - var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); - if (!dirInfo.Exists) - System.IO.Directory.CreateDirectory(dirInfo.FullName); - - //check if there's a configured directory factory, if so create it and use that to create the lucene dir - var configuredDirectoryFactory = _settings.LuceneDirectoryFactory; - - if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) - { - //this should be a fully qualified type - var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); - if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); - var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); - return directoryFactory.CreateDirectory(dirInfo); - } - - //no dir factory, just create a normal fs directory - - var luceneDir = new SimpleFSDirectory(dirInfo); - - //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain - //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock - //which simply checks the existence of the lock file - // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo) - // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally. - luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo)); - return luceneDir; - - - } + public abstract IEnumerable Create(); } } diff --git a/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs new file mode 100644 index 0000000000..327328390b --- /dev/null +++ b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs @@ -0,0 +1,24 @@ +using Lucene.Net.Store; +using System; + +namespace Umbraco.Examine +{ + public class LuceneRAMDirectoryFactory : ILuceneDirectoryFactory + { + public LuceneRAMDirectoryFactory() + { + + } + + public Lucene.Net.Store.Directory CreateDirectory(string indexName) => new RandomIdRAMDirectory(); + + private class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockId() + { + return _lockId; + } + } + } +} diff --git a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs index 3cf7d6d386..4bb14a3a08 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs @@ -12,6 +12,7 @@ using Umbraco.Core.IO; namespace Umbraco.Examine { + /// /// Creates the indexes used by Umbraco /// @@ -28,7 +29,8 @@ namespace Umbraco.Examine IUmbracoIndexConfig umbracoIndexConfig, IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, - IIndexCreatorSettings settings) : base(typeFinder, hostingEnvironment, settings) + IIndexCreatorSettings settings, + ILuceneDirectoryFactory directoryFactory) : base(typeFinder, hostingEnvironment, settings) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); @@ -37,11 +39,13 @@ namespace Umbraco.Examine UmbracoIndexConfig = umbracoIndexConfig; HostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment)); RuntimeState = runtimeState ?? throw new System.ArgumentNullException(nameof(runtimeState)); + DirectoryFactory = directoryFactory; } protected IProfilingLogger ProfilingLogger { get; } protected IHostingEnvironment HostingEnvironment { get; } protected IRuntimeState RuntimeState { get; } + protected ILuceneDirectoryFactory DirectoryFactory { get; } protected ILocalizationService LanguageService { get; } protected IPublicAccessService PublicAccessService { get; } protected IMemberService MemberService { get; } @@ -65,7 +69,7 @@ namespace Umbraco.Examine { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.InternalIndexName, - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.InternalIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.InternalIndexPath), new UmbracoFieldDefinitionCollection(), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, @@ -81,7 +85,7 @@ namespace Umbraco.Examine { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.ExternalIndexName, - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.ExternalIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.ExternalIndexPath), new UmbracoFieldDefinitionCollection(), new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), ProfilingLogger, @@ -97,7 +101,7 @@ namespace Umbraco.Examine var index = new UmbracoMemberIndex( Constants.UmbracoIndexes.MembersIndexName, new UmbracoFieldDefinitionCollection(), - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, HostingEnvironment, diff --git a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs index 88ae80bf8b..b2ded07034 100644 --- a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core TypeLoader typeLoader, IRuntimeState state, ITypeFinder typeFinder, + IIOHelper ioHelper, IUmbracoVersion umbracoVersion, IDbProviderFactoryCreator dbProviderFactoryCreator, diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 2f9766e5c2..ff86f87569 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -5,6 +5,7 @@ using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Hosting; using Umbraco.Core.IO; @@ -23,7 +24,8 @@ namespace Umbraco.Core.Runtime { private ComponentCollection _components; private IFactory _factory; - private readonly RuntimeState _state; + // runtime state, this instance will get replaced again once the essential services are available to run the check + private RuntimeState _state = RuntimeState.Booting(); private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; private readonly IRequestCache _requestCache; private readonly IGlobalSettings _globalSettings; @@ -61,14 +63,6 @@ namespace Umbraco.Core.Runtime _globalSettings = Configs.Global(); _connectionStrings = configs.ConnectionStrings(); - - // runtime state - // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' - // as the second one captures the current value (null) and therefore fails - _state = new RuntimeState(Configs.Global(), UmbracoVersion) - { - Level = RuntimeLevel.Boot - }; } /// @@ -88,7 +82,7 @@ namespace Umbraco.Core.Runtime /// /// Gets the profiling logger. /// - protected IProfilingLogger ProfilingLogger { get; private set; } + public IProfilingLogger ProfilingLogger { get; private set; } /// /// Gets the @@ -100,8 +94,8 @@ namespace Umbraco.Core.Runtime /// protected IIOHelper IOHelper { get; } protected IHostingEnvironment HostingEnvironment { get; } - protected Configs Configs { get; } - protected IUmbracoVersion UmbracoVersion { get; } + public Configs Configs { get; } + public IUmbracoVersion UmbracoVersion { get; } /// public IRuntimeState State => _state; @@ -164,31 +158,36 @@ namespace Umbraco.Core.Runtime try { - - // run handlers - RuntimeOptions.DoRuntimeBoot(ProfilingLogger); + OnRuntimeBoot(); // application caches - var appCaches = GetAppCaches(); + var appCaches = CreateAppCaches(); // database factory - var databaseFactory = GetDatabaseFactory(); + var databaseFactory = CreateDatabaseFactory(); // type finder/loader var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); + // re-create the state object with the essential services + _state = new RuntimeState(Configs.Global(), UmbracoVersion, databaseFactory, Logger); + // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); // register ourselves (TODO: Should we put this in RegisterEssentials?) composition.Register(_ => this, Lifetime.Singleton); + // run handlers + OnRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + try { // determine our runtime level - DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + DetermineRuntimeLevel(databaseFactory); } finally { @@ -243,9 +242,6 @@ namespace Umbraco.Core.Runtime // throws if not full-trust _umbracoBootPermissionChecker.ThrowIfNotPermissions(); - // run handlers - RuntimeOptions.DoRuntimeEssentials(_factory); - var hostingEnvironmentLifetime = _factory.TryGetInstance(); if (hostingEnvironmentLifetime == null) throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}"); @@ -257,7 +253,6 @@ namespace Umbraco.Core.Runtime _components = _factory.GetInstance(); _components.Initialize(); - // now (and only now) is the time to switch over to perWebRequest scopes. // up until that point we may not have a request, and scoped services would // fail to resolve - but we run Initialize within a factory scope - and then, @@ -313,31 +308,29 @@ namespace Umbraco.Core.Runtime } } - // internal for tests - internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + private void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory) { - using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) + using var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined."); + + try { - try - { - _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); + _state.DetermineRuntimeLevel(); - profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); + ProfilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); - if (_state.Level == RuntimeLevel.Upgrade) - { - profilingLogger.Debug("Configure database factory for upgrades."); - databaseFactory.ConfigureForUpgrade(); - } - } - catch + if (_state.Level == RuntimeLevel.Upgrade) { - _state.Level = RuntimeLevel.BootFailed; - _state.Reason = RuntimeLevelReason.BootFailedOnException; - timer?.Fail(); - throw; + ProfilingLogger.Debug("Configure database factory for upgrades."); + databaseFactory.ConfigureForUpgrade(); } } + catch + { + _state.Level = RuntimeLevel.BootFailed; + _state.Reason = RuntimeLevelReason.BootFailedOnException; + timer?.Fail(); + throw; + } } private IEnumerable ResolveComposerTypes(TypeLoader typeLoader) @@ -374,9 +367,9 @@ namespace Umbraco.Core.Runtime => typeLoader.GetTypes(); /// - /// Gets the application caches. + /// Creates the application caches. /// - protected virtual AppCaches GetAppCaches() + protected virtual AppCaches CreateAppCaches() { // need the deep clone runtime cache provider to ensure entities are cached properly, ie // are cloned in and cloned out - no request-based cache here since no web-based context, @@ -400,14 +393,33 @@ namespace Umbraco.Core.Runtime => null; /// - /// Gets the database factory. + /// Creates the database factory. /// /// This is strictly internal, for tests only. - protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal virtual IUmbracoDatabaseFactory CreateDatabaseFactory() => new UmbracoDatabaseFactory(Logger, _globalSettings, _connectionStrings, new Lazy(() => _factory.GetInstance()), DbProviderFactoryCreator); #endregion + #region Events + + protected void OnRuntimeBoot() + { + RuntimeOptions.DoRuntimeBoot(ProfilingLogger); + RuntimeBooting?.Invoke(this, ProfilingLogger); + } + + protected void OnRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + { + RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + RuntimeEssentials?.Invoke(this, new RuntimeEssentialsEventArgs(composition, appCaches, typeLoader, databaseFactory)); + } + + public event TypedEventHandler RuntimeBooting; + public event TypedEventHandler RuntimeEssentials; + + #endregion + } } diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs new file mode 100644 index 0000000000..78d068cb9c --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Runtime +{ + public class RuntimeEssentialsEventArgs : EventArgs + { + public RuntimeEssentialsEventArgs(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + { + Composition = composition; + AppCaches = appCaches; + TypeLoader = typeLoader; + DatabaseFactory = databaseFactory; + } + + public Composition Composition { get; } + public AppCaches AppCaches { get; } + public TypeLoader TypeLoader { get; } + public IUmbracoDatabaseFactory DatabaseFactory { get; } + } +} diff --git a/src/Umbraco.Infrastructure/RuntimeOptions.cs b/src/Umbraco.Infrastructure/RuntimeOptions.cs index 562a7e3c5c..23abd474a4 100644 --- a/src/Umbraco.Infrastructure/RuntimeOptions.cs +++ b/src/Umbraco.Infrastructure/RuntimeOptions.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core public static class RuntimeOptions { private static List> _onBoot; - private static List> _onEssentials; + private static List> _onEssentials; /// /// Executes the RuntimeBoot handlers. @@ -33,13 +33,13 @@ namespace Umbraco.Core /// /// Executes the RuntimeEssentials handlers. /// - internal static void DoRuntimeEssentials(IFactory factory) + internal static void DoRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) { if (_onEssentials== null) return; foreach (var action in _onEssentials) - action(factory); + action(composition, appCaches, typeLoader, databaseFactory); } /// @@ -64,10 +64,10 @@ namespace Umbraco.Core /// essential things (AppCaches, a TypeLoader, and a database factory) but /// before anything else. /// - public static void OnRuntimeEssentials(Action action) + public static void OnRuntimeEssentials(Action action) { if (_onEssentials == null) - _onEssentials = new List>(); + _onEssentials = new List>(); _onEssentials.Add(action); } } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 4a33291314..9b88430080 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -1,10 +1,8 @@ using System; using System.Threading; using Semver; -using Umbraco.Core.Collections; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; -using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; @@ -18,14 +16,27 @@ namespace Umbraco.Core { private readonly IGlobalSettings _globalSettings; private readonly IUmbracoVersion _umbracoVersion; + private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly ILogger _logger; + + /// + /// The initial + /// + public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot }; + + private RuntimeState() + { + } /// /// Initializes a new instance of the class. /// - public RuntimeState(IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion) + public RuntimeState(IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger) { _globalSettings = globalSettings; _umbracoVersion = umbracoVersion; + _databaseFactory = databaseFactory; + _logger = logger; } @@ -53,16 +64,14 @@ namespace Umbraco.Core /// public BootFailedException BootFailedException { get; internal set; } - /// - /// Determines the runtime level. - /// - public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + /// + public void DetermineRuntimeLevel() { - if (databaseFactory.Configured == false) + if (_databaseFactory.Configured == false) { // local version *does* match code version, but the database is not configured // install - may happen with Deploy/Cloud/etc - logger.Debug("Database is not configured, need to install Umbraco."); + _logger.Debug("Database is not configured, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoDatabase; return; @@ -75,16 +84,16 @@ namespace Umbraco.Core var tries = _globalSettings.InstallMissingDatabase ? 2 : 5; for (var i = 0;;) { - connect = databaseFactory.CanConnect; + connect = _databaseFactory.CanConnect; if (connect || ++i == tries) break; - logger.Debug("Could not immediately connect to database, trying again."); + _logger.Debug("Could not immediately connect to database, trying again."); Thread.Sleep(1000); } if (connect == false) { // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); + _logger.Debug("Could not connect to database."); if (_globalSettings.InstallMissingDatabase) { @@ -108,12 +117,12 @@ namespace Umbraco.Core bool noUpgrade; try { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + noUpgrade = EnsureUmbracoUpgradeState(_databaseFactory, _logger); } catch (Exception e) { // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); + _logger.Warn(e, "Could not check the upgrade state."); if (_globalSettings.InstallEmptyDatabase) { @@ -145,7 +154,7 @@ namespace Umbraco.Core // although the files version matches the code version, the database version does not // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); Level = RuntimeLevel.Upgrade; Reason = RuntimeLevelReason.UpgradeMigrations; } diff --git a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs index a5850719ab..3539993358 100644 --- a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs @@ -18,7 +18,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Scheduling { - internal sealed class SchedulerComponent : IComponent + public sealed class SchedulerComponent : IComponent { private const int DefaultDelayMilliseconds = 180000; // 3 mins private const int OneMinuteMilliseconds = 60000; diff --git a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs index 1946e2041b..d18480eb82 100644 --- a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Search /// /// Utility to rebuild all indexes on a background thread /// - public sealed class BackgroundIndexRebuilder + public class BackgroundIndexRebuilder { private static readonly object RebuildLocker = new object(); private readonly IndexRebuilder _indexRebuilder; @@ -34,7 +34,7 @@ namespace Umbraco.Web.Search /// /// /// - public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) + public virtual void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) { // TODO: need a way to disable rebuilding on startup diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index 85a463ddfa..cb8b6618ee 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -47,13 +47,6 @@ namespace Umbraco.Tests.Common public Configs GetConfigs() => GetConfigsFactory().Create(); - public IRuntimeState GetRuntimeState() - { - return new RuntimeState( - Mock.Of(), - GetUmbracoVersion()); - } - public abstract IBackOfficeInfo GetBackOfficeInfo(); public IConfigsFactory GetConfigsFactory() => new ConfigsFactory(); diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs index 2098b7241e..3ba3c31d03 100644 --- a/src/Umbraco.Tests.Integration/ContainerTests.cs +++ b/src/Umbraco.Tests.Integration/ContainerTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Integration // it means the container won't be disposed, and maybe other services? not sure. // In cases where we use it can we use IConfigureOptions? https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); IHostApplicationLifetime lifetime1 = null; diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs deleted file mode 100644 index edbdcd0722..0000000000 --- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Data.Common; -using System.Data.SqlClient; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Tests.Integration.Testing; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Integration.Extensions -{ - public static class ApplicationBuilderExtensions - { - /// - /// Creates a LocalDb instance to use for the test - /// - /// - /// - /// - /// - /// - public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app, - string workingDirectory, - UmbracoIntegrationTest integrationTest, out string connectionString) - { - connectionString = null; - var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - - // get the currently set db options - var testOptions = TestOptionAttributeBase.GetTestOptions(); - - if (testOptions.Database == UmbracoTestOptions.Database.None) - return app; - - // need to manually register this factory - DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - - if (!Directory.Exists(dbFilePath)) - Directory.CreateDirectory(dbFilePath); - - var db = UmbracoIntegrationTest.GetOrCreate(dbFilePath, - app.ApplicationServices.GetRequiredService(), - app.ApplicationServices.GetRequiredService(), - app.ApplicationServices.GetRequiredService()); - - - switch (testOptions.Database) - { - case UmbracoTestOptions.Database.NewSchemaPerTest: - - // New DB + Schema - var newSchemaDbId = db.AttachSchema(); - - // Add teardown callback - integrationTest.OnTestTearDown(() => db.Detach(newSchemaDbId)); - - // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings - var dbFactory = app.ApplicationServices.GetRequiredService(); - if (!dbFactory.Configured) - { - dbFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer); - } - - // In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that - // the runtime state is configured correctly so we'll force update the configuration flag and re-run the - // runtime state checker. - // TODO: This wouldn't be required if we don't store the Umbraco version in config - - // right now we are an an 'Install' state - var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService(); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - - // dynamically change the config status - var umbVersion = app.ApplicationServices.GetRequiredService(); - var config = app.ApplicationServices.GetRequiredService(); - config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); - - // re-run the runtime level check - var profilingLogger = app.ApplicationServices.GetRequiredService(); - runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); - - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewEmptyPerTest: - - var newEmptyDbId = db.AttachEmpty(); - - // Add teardown callback - integrationTest.OnTestTearDown(() => db.Detach(newEmptyDbId)); - - - break; - case UmbracoTestOptions.Database.NewSchemaPerFixture: - - // New DB + Schema - var newSchemaFixtureDbId = db.AttachSchema(); - - // Add teardown callback - integrationTest.OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId)); - - break; - case UmbracoTestOptions.Database.NewEmptyPerFixture: - - throw new NotImplementedException(); - - //// Add teardown callback - //integrationTest.OnFixtureTearDown(() => db.Detach()); - - break; - default: - throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null); - } - connectionString = db.ConnectionString; - return app; - } - } - - -} diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index a6cd372411..7faa4dac34 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Runtime; using Umbraco.Tests.Common; using Umbraco.Web.Common.AspNetCore; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; +using Microsoft.Extensions.FileProviders; namespace Umbraco.Tests.Integration.Implementations { @@ -39,11 +40,17 @@ namespace Umbraco.Tests.Integration.Implementations _httpContextAccessor = Mock.Of(x => x.HttpContext == httpContext); _ipResolver = new AspNetCoreIpResolver(_httpContextAccessor); + var contentRoot = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); var hostEnvironment = new Mock(); - hostEnvironment.Setup(x => x.ApplicationName).Returns("UmbracoIntegrationTests"); - hostEnvironment.Setup(x => x.ContentRootPath) - .Returns(() => Assembly.GetExecutingAssembly().GetRootDirectorySafe()); + // this must be the assembly name for the WebApplicationFactory to work + hostEnvironment.Setup(x => x.ApplicationName).Returns(GetType().Assembly.GetName().Name); + hostEnvironment.Setup(x => x.ContentRootPath).Returns(() => contentRoot); + hostEnvironment.Setup(x => x.ContentRootFileProvider).Returns(() => new PhysicalFileProvider(contentRoot)); hostEnvironment.Setup(x => x.WebRootPath).Returns(() => WorkingDirectory); + hostEnvironment.Setup(x => x.WebRootFileProvider).Returns(() => new PhysicalFileProvider(WorkingDirectory)); + // we also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it + hostEnvironment.As(); + _hostEnvironment = hostEnvironment.Object; _hostingLifetime = new AspNetCoreApplicationShutdownRegistry(Mock.Of()); diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index bd7684fcae..b41ef4ac02 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -88,7 +88,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task AddUmbracoCore() { - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task UseUmbracoCore() { - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs new file mode 100644 index 0000000000..08cd49bd81 --- /dev/null +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -0,0 +1,40 @@ +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Common.Security; + +namespace Umbraco.Tests.Integration.TestServerTest +{ + public class TestAuthHandler : AuthenticationHandler + { + private readonly BackOfficeSignInManager _backOfficeSignInManager; + + private readonly BackOfficeIdentityUser _fakeUser; + public TestAuthHandler(IOptionsMonitor options, + ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper) + : base(options, logger, encoder, clock) + { + _backOfficeSignInManager = backOfficeSignInManager; + + var user = userService.GetUserById(Constants.Security.SuperUserId); + _fakeUser = umbracoMapper.Map(user); + _fakeUser.SecurityStamp = "Needed"; + } + + protected override async Task HandleAuthenticateAsync() + { + + var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); + var ticket = new AuthenticationTicket(principal, "Test"); + + return AuthenticateResult.Success(ticket); + } + } +} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..7c919e3894 --- /dev/null +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -0,0 +1,40 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing.LightInject; +using Umbraco.Core.Runtime; +using Umbraco.Extensions; +using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Web.Common.Builder; + +namespace Umbraco.Tests.Integration.TestServerTest +{ + public static class UmbracoBuilderExtensions + { + /// + /// Uses a test version of Umbraco Core with a test IRuntime + /// + /// + /// + public static IUmbracoBuilder WithTestCore(this IUmbracoBuilder builder, TestHelper testHelper, LightInjectContainer container, + Action dbInstallEventHandler) + { + return builder.AddWith(nameof(global::Umbraco.Web.Common.Builder.UmbracoBuilderExtensions.WithCore), + () => + { + builder.Services.AddUmbracoCore( + builder.WebHostEnvironment, + container, + typeof(UmbracoBuilderExtensions).Assembly, + NoAppCache.Instance, + testHelper.GetLoggingConfiguration(), + // TODO: Yep that's extremely ugly + (a, b, c, d, e, f, g, h, i, j) => + UmbracoIntegrationTest.CreateTestRuntime(a, b, c, d, e, f, g, h, i, j, + testHelper.MainDom, // SimpleMainDom + dbInstallEventHandler), // DB Installation event handler + out _); + }); + } + } +} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index c5f413e24f..18a5836223 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -2,22 +2,26 @@ using System; using System.Linq.Expressions; using System.Net.Http; -using System.Reflection; -using Examine; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Composing; -using Umbraco.Core.Scoping; -using Umbraco.Examine; +using Umbraco.Core; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; +using Umbraco.Web.Common.Builder; using Umbraco.Web.Common.Controllers; - +using Umbraco.Web.Editors; +using Microsoft.Extensions.Hosting; namespace Umbraco.Tests.Integration.TestServerTest { @@ -25,37 +29,56 @@ namespace Umbraco.Tests.Integration.TestServerTest [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = false)] public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest { - [SetUp] - public void SetUp() + public override Task Setup() { - Factory = new UmbracoWebApplicationFactory(TestDBConnectionString); - Client = Factory.CreateClient(new WebApplicationFactoryClientOptions(){ - AllowAutoRedirect = false + InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = null; + InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true"; + + // create new WebApplicationFactory specifying 'this' as the IStartup instance + var factory = new UmbracoWebApplicationFactory(CreateHostBuilder); + + // additional host configuration for web server integration tests + Factory = factory.WithWebHostBuilder(builder => + { + // Executes after the standard ConfigureServices method + builder.ConfigureTestServices(services => + { + services.AddAuthentication("Test").AddScheme("Test", options => { }); + }); }); + + Client = Factory.CreateClient(new WebApplicationFactoryClientOptions + { + AllowAutoRedirect = false + }); + LinkGenerator = Factory.Services.GetRequiredService(); - ExecuteExamineIndexOperationsInSync(); + return Task.CompletedTask; } - private void ExecuteExamineIndexOperationsInSync() + public override IHostBuilder CreateHostBuilder() { - var examineManager = Factory.Services.GetRequiredService(); + var builder = base.CreateHostBuilder(); + builder.ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => { + c.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + }); - foreach (var index in examineManager.Indexes) - { - if (index is UmbracoExamineIndex umbracoExamineIndex) - { - umbracoExamineIndex.ProcessNonAsync(); - } - } + // call startup + builder.Configure(app => + { + Services = app.ApplicationServices; + Configure(app); + }); + }) + .UseEnvironment(Environments.Development); + return builder; } - /// - /// Get the service from the underlying container that is also used by the . - /// - protected T GetRequiredService() => Factory.Services.GetRequiredService(); - /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. @@ -85,13 +108,14 @@ namespace Umbraco.Tests.Integration.TestServerTest return url; } - protected HttpClient Client { get; set; } - protected LinkGenerator LinkGenerator { get; set; } - protected UmbracoWebApplicationFactory Factory { get; set; } + protected HttpClient Client { get; private set; } + protected LinkGenerator LinkGenerator { get; private set; } + protected WebApplicationFactory Factory { get; private set; } [TearDown] - public void TearDown() + public override void TearDown() { + base.TearDown(); Factory.Dispose(); @@ -99,7 +123,40 @@ namespace Umbraco.Tests.Integration.TestServerTest { Current.IsInitialized = false; } - } + + #region IStartup + + public override void ConfigureServices(IServiceCollection services) + { + //services.AddSingleton(TestHelper.DbProviderFactoryCreator); + //var webHostEnvironment = TestHelper.GetWebHostEnvironment(); + //services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); + + var umbracoBuilder = services.AddUmbraco(TestHelper.GetWebHostEnvironment(), Configuration); + umbracoBuilder + .WithConfiguration() + .WithTestCore(TestHelper, UmbracoContainer, UseTestLocalDb) // This is the important one! + .WithWebComponents() + .WithRuntimeMinifier() + .WithBackOffice() + .WithBackOfficeIdentity() + //.WithMiniProfiler() + .WithMvcAndRazor(mvcBuilding: mvcBuilder => + { + mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); + }) + .WithWebServer() + .Build(); + } + + public override void Configure(IApplicationBuilder app) + { + app.UseUmbraco(); + } + + #endregion + + } } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs index ea2b64c790..251f155d2c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs @@ -1,97 +1,23 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Hosting; +using System; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Common.Security; -using Umbraco.Web.UI.NetCore; namespace Umbraco.Tests.Integration.TestServerTest { - public class UmbracoWebApplicationFactory : CustomWebApplicationFactory + + public class UmbracoWebApplicationFactory : WebApplicationFactory where TStartup : class { - public UmbracoWebApplicationFactory(string testDbConnectionString) :base(testDbConnectionString) - { - } - } + private readonly Func _createHostBuilder; - public abstract class CustomWebApplicationFactory : WebApplicationFactory where TStartup : class - { - private readonly string _testDbConnectionString; - - protected CustomWebApplicationFactory(string testDbConnectionString) + /// + /// Constructor to create a new WebApplicationFactory + /// + /// Method to create the IHostBuilder + public UmbracoWebApplicationFactory(Func createHostBuilder) { - _testDbConnectionString = testDbConnectionString; + _createHostBuilder = createHostBuilder; } - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - base.ConfigureWebHost(builder); - - builder.ConfigureTestServices(services => - { - services.AddAuthentication("Test").AddScheme("Test", options => {}); - }); - - builder.ConfigureAppConfiguration(x => - { - x.AddInMemoryCollection(new Dictionary() - { - ["ConnectionStrings:"+ Constants.System.UmbracoConnectionName] = _testDbConnectionString, - ["Umbraco:CMS:Hosting:Debug"] = "true", - }); - }); - - } - - protected override IHostBuilder CreateHostBuilder() - { - - var builder = base.CreateHostBuilder(); - builder.UseUmbraco(); - return builder; - } - } - - public class TestAuthHandler : AuthenticationHandler - { - private readonly BackOfficeSignInManager _backOfficeSignInManager; - - private readonly BackOfficeIdentityUser _fakeUser; - public TestAuthHandler(IOptionsMonitor options, - ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper) - : base(options, logger, encoder, clock) - { - _backOfficeSignInManager = backOfficeSignInManager; - - var user = userService.GetUserById(Constants.Security.SuperUserId); - _fakeUser = umbracoMapper.Map(user); - _fakeUser.SecurityStamp = "Needed"; - } - - protected override async Task HandleAuthenticateAsync() - { - - var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); - var ticket = new AuthenticationTicket(principal, "Test"); - - return AuthenticateResult.Success(ticket); - } + protected override IHostBuilder CreateHostBuilder() => _createHostBuilder(); } } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs new file mode 100644 index 0000000000..69819c9bef --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs @@ -0,0 +1,43 @@ +using Examine; +using Examine.LuceneEngine.Providers; +using Umbraco.Core.Composing; +using Umbraco.Examine; + +namespace Umbraco.Tests.Integration.Testing +{ + /// + /// A component to customize some services to work nicely with integration tests + /// + public class IntegrationTestComponent : IComponent + { + private readonly IExamineManager _examineManager; + + public IntegrationTestComponent(IExamineManager examineManager) + { + _examineManager = examineManager; + } + + public void Initialize() + { + ConfigureExamineIndexes(); + } + + public void Terminate() + { + } + + /// + /// Configure all indexes to run sync (non-backbround threads) and to use RAMDirectory + /// + private void ConfigureExamineIndexes() + { + foreach (var index in _examineManager.Indexes) + { + if (index is LuceneIndex luceneIndex) + { + luceneIndex.ProcessNonAsync(); + } + } + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 844877b1fd..97482f9482 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -1,7 +1,15 @@ using Moq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Core.WebAssets; +using Umbraco.Examine; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.Scheduling; +using Umbraco.Web.Search; namespace Umbraco.Tests.Integration.Testing { @@ -13,11 +21,36 @@ namespace Umbraco.Tests.Integration.Testing /// This is a IUserComposer so that it runs after all core composers /// [RuntimeLevel(MinLevel = RuntimeLevel.Boot)] - public class IntegrationTestComposer : IUserComposer + public class IntegrationTestComposer : ComponentComposer { - public void Compose(Composition composition) + public override void Compose(Composition composition) { + base.Compose(composition); + + composition.Components().Remove(); + composition.RegisterUnique(); composition.RegisterUnique(factory => Mock.Of()); + + // we don't want persisted nucache files in tests + composition.Register(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }); + + // ensure all lucene indexes are using RAM directory (no file system) + composition.RegisterUnique(); } + + // replace the default so there is no background index rebuilder + private class TestBackgroundIndexRebuilder : BackgroundIndexRebuilder + { + public TestBackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) + : base(mainDom, logger, hostingEnvironment, indexRebuilder) + { + } + + public override void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + // noop + } + } + } } diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 89cbfa992d..ee03e2a146 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; @@ -22,9 +21,19 @@ using Umbraco.Extensions; using Umbraco.Tests.Testing; using Umbraco.Web; using ILogger = Umbraco.Core.Logging.ILogger; +using Umbraco.Core.Runtime; +using Umbraco.Core; +using Moq; +using System.Collections.Generic; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using System.Data.SqlClient; +using System.Data.Common; +using System.IO; namespace Umbraco.Tests.Integration.Testing { + /// /// Abstract class for integration tests /// @@ -35,7 +44,7 @@ namespace Umbraco.Tests.Integration.Testing [NonParallelizable] public abstract class UmbracoIntegrationTest { - public static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) + public static LightInjectContainer CreateUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) { var container = UmbracoServiceProviderFactory.CreateServiceContainer(); serviceProviderFactory = new UmbracoServiceProviderFactory(container, false); @@ -43,6 +52,191 @@ namespace Umbraco.Tests.Integration.Testing return umbracoContainer; } + private Action _testTeardown = null; + private Action _fixtureTeardown = null; + + public void OnTestTearDown(Action tearDown) => _testTeardown = tearDown; + + public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown = tearDown; + + [OneTimeTearDown] + public void FixtureTearDown() => _fixtureTeardown?.Invoke(); + + [TearDown] + public virtual void TearDown() => _testTeardown?.Invoke(); + + [SetUp] + public virtual async Task Setup() + { + var hostBuilder = CreateHostBuilder(); + var host = await hostBuilder.StartAsync(); + Services = host.Services; + var app = new ApplicationBuilder(host.Services); + Configure(app); + } + + #region Generic Host Builder and Runtime + + /// + /// Create the Generic Host and execute startup ConfigureServices/Configure calls + /// + /// + public virtual IHostBuilder CreateHostBuilder() + { + UmbracoContainer = CreateUmbracoContainer(out var serviceProviderFactory); + _serviceProviderFactory = serviceProviderFactory; + + var hostBuilder = Host.CreateDefaultBuilder() + // IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work + // if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will + // create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves, + // and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly. + //.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); }) + .UseUmbraco(_serviceProviderFactory) + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + Configuration = context.Configuration; + configBuilder.AddInMemoryCollection(InMemoryConfiguration); + }) + .ConfigureServices((hostContext, services) => + { + ConfigureServices(services); + }); + return hostBuilder; + } + + /// + /// Creates a instance for testing and registers an event handler for database install + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, + ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator) + { + return CreateTestRuntime(configs, + umbracoVersion, + ioHelper, + logger, + profiler, + hostingEnvironment, + backOfficeInfo, + typeFinder, + requestCache, + dbProviderFactoryCreator, + TestHelper.MainDom, // SimpleMainDom + UseTestLocalDb // DB Installation event handler + ); + } + + /// + /// Creates a instance for testing and registers an event handler for database install + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The event handler used for DB installation + /// + public static CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, + ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator, + IMainDom mainDom, Action eventHandler) + { + var runtime = new CoreRuntime( + configs, + umbracoVersion, + ioHelper, + logger, + profiler, + Mock.Of(), + hostingEnvironment, + backOfficeInfo, + dbProviderFactoryCreator, + mainDom, + typeFinder, + requestCache); + + runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args); + + return runtime; + } + + #endregion + + #region IStartup + + public virtual void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(TestHelper.DbProviderFactoryCreator); + var webHostEnvironment = TestHelper.GetWebHostEnvironment(); + services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); + + // Add it! + services.AddUmbracoConfiguration(Configuration); + services.AddUmbracoCore(webHostEnvironment, UmbracoContainer, GetType().Assembly, NoAppCache.Instance, TestHelper.GetLoggingConfiguration(), CreateTestRuntime, out _); + services.AddUmbracoWebComponents(); + services.AddUmbracoRuntimeMinifier(Configuration); + services.AddUmbracoBackOffice(); + services.AddUmbracoBackOfficeIdentity(); + + services.AddMvc(); + + services.AddSingleton(new ConsoleLogger(new MessageTemplates())); + + CustomTestSetup(services); + } + + public virtual void Configure(IApplicationBuilder app) + { + Services.GetRequiredService().EnsureUmbracoContext(); + + // get the currently set ptions + var testOptions = TestOptionAttributeBase.GetTestOptions(); + if (testOptions.Boot) + { + app.UseUmbracoCore(); + } + } + + #endregion + + #region LocalDb + + + private static readonly object _dbLocker = new object(); + private static LocalDbTestDatabase _dbInstance; + + /// + /// Event handler for the to install the database + /// + /// + /// + protected void UseTestLocalDb(CoreRuntime sender, RuntimeEssentialsEventArgs args) + { + // This will create a db, install the schema and ensure the app is configured to run + InstallTestLocalDb(args.DatabaseFactory, sender.ProfilingLogger, sender.Configs.Global(), sender.State, TestHelper.WorkingDirectory, out var connectionString); + TestDBConnectionString = connectionString; + InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString; + } + /// /// Get or create an instance of /// @@ -54,7 +248,7 @@ namespace Umbraco.Tests.Integration.Testing /// /// There must only be ONE instance shared between all tests in a session /// - public static LocalDbTestDatabase GetOrCreate(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory) + private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory) { lock (_dbLocker) { @@ -68,86 +262,104 @@ namespace Umbraco.Tests.Integration.Testing } } - private static readonly object _dbLocker = new object(); - private static LocalDbTestDatabase _dbInstance; - - private Action _testTeardown = null; - private Action _fixtureTeardown = null; - - public void OnTestTearDown(Action tearDown) + /// + /// Creates a LocalDb instance to use for the test + /// + /// + /// + /// + /// + /// + private void InstallTestLocalDb( + IUmbracoDatabaseFactory databaseFactory, IProfilingLogger logger, IGlobalSettings globalSettings, + IRuntimeState runtimeState, string workingDirectory, out string connectionString) { - _testTeardown = tearDown; - } + connectionString = null; + var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - public void OnFixtureTearDown(Action tearDown) - { - _fixtureTeardown = tearDown; - } - - [OneTimeTearDown] - public void FixtureTearDown() - { - _fixtureTeardown?.Invoke(); - } - - [TearDown] - public void TearDown() - { - _testTeardown?.Invoke(); - } - - [SetUp] - public async Task Setup() - { - var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); - var testHelper = new TestHelper(); // get the currently set db options var testOptions = TestOptionAttributeBase.GetTestOptions(); - var hostBuilder = new HostBuilder() - .UseUmbraco(serviceProviderFactory) - .ConfigureServices((hostContext, services) => - { - services.AddSingleton(testHelper.DbProviderFactoryCreator); - var webHostEnvironment = testHelper.GetWebHostEnvironment(); - services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); + if (testOptions.Database == UmbracoTestOptions.Database.None) + return; - // Add it! - services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); - services.AddUmbracoWebComponents(); - services.AddUmbracoRuntimeMinifier(hostContext.Configuration); - services.AddUmbracoBackOffice(); - services.AddUmbracoBackOfficeIdentity(); + // need to manually register this factory + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - services.AddMvc(); + if (!Directory.Exists(dbFilePath)) + Directory.CreateDirectory(dbFilePath); + var db = GetOrCreateDatabase(dbFilePath, logger, globalSettings, databaseFactory); - services.AddSingleton(new ConsoleLogger(new MessageTemplates())); - - CustomTestSetup(services); - }); - - var host = await hostBuilder.StartAsync(); - var app = new ApplicationBuilder(host.Services); - Services = app.ApplicationServices; - - Services.GetRequiredService().EnsureUmbracoContext(); - // This will create a db, install the schema and ensure the app is configured to run - app.UseTestLocalDb(testHelper.WorkingDirectory, this, out var connectionString); - TestDBConnectionString = connectionString; - - if (testOptions.Boot) + switch (testOptions.Database) { - app.UseUmbracoCore(); - } + case UmbracoTestOptions.Database.NewSchemaPerTest: + // New DB + Schema + var newSchemaDbId = db.AttachSchema(); + + // Add teardown callback + OnTestTearDown(() => db.Detach(newSchemaDbId)); + + // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings + if (!databaseFactory.Configured) + { + databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer); + } + + // re-run the runtime level check + runtimeState.DetermineRuntimeLevel(); + + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewEmptyPerTest: + + var newEmptyDbId = db.AttachEmpty(); + + // Add teardown callback + OnTestTearDown(() => db.Detach(newEmptyDbId)); + + + break; + case UmbracoTestOptions.Database.NewSchemaPerFixture: + + // New DB + Schema + var newSchemaFixtureDbId = db.AttachSchema(); + + // Add teardown callback + OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId)); + + break; + case UmbracoTestOptions.Database.NewEmptyPerFixture: + + throw new NotImplementedException(); + + //// Add teardown callback + //integrationTest.OnFixtureTearDown(() => db.Detach()); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null); + } + connectionString = db.ConnectionString; } - protected T GetRequiredService() => Services.GetRequiredService(); + #endregion #region Common services + protected LightInjectContainer UmbracoContainer { get; private set; } + private UmbracoServiceProviderFactory _serviceProviderFactory; + + protected virtual T GetRequiredService() => Services.GetRequiredService(); + + public Dictionary InMemoryConfiguration { get; } = new Dictionary(); + + public IConfiguration Configuration { get; protected set; } + + public TestHelper TestHelper = new TestHelper(); + protected string TestDBConnectionString { get; private set; } protected virtual Action CustomTestSetup => services => { }; @@ -155,7 +367,7 @@ namespace Umbraco.Tests.Integration.Testing /// /// Returns the DI container /// - protected IServiceProvider Services { get; private set; } + protected IServiceProvider Services { get; set; } /// /// Returns the diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 072f15bcd1..ab11207df2 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 0165e7714f..e86c5702e3 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Routing //create the module var logger = Mock.Of(); var globalSettings = TestObjects.GetGlobalSettings(); - var runtime = new RuntimeState(globalSettings, UmbracoVersion); + var runtime = Umbraco.Core.RuntimeState.Booting(); _module = new UmbracoInjectedModule ( diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index f63c56b64e..c338cf8a50 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -125,7 +125,7 @@ namespace Umbraco.Tests.Runtimes // must override the database factory // else BootFailedException because U cannot connect to the configured db - protected internal override IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal override IUmbracoDatabaseFactory CreateDatabaseFactory() { var mock = new Mock(); mock.Setup(x => x.Configured).Returns(true); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index ac0ff3f9f9..bf26b2167c 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Runtimes var mainDom = new SimpleMainDom(); var umbracoVersion = TestHelper.GetUmbracoVersion(); var backOfficeInfo = TestHelper.GetBackOfficeInfo(); - var runtimeState = new RuntimeState(null, umbracoVersion); + var runtimeState = new RuntimeState(globalSettings, umbracoVersion, databaseFactory, logger); var configs = TestHelper.GetConfigs(); var variationContextAccessor = TestHelper.VariationContextAccessor; @@ -87,7 +87,7 @@ namespace Umbraco.Tests.Runtimes var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance); // determine actual runtime level - runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + runtimeState.DetermineRuntimeLevel(); Console.WriteLine(runtimeState.Level); // going to be Install BUT we want to force components to be there (nucache etc) runtimeState.Level = RuntimeLevel.Run; diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 9aeb668518..5c968f2f90 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -78,8 +78,6 @@ namespace Umbraco.Tests.TestHelpers public static Configs GetConfigs() => _testHelperInternal.GetConfigs(); - public static IRuntimeState GetRuntimeState() => _testHelperInternal.GetRuntimeState(); - public static IBackOfficeInfo GetBackOfficeInfo() => _testHelperInternal.GetBackOfficeInfo(); public static IConfigsFactory GetConfigsFactory() => _testHelperInternal.GetConfigsFactory(); diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs similarity index 79% rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index 282668e4e6..3cba66812e 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -7,7 +7,7 @@ using Umbraco.Web.BackOffice.Security; namespace Umbraco.Extensions { - public static class UmbracoBackOfficeApplicationBuilderExtensions + public static class BackOfficeApplicationBuilderExtensions { public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app) { @@ -44,11 +44,6 @@ namespace Umbraco.Extensions app.UseUmbracoRuntimeMinification(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. - // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoWebsite too? - app.UseImageSharp(); - app.UseStaticFiles(); - app.UseMiddleware(); return app; diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs similarity index 64% rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index fc3efab5e0..f135a72293 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -20,51 +20,9 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Extensions { - public static class UmbracoBackOfficeServiceCollectionExtensions + + public static class BackOfficeServiceCollectionExtensions { - public static IServiceCollection AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - // TODO: We will need to decide on if we want to use the ServiceBasedControllerActivator to create our controllers - // or use the default IControllerActivator: DefaultControllerActivator (which doesn't directly use the container to resolve controllers) - // This will affect whether we need to explicitly register controllers in the container like we do today in v8. - // What we absolutely must do though is make sure we explicitly opt-in to using one or the other *always* for our controllers instead of - // relying on a global configuration set by a user since if a custom IControllerActivator is used for our own controllers we may not - // guarantee it will work. And then... is that even possible? - - // TODO: we will need to simplify this and prob just have a one or 2 main method that devs call which call all other required methods, - // but for now we'll just be explicit with all of them - services.AddUmbracoConfiguration(config); - services.AddUmbracoCore(webHostEnvironment, out var factory); - services.AddUmbracoWebComponents(); - services.AddUmbracoRuntimeMinifier(config); - services.AddUmbracoBackOffice(); - services.AddUmbracoBackOfficeIdentity(); - services.AddMiniProfiler(options => - { - options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile - }); - - //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. - //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. - services.AddControllersWithViews().AddRazorRuntimeCompilation(); - - - // If using Kestrel: https://stackoverflow.com/a/55196057 - services.Configure(options => - { - options.AllowSynchronousIO = true; - }); - - services.Configure(options => - { - options.AllowSynchronousIO = true; - }); - - return services; - } - /// /// Adds the services required for running the Umbraco back office /// @@ -105,7 +63,7 @@ namespace Umbraco.Extensions // Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance services.ConfigureOptions(); services.ConfigureOptions(); - } + } private static IdentityBuilder BuildUmbracoBackOfficeIdentity(this IServiceCollection services) { diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..71325d70d4 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -0,0 +1,28 @@ +using Umbraco.Web.Common.Builder; + +namespace Umbraco.Extensions +{ + public static class UmbracoBuilderExtensions + { + public static void BuildWithAllBackOfficeComponents(this IUmbracoBuilder builder) + { + builder + .WithConfiguration() + .WithCore() + .WithWebComponents() + .WithRuntimeMinifier() + .WithBackOffice() + .WithBackOfficeIdentity() + .WithMiniProfiler() + .WithMvcAndRazor() + .WithWebServer() + .Build(); + } + + public static IUmbracoBuilder WithBackOffice(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithBackOffice), () => builder.Services.AddUmbracoBackOffice()); + + public static IUmbracoBuilder WithBackOfficeIdentity(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithBackOfficeIdentity), () => builder.Services.AddUmbracoBackOfficeIdentity()); + } +} diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 2547117531..1c66edc2ce 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs new file mode 100644 index 0000000000..2de7d2d285 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Text; + +namespace Umbraco.Web.Common.Builder +{ + + public interface IUmbracoBuilder + { + IServiceCollection Services { get; } + IWebHostEnvironment WebHostEnvironment { get; } + IConfiguration Config { get; } + IUmbracoBuilder AddWith(string key, Action add); + void Build(); + } +} diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs new file mode 100644 index 0000000000..3efb1e74f5 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; + +namespace Umbraco.Web.Common.Builder +{ + public class UmbracoBuilder : IUmbracoBuilder + { + private readonly Dictionary _registrations = new Dictionary(); + + public UmbracoBuilder(IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + Services = services; + WebHostEnvironment = webHostEnvironment; + Config = config; + } + + public IServiceCollection Services { get; } + public IWebHostEnvironment WebHostEnvironment { get; } + public IConfiguration Config { get; } + + public IUmbracoBuilder AddWith(string key, Action add) + { + if (_registrations.ContainsKey(key)) return this; + _registrations[key] = add; + return this; + } + + public void Build() + { + foreach (var a in _registrations) + a.Value(); + } + } +} diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..dd91a2cca9 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using Umbraco.Extensions; + +namespace Umbraco.Web.Common.Builder +{ + // TODO: We could add parameters to configure each of these for flexibility + public static class UmbracoBuilderExtensions + { + public static IUmbracoBuilder AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + if (services is null) throw new ArgumentNullException(nameof(services)); + if (webHostEnvironment is null) throw new ArgumentNullException(nameof(webHostEnvironment)); + if (config is null) throw new ArgumentNullException(nameof(config)); + + var builder = new UmbracoBuilder(services, webHostEnvironment, config); + return builder; + } + + public static IUmbracoBuilder WithConfiguration(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithConfiguration), () => builder.Services.AddUmbracoConfiguration(builder.Config)); + + public static IUmbracoBuilder WithCore(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithCore), () => builder.Services.AddUmbracoCore(builder.WebHostEnvironment, out _)); + + public static IUmbracoBuilder WithMiniProfiler(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithMiniProfiler), () => + builder.Services.AddMiniProfiler(options => + { + options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile + })); + + public static IUmbracoBuilder WithMvcAndRazor(this IUmbracoBuilder builder, Action mvcOptions = null, Action mvcBuilding = null) + => builder.AddWith(nameof(WithMvcAndRazor), () => + { + // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important + // this will directly affect developers who need to call that themselves. + //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. + //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. + var mvcBuilder = builder.Services.AddControllersWithViews(mvcOptions).AddRazorRuntimeCompilation(); + mvcBuilding?.Invoke(mvcBuilder); + }); + + public static IUmbracoBuilder WithRuntimeMinifier(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithRuntimeMinifier), () => builder.Services.AddUmbracoRuntimeMinifier(builder.Config)); + + public static IUmbracoBuilder WithWebComponents(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithWebComponents), () => builder.Services.AddUmbracoWebComponents()); + + public static IUmbracoBuilder WithWebServer(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithWebServer), () => + { + // TODO: We need to figure out why thsi is needed and fix those endpoints to not need them, we don't want to change global things + // If using Kestrel: https://stackoverflow.com/a/55196057 + builder.Services.Configure(options => + { + options.AllowSynchronousIO = true; + }); + builder.Services.Configure(options => + { + options.AllowSynchronousIO = true; + }); + }); + } +} diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index d18275ca7f..f4ae6ed5e5 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -178,6 +178,31 @@ namespace Umbraco.Extensions IRequestCache requestCache, ILoggingConfiguration loggingConfiguration, out IFactory factory) + => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, requestCache, loggingConfiguration, GetCoreRuntime, out factory); + + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + /// + /// + /// + /// Delegate to create an + /// + /// + public static IServiceCollection AddUmbracoCore( + this IServiceCollection services, + IWebHostEnvironment webHostEnvironment, + IRegister umbContainer, + Assembly entryAssembly, + IRequestCache requestCache, + ILoggingConfiguration loggingConfiguration, + // TODO: Yep that's extremely ugly + Func getRuntime, + out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; @@ -216,7 +241,7 @@ namespace Umbraco.Extensions var umbracoVersion = new UmbracoVersion(globalSettings); var typeFinder = CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly, configs.TypeFinder()); - var coreRuntime = GetCoreRuntime( + var runtime = getRuntime( configs, umbracoVersion, ioHelper, @@ -228,7 +253,7 @@ namespace Umbraco.Extensions requestCache, dbProviderFactoryCreator); - factory = coreRuntime.Configure(container); + factory = runtime.Configure(container); return services; } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 2c8ce61d9a..ee657e82bb 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 673ac02013..72e0d792f3 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Extensions; +using Umbraco.Web.Common.Builder; namespace Umbraco.Web.UI.NetCore { @@ -31,7 +32,8 @@ namespace Umbraco.Web.UI.NetCore // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddUmbraco(_env, _config); + var umbracoBuilder = services.AddUmbraco(_env, _config); + umbracoBuilder.BuildWithAllBackOfficeComponents(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 27586d9f19a1121d9f949c5825177ccce3bf054b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Sep 2020 18:19:27 +1000 Subject: [PATCH 03/14] removes proj ref that wasn't needed --- src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index ab11207df2..072f15bcd1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -30,7 +30,6 @@ - From b5d2adaf25b4bda0d2a82b914af1ff71733e5bc2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Sep 2020 18:20:30 +1000 Subject: [PATCH 04/14] remove explicit --- .../TestServerTest/Controllers/ContentControllerTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index 57be7e2e05..9a1b335c62 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -18,7 +18,6 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Integration.TestServerTest.Controllers { [TestFixture] - //[Explicit("Need to figure out whats wrong with these tests when executed all in one run.")] public class ContentControllerTests : UmbracoTestServerTestBase { From a5772f10e14c03d3a55b51b660e86317ab4f7afd Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Sep 2020 18:21:51 +1000 Subject: [PATCH 05/14] comments --- .../TestServerTest/UmbracoTestServerTestBase.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 18a5836223..145b9d2848 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -129,10 +129,6 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { - //services.AddSingleton(TestHelper.DbProviderFactoryCreator); - //var webHostEnvironment = TestHelper.GetWebHostEnvironment(); - //services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); - var umbracoBuilder = services.AddUmbraco(TestHelper.GetWebHostEnvironment(), Configuration); umbracoBuilder .WithConfiguration() @@ -141,7 +137,7 @@ namespace Umbraco.Tests.Integration.TestServerTest .WithRuntimeMinifier() .WithBackOffice() .WithBackOfficeIdentity() - //.WithMiniProfiler() + //.WithMiniProfiler() // we don't want this running in tests .WithMvcAndRazor(mvcBuilding: mvcBuilder => { mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); From 7c78b5b34f18bee22ee04f21fa346575b4ef97a3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 00:27:43 +1000 Subject: [PATCH 06/14] Updates CoreRuntime with better dependencies so we can disable caches, fixes integration test to run multiple callbacks registered for TearDown, ensure we TearDown the runtime. --- .../Implement/ContentTypeRepository.cs | 5 +- .../Implement/ContentTypeRepositoryBase.cs | 18 ++++++ .../Implement/MediaTypeRepository.cs | 5 +- .../Implement/MemberTypeRepository.cs | 5 +- .../Runtime/CoreRuntime.cs | 36 +++-------- src/Umbraco.Tests.Integration/RuntimeTests.cs | 6 +- .../UmbracoBuilderExtensions.cs | 22 +++++-- .../UmbracoTestServerTestBase.cs | 17 ++--- .../Testing/IntegrationTestComposer.cs | 2 + .../Testing/UmbracoIntegrationTest.cs | 62 +++++++++++++------ .../Routing/RenderRouteHandlerTests.cs | 2 +- .../Runtimes/CoreRuntimeTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../UmbracoCoreServiceCollectionExtensions.cs | 30 +++++---- src/Umbraco.Web/UmbracoApplication.cs | 7 ++- 15 files changed, 133 insertions(+), 90 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index 61aabdc1b5..868e088d93 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -54,11 +54,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override bool PerformExists(Guid id) => GetMany().FirstOrDefault(x => x.Key == id) != null; - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index b2a83e83fd..50ba0a698b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -90,6 +90,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return moveInfo; } + protected override IEnumerable PerformGetAll(params int[] ids) + { + var result = GetAllWithFullCachePolicy(); + + // By default the cache policy will always want everything + // even GetMany(ids) gets everything and filters afterwards, + // however if we are using No Cache, we must still be able to support + // collections of Ids, so this is to work around that: + if (ids.Any()) + { + return result.Where(x => ids.Contains(x.Id)); + } + + return result; + } + + protected abstract IEnumerable GetAllWithFullCachePolicy(); + protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType storageType, string propertyTypeAlias) { return new PropertyType(_shortStringHelper, propertyEditorAlias, storageType, propertyTypeAlias); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index c6caa40750..818b5c9f21 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -48,11 +48,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IMediaType PerformGet(string alias) => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index b9c40eebc9..059035a9bc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -59,11 +59,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IMemberType PerformGet(string alias) => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index ff86f87569..2a925f9de9 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -27,12 +27,11 @@ namespace Umbraco.Core.Runtime // runtime state, this instance will get replaced again once the essential services are available to run the check private RuntimeState _state = RuntimeState.Booting(); private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; - private readonly IRequestCache _requestCache; private readonly IGlobalSettings _globalSettings; private readonly IConnectionStrings _connectionStrings; public CoreRuntime( - Configs configs, + Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, @@ -43,10 +42,11 @@ namespace Umbraco.Core.Runtime IDbProviderFactoryCreator dbProviderFactoryCreator, IMainDom mainDom, ITypeFinder typeFinder, - IRequestCache requestCache) + AppCaches appCaches) { IOHelper = ioHelper; Configs = configs; + AppCaches = appCaches; UmbracoVersion = umbracoVersion ; Profiler = profiler; HostingEnvironment = hostingEnvironment; @@ -54,8 +54,7 @@ namespace Umbraco.Core.Runtime DbProviderFactoryCreator = dbProviderFactoryCreator; _umbracoBootPermissionChecker = umbracoBootPermissionChecker; - _requestCache = requestCache; - + Logger = logger; MainDom = mainDom; TypeFinder = typeFinder; @@ -95,6 +94,7 @@ namespace Umbraco.Core.Runtime protected IIOHelper IOHelper { get; } protected IHostingEnvironment HostingEnvironment { get; } public Configs Configs { get; } + public AppCaches AppCaches { get; } public IUmbracoVersion UmbracoVersion { get; } /// @@ -161,28 +161,25 @@ namespace Umbraco.Core.Runtime // run handlers OnRuntimeBoot(); - // application caches - var appCaches = CreateAppCaches(); - // database factory var databaseFactory = CreateDatabaseFactory(); // type finder/loader - var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); + var typeLoader = new TypeLoader(TypeFinder, AppCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); // re-create the state object with the essential services _state = new RuntimeState(Configs.Global(), UmbracoVersion, databaseFactory, Logger); // create the composition - composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches); + composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, AppCaches); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, AppCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); // register ourselves (TODO: Should we put this in RegisterEssentials?) composition.Register(_ => this, Lifetime.Singleton); // run handlers - OnRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + OnRuntimeEssentials(composition, AppCaches, typeLoader, databaseFactory); try { @@ -366,21 +363,6 @@ namespace Umbraco.Core.Runtime protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) => typeLoader.GetTypes(); - /// - /// Creates the application caches. - /// - protected virtual AppCaches CreateAppCaches() - { - // need the deep clone runtime cache provider to ensure entities are cached properly, ie - // are cloned in and cloned out - no request-based cache here since no web-based context, - // is overridden by the web runtime - - return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), - _requestCache, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - } - /// /// Returns the application path of the site/solution /// diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index b41ef4ac02..07a0db167c 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Integration var coreRuntime = new CoreRuntime(testHelper.GetConfigs(), testHelper.GetUmbracoVersion(), testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker, testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator, - testHelper.MainDom, testHelper.GetTypeFinder(), NoAppCache.Instance); + testHelper.MainDom, testHelper.GetTypeFinder(), AppCaches.NoCache); // boot it! var factory = coreRuntime.Configure(umbracoContainer); @@ -101,7 +101,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), out _); }); var host = await hostBuilder.StartAsync(); @@ -141,7 +141,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs index 7c919e3894..ba96c06826 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -26,13 +26,27 @@ namespace Umbraco.Tests.Integration.TestServerTest builder.WebHostEnvironment, container, typeof(UmbracoBuilderExtensions).Assembly, - NoAppCache.Instance, + AppCaches.NoCache, // Disable caches in integration tests testHelper.GetLoggingConfiguration(), // TODO: Yep that's extremely ugly - (a, b, c, d, e, f, g, h, i, j) => - UmbracoIntegrationTest.CreateTestRuntime(a, b, c, d, e, f, g, h, i, j, + (configs, umbVersion, ioHelper, logger, profiler, hostingEnv, backOfficeInfo, typeFinder, appCaches, dbProviderFactoryCreator) => + { + var runtime = UmbracoIntegrationTest.CreateTestRuntime( + configs, + umbVersion, + ioHelper, + logger, + profiler, + hostingEnv, + backOfficeInfo, + typeFinder, + appCaches, + dbProviderFactoryCreator, testHelper.MainDom, // SimpleMainDom - dbInstallEventHandler), // DB Installation event handler + dbInstallEventHandler); // DB Installation event handler + + return runtime; + }, out _); }); } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 145b9d2848..08be22c07b 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -49,8 +49,8 @@ namespace Umbraco.Tests.Integration.TestServerTest }); Client = Factory.CreateClient(new WebApplicationFactoryClientOptions - { - AllowAutoRedirect = false + { + AllowAutoRedirect = false }); LinkGenerator = Factory.Services.GetRequiredService(); @@ -62,9 +62,10 @@ namespace Umbraco.Tests.Integration.TestServerTest { var builder = base.CreateHostBuilder(); builder.ConfigureWebHost(builder => - { + { // need to configure the IWebHostEnvironment too - builder.ConfigureServices((c, s) => { + builder.ConfigureServices((c, s) => + { c.HostingEnvironment = TestHelper.GetWebHostEnvironment(); }); @@ -74,8 +75,8 @@ namespace Umbraco.Tests.Integration.TestServerTest Services = app.ApplicationServices; Configure(app); }); - }) - .UseEnvironment(Environments.Development); + }).UseEnvironment(Environments.Development); + return builder; } @@ -140,14 +141,14 @@ namespace Umbraco.Tests.Integration.TestServerTest //.WithMiniProfiler() // we don't want this running in tests .WithMvcAndRazor(mvcBuilding: mvcBuilder => { - mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); + mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); }) .WithWebServer() .Build(); } public override void Configure(IApplicationBuilder app) - { + { app.UseUmbraco(); } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 97482f9482..7cf114bfc3 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.WebAssets; using Umbraco.Examine; +using Umbraco.Web.Compose; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.Scheduling; using Umbraco.Web.Search; @@ -28,6 +29,7 @@ namespace Umbraco.Tests.Integration.Testing base.Compose(composition); composition.Components().Remove(); + composition.Components().Remove(); composition.RegisterUnique(); composition.RegisterUnique(factory => Mock.Of()); diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index ee03e2a146..c9e88b9deb 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -25,7 +25,6 @@ using Umbraco.Core.Runtime; using Umbraco.Core; using Moq; using System.Collections.Generic; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using System.Data.SqlClient; using System.Data.Common; @@ -52,18 +51,30 @@ namespace Umbraco.Tests.Integration.Testing return umbracoContainer; } - private Action _testTeardown = null; - private Action _fixtureTeardown = null; + private List _testTeardown = null; + private List _fixtureTeardown = new List(); - public void OnTestTearDown(Action tearDown) => _testTeardown = tearDown; + public void OnTestTearDown(Action tearDown) + { + if (_testTeardown == null) + _testTeardown = new List(); + _testTeardown.Add(tearDown); + } - public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown = tearDown; + public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); [OneTimeTearDown] - public void FixtureTearDown() => _fixtureTeardown?.Invoke(); + public void FixtureTearDown() + { + foreach (var a in _fixtureTeardown) a(); + } [TearDown] - public virtual void TearDown() => _testTeardown?.Invoke(); + public virtual void TearDown() + { + foreach (var a in _testTeardown) a(); + _testTeardown = null; + } [SetUp] public virtual async Task Setup() @@ -117,14 +128,14 @@ namespace Umbraco.Tests.Integration.Testing /// /// /// - /// + /// /// /// public CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator) + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) { - return CreateTestRuntime(configs, + var runtime = CreateTestRuntime(configs, umbracoVersion, ioHelper, logger, @@ -132,11 +143,13 @@ namespace Umbraco.Tests.Integration.Testing hostingEnvironment, backOfficeInfo, typeFinder, - requestCache, + appCaches, dbProviderFactoryCreator, TestHelper.MainDom, // SimpleMainDom UseTestLocalDb // DB Installation event handler - ); + ); + + return runtime; } /// @@ -157,7 +170,7 @@ namespace Umbraco.Tests.Integration.Testing /// public static CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator, + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator, IMainDom mainDom, Action eventHandler) { var runtime = new CoreRuntime( @@ -172,7 +185,7 @@ namespace Umbraco.Tests.Integration.Testing dbProviderFactoryCreator, mainDom, typeFinder, - requestCache); + appCaches); runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args); @@ -191,7 +204,15 @@ namespace Umbraco.Tests.Integration.Testing // Add it! services.AddUmbracoConfiguration(Configuration); - services.AddUmbracoCore(webHostEnvironment, UmbracoContainer, GetType().Assembly, NoAppCache.Instance, TestHelper.GetLoggingConfiguration(), CreateTestRuntime, out _); + services.AddUmbracoCore( + webHostEnvironment, + UmbracoContainer, + GetType().Assembly, + AppCaches.NoCache, // Disable caches for integration tests + TestHelper.GetLoggingConfiguration(), + CreateTestRuntime, + out _); + services.AddUmbracoWebComponents(); services.AddUmbracoRuntimeMinifier(Configuration); services.AddUmbracoBackOffice(); @@ -225,14 +246,17 @@ namespace Umbraco.Tests.Integration.Testing private static LocalDbTestDatabase _dbInstance; /// - /// Event handler for the to install the database + /// Event handler for the to install the database and register the to Terminate /// - /// + /// /// - protected void UseTestLocalDb(CoreRuntime sender, RuntimeEssentialsEventArgs args) + protected void UseTestLocalDb(CoreRuntime runtime, RuntimeEssentialsEventArgs args) { + // MUST be terminated on teardown + OnTestTearDown(() => runtime.Terminate()); + // This will create a db, install the schema and ensure the app is configured to run - InstallTestLocalDb(args.DatabaseFactory, sender.ProfilingLogger, sender.Configs.Global(), sender.State, TestHelper.WorkingDirectory, out var connectionString); + InstallTestLocalDb(args.DatabaseFactory, runtime.ProfilingLogger, runtime.Configs.Global(), runtime.State, TestHelper.WorkingDirectory, out var connectionString); TestDBConnectionString = connectionString; InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString; } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 358a47f09b..9a4531b4e9 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Routing public class TestRuntime : CoreRuntime { public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - : base(configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance) + : base(configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), AppCaches.NoCache) { } diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index c338cf8a50..e53e6bfb8a 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Runtimes public class TestRuntime : CoreRuntime { public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance) + :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), AppCaches.NoCache) { } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index bf26b2167c..70dfc9ccc6 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -84,7 +84,7 @@ namespace Umbraco.Tests.Runtimes composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, hostingEnvironment, backOfficeInfo); // create the core runtime and have it compose itself - var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance); + var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, AppCaches.NoCache); // determine actual runtime level runtimeState.DetermineRuntimeLevel(); @@ -278,7 +278,7 @@ namespace Umbraco.Tests.Runtimes composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, hostingEnvironment, backOfficeInfo); // create the core runtime and have it compose itself - var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance); + var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, AppCaches.NoCache); // get the components // all of them? diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index f4ae6ed5e5..b5d581afd7 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -146,12 +146,17 @@ namespace Umbraco.Extensions IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); + var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); + var appCaches = new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + requestCache, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), - requestCache, + appCaches, loggingConfig, out factory); @@ -165,7 +170,7 @@ namespace Umbraco.Extensions /// /// /// - /// + /// /// /// /// @@ -175,10 +180,11 @@ namespace Umbraco.Extensions IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, - IRequestCache requestCache, + AppCaches appCaches, ILoggingConfiguration loggingConfiguration, out IFactory factory) - => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, requestCache, loggingConfiguration, GetCoreRuntime, out factory); + => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, appCaches, loggingConfiguration, GetCoreRuntime, out factory); + /// /// Adds the Umbraco Back Core requirements @@ -187,7 +193,7 @@ namespace Umbraco.Extensions /// /// /// - /// + /// /// /// /// Delegate to create an @@ -198,10 +204,10 @@ namespace Umbraco.Extensions IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, - IRequestCache requestCache, + AppCaches appCaches, ILoggingConfiguration loggingConfiguration, // TODO: Yep that's extremely ugly - Func getRuntime, + Func getRuntime, out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); @@ -242,7 +248,7 @@ namespace Umbraco.Extensions var typeFinder = CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly, configs.TypeFinder()); var runtime = getRuntime( - configs, + configs, umbracoVersion, ioHelper, logger, @@ -250,7 +256,7 @@ namespace Umbraco.Extensions hostingEnvironment, backOfficeInfo, typeFinder, - requestCache, + appCaches, dbProviderFactoryCreator); factory = runtime.Configure(container); @@ -269,7 +275,7 @@ namespace Umbraco.Extensions private static IRuntime GetCoreRuntime( Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator) + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) { // Determine if we should use the sql main dom or the default @@ -285,7 +291,7 @@ namespace Umbraco.Extensions var mainDom = new MainDom(logger, mainDomLock); var coreRuntime = new CoreRuntime( - configs, + configs, umbracoVersion, ioHelper, logger, @@ -296,7 +302,7 @@ namespace Umbraco.Extensions dbProviderFactoryCreator, mainDom, typeFinder, - requestCache); + appCaches); return coreRuntime; } diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 6334f96d4b..2e9ba11866 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -43,9 +43,14 @@ namespace Umbraco.Web var mainDom = new MainDom(logger, mainDomLock); var requestCache = new HttpRequestAppCache(() => HttpContext.Current != null ? HttpContext.Current.Items : null); + var appCaches = new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + requestCache, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker(); return new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, - GetTypeFinder(hostingEnvironment, logger, profiler), requestCache); + GetTypeFinder(hostingEnvironment, logger, profiler), appCaches); } From 16e4ffb725ca31ec3c9dd7e51c41f87ad1c88c81 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 00:28:38 +1000 Subject: [PATCH 07/14] Fixes unsubscribing from static events on Terminate --- .../Compose/NotificationsComponent.cs | 100 ++++++++++++------ .../Compose/PublicAccessComponent.cs | 10 +- .../Compose/RelateOnCopyComponent.cs | 4 +- .../Compose/RelateOnTrashComponent.cs | 55 +++++----- .../PropertyEditorsComponent.cs | 61 +++++++---- .../Routing/RedirectTrackingComponent.cs | 7 +- .../Search/ExamineComponent.cs | 9 +- .../Compose/ModelsBuilderComponent.cs | 44 ++++---- .../Profiler/WebProfilerComponent.cs | 17 ++- .../Compose/AuditEventsComponent.cs | 14 ++- .../BackOfficeUserAuditEventsComponent.cs | 14 ++- .../Logging/WebProfilerComponent.cs | 4 +- 12 files changed, 227 insertions(+), 112 deletions(-) diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 3a4b8a5fac..b1303859e0 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -5,11 +5,9 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -33,42 +31,78 @@ namespace Umbraco.Web.Compose public void Initialize() { //Send notifications for the send to publish action - ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.SentToPublish += ContentService_SentToPublish; //Send notifications for the published action - ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Published += ContentService_Published; //Send notifications for the saved action - ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions); - + ContentService.Sorted += ContentService_Sorted; //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions); - + ContentService.Saved += ContentService_Saved; //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Unpublished += ContentService_Unpublished; //Send notifications for the move/move to recycle bin and restore actions - ContentService.Moved += (sender, args) => ContentServiceMoved(_notifier, sender, args, _actions); - + ContentService.Moved += ContentService_Moved; //Send notifications for the delete action when content is moved to the recycle bin - ContentService.Trashed += (sender, args) => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - + ContentService.Trashed += ContentService_Trashed; //Send notifications for the copy action - ContentService.Copied += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Original); - + ContentService.Copied += ContentService_Copied; //Send notifications for the rollback action - ContentService.RolledBack += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.RolledBack += ContentService_RolledBack; //Send notifications for the public access changed action - PublicAccessService.Saved += (sender, args) => PublicAccessServiceSaved(_notifier, sender, args, _contentService, _actions); + PublicAccessService.Saved += PublicAccessService_Saved; - UserService.UserGroupPermissionsAssigned += (sender, args) => UserServiceUserGroupPermissionsAssigned(_notifier, sender, args, _contentService, _actions); + UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned; } public void Terminate() - { } + { + ContentService.SentToPublish -= ContentService_SentToPublish; + ContentService.Published -= ContentService_Published; + ContentService.Sorted -= ContentService_Sorted; + ContentService.Saved -= ContentService_Saved; + ContentService.Unpublished -= ContentService_Unpublished; + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + ContentService.Copied -= ContentService_Copied; + ContentService.RolledBack -= ContentService_RolledBack; + PublicAccessService.Saved -= PublicAccessService_Saved; + UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; + } - private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void UserService_UserGroupPermissionsAssigned(IUserService sender, Core.Events.SaveEventArgs args) + => UserServiceUserGroupPermissionsAssigned(args, _contentService); + + private void PublicAccessService_Saved(IPublicAccessService sender, Core.Events.SaveEventArgs args) + => PublicAccessServiceSaved(args, _contentService); + + private void ContentService_RolledBack(IContentService sender, Core.Events.RollbackEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentService_Copied(IContentService sender, Core.Events.CopyEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Original); + + private void ContentService_Trashed(IContentService sender, Core.Events.MoveEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + private void ContentService_Moved(IContentService sender, Core.Events.MoveEventArgs args) + => ContentServiceMoved(args); + + private void ContentService_Unpublished(IContentService sender, Core.Events.PublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_Saved(IContentService sender, Core.Events.ContentSavedEventArgs args) + => ContentServiceSaved(args); + + private void ContentService_Sorted(IContentService sender, Core.Events.SaveEventArgs args) + => ContentServiceSorted(sender, args); + + private void ContentService_Published(IContentService sender, Core.Events.ContentPublishedEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_SentToPublish(IContentService sender, Core.Events.SendToPublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentServiceSorted(IContentService sender, Core.Events.SaveEventArgs args) { var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id @@ -80,10 +114,10 @@ namespace Umbraco.Web.Compose var parent = sender.GetById(parentId[0]); if (parent == null) return; // this shouldn't happen - notifier.Notify(actions.GetAction(), new[] { parent }); + _notifier.Notify(_actions.GetAction(), new[] { parent }); } - private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void ContentServiceSaved(Core.Events.SaveEventArgs args) { var newEntities = new List(); var updatedEntities = new List(); @@ -103,21 +137,21 @@ namespace Umbraco.Web.Compose updatedEntities.Add(entity); } } - notifier.Notify(actions.GetAction(), newEntities.ToArray()); - notifier.Notify(actions.GetAction(), updatedEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); } - private void UserServiceUserGroupPermissionsAssigned(Notifier notifier, IUserService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void UserServiceUserGroupPermissionsAssigned(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); if(entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } - private void ContentServiceMoved(Notifier notifier, IContentService sender, Core.Events.MoveEventArgs args, ActionCollection actions) + private void ContentServiceMoved(Core.Events.MoveEventArgs args) { // notify about the move for all moved items _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); @@ -133,14 +167,14 @@ namespace Umbraco.Web.Compose } } - private void PublicAccessServiceSaved(Notifier notifier, IPublicAccessService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void PublicAccessServiceSaved(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); if(entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } /// diff --git a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs b/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs index 37bcfb1ceb..a917cfe0ef 100644 --- a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs @@ -16,13 +16,15 @@ namespace Umbraco.Web.Compose public void Initialize() { - MemberGroupService.Saved += (s, e) => MemberGroupService_Saved(s, e, _publicAccessService); + MemberGroupService.Saved += MemberGroupService_Saved; } public void Terminate() - { } + { + MemberGroupService.Saved -= MemberGroupService_Saved; + } - static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e, IPublicAccessService publicAccessService) + private void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) { foreach (var grp in e.SavedEntities) { @@ -32,7 +34,7 @@ namespace Umbraco.Web.Compose && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false && grp.AdditionalData["previousName"].ToString() != grp.Name) { - publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); + _publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); } } } diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs index 56a97e4cba..3418dfcfc0 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs @@ -23,7 +23,9 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Copied -= ContentServiceCopied; + } private void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs index c81aa2fd7d..aa92972e9c 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs @@ -24,47 +24,52 @@ namespace Umbraco.Core.Compose public void Initialize() { - ContentService.Moved += (sender, args) => ContentService_Moved(sender, args, _relationService); - ContentService.Trashed += (sender, args) => ContentService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService); - MediaService.Moved += (sender, args) => MediaService_Moved(sender, args, _relationService); - MediaService.Trashed += (sender, args) => MediaService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService); + ContentService.Moved += ContentService_Moved; + ContentService.Trashed += ContentService_Trashed; + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; } public void Terminate() - { } + { + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + } - private static void ContentService_Moved(IContentService sender, MoveEventArgs e, IRelationService relationService) + private void ContentService_Moved(IContentService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContentString))) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; - var relations = relationService.GetByChildId(item.Entity.Id); + var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) { - relationService.Delete(relation); + _relationService.Delete(relation); } } } - private static void MediaService_Moved(IMediaService sender, MoveEventArgs e, IRelationService relationService) + private void MediaService_Moved(IMediaService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString))) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relations = relationService.GetByChildId(item.Entity.Id); + var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) { - relationService.Delete(relation); + _relationService.Delete(relation); } } } - private static void ContentService_Trashed(IContentService sender, MoveEventArgs e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService) + private void ContentService_Trashed(IContentService sender, MoveEventArgs e) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; - var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); + var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) @@ -73,7 +78,7 @@ namespace Umbraco.Core.Compose const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); - relationService.Save(relationType); + _relationService.Save(relationType); } foreach (var item in e.MoveInfoCollection) @@ -86,34 +91,34 @@ namespace Umbraco.Core.Compose //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin - if (entityService.Exists(originalParentId)) + if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later var relation = new Relation(originalParentId, item.Entity.Id, relationType); - relationService.Save(relation); + _relationService.Save(relation); - auditService.Add(AuditType.Delete, + _auditService.Add(AuditType.Delete, item.Entity.WriterId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - string.Format(textService.Localize( + string.Format(_textService.Localize( "recycleBin/contentTrashed"), item.Entity.Id, originalParentId)); } } } - private static void MediaService_Trashed(IMediaService sender, MoveEventArgs e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService) + private void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); + var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); - relationService.Save(relationType); + _relationService.Save(relationType); } foreach (var item in e.MoveInfoCollection) { @@ -123,16 +128,16 @@ namespace Umbraco.Core.Compose : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin - if (entityService.Exists(originalParentId)) + if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later var relation = new Relation(originalParentId, item.Entity.Id, relationType); - relationService.Save(relation); - auditService.Add(AuditType.Delete, + _relationService.Save(relation); + _auditService.Add(AuditType.Delete, item.Entity.CreatorId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Media), - string.Format(textService.Localize( + string.Format(_textService.Localize( "recycleBin/mediaTrashed"), item.Entity.Id, originalParentId)); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs index de071d1a1e..cd7b7a1f39 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs @@ -1,7 +1,11 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Composing; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Web.PropertyEditors @@ -9,6 +13,7 @@ namespace Umbraco.Web.PropertyEditors public sealed class PropertyEditorsComponent : IComponent { private readonly PropertyEditorCollection _propertyEditors; + private readonly List _terminate = new List(); public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) { @@ -27,32 +32,48 @@ namespace Umbraco.Web.PropertyEditors } public void Terminate() - { } - - private static void Initialize(FileUploadPropertyEditor fileUpload) { - MediaService.Saving += fileUpload.MediaServiceSaving; - ContentService.Copied += fileUpload.ContentServiceCopied; - - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + foreach (var t in _terminate) t(); } - private static void Initialize(ImageCropperPropertyEditor imageCropper) + private void Initialize(FileUploadPropertyEditor fileUpload) + { + MediaService.Saving += fileUpload.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving); + ContentService.Copied += fileUpload.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied); + + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); + } + + private void Initialize(ImageCropperPropertyEditor imageCropper) { MediaService.Saving += imageCropper.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving); ContentService.Copied += imageCropper.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied); - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } } } diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs index f9256b3692..7fecba7c78 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs @@ -57,7 +57,12 @@ namespace Umbraco.Web.Routing } public void Terminate() - { } + { + ContentService.Publishing -= ContentService_Publishing; + ContentService.Published -= ContentService_Published; + ContentService.Moving -= ContentService_Moving; + ContentService.Moved -= ContentService_Moved; + } private void ContentService_Publishing(IContentService sender, PublishEventArgs args) { diff --git a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs index 35a8804ecb..ae5bfd628b 100644 --- a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs +++ b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Search private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; private readonly BackgroundIndexRebuilder _backgroundIndexRebuilder; - private static object _isConfiguredLocker = new object(); private readonly IScopeProvider _scopeProvider; private readonly ServiceContext _services; private readonly IMainDom _mainDom; @@ -104,7 +103,13 @@ namespace Umbraco.Web.Search } public void Terminate() - { } + { + ContentCacheRefresher.CacheUpdated -= ContentCacheRefresherUpdated; + ContentTypeCacheRefresher.CacheUpdated -= ContentTypeCacheRefresherUpdated; + MediaCacheRefresher.CacheUpdated -= MediaCacheRefresherUpdated; + MemberCacheRefresher.CacheUpdated -= MemberCacheRefresherUpdated; + LanguageCacheRefresher.CacheUpdated -= LanguageCacheRefresherUpdated; + } #region Cache refresher updated event handlers diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 32cfd3057e..30754d3a7f 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -53,32 +53,38 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose } public void Terminate() - { } + { + ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing; + ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException; + FileService.SavingTemplate -= FileService_SavingTemplate; + } private void InstallServerVars() { // register our url - for the backoffice api - ServerVariablesParser.Parsing += (sender, serverVars) => - { - if (!serverVars.ContainsKey("umbracoUrls")) - throw new ArgumentException("Missing umbracoUrls."); - var umbracoUrlsObject = serverVars["umbracoUrls"]; - if (umbracoUrlsObject == null) - throw new ArgumentException("Null umbracoUrls"); - if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new ArgumentException("Invalid umbracoUrls"); + ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; + } - if (!serverVars.ContainsKey("umbracoPlugins")) - throw new ArgumentException("Missing umbracoPlugins."); - if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new ArgumentException("Invalid umbracoPlugins"); + private void ServerVariablesParser_Parsing(object sender, Dictionary serverVars) + { + if (!serverVars.ContainsKey("umbracoUrls")) + throw new ArgumentException("Missing umbracoUrls."); + var umbracoUrlsObject = serverVars["umbracoUrls"]; + if (umbracoUrlsObject == null) + throw new ArgumentException("Null umbracoUrls"); + if (!(umbracoUrlsObject is Dictionary umbracoUrls)) + throw new ArgumentException("Invalid umbracoUrls"); - if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + if (!serverVars.ContainsKey("umbracoPlugins")) + throw new ArgumentException("Missing umbracoPlugins."); + if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) + throw new ArgumentException("Invalid umbracoPlugins"); - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); - umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); - }; + if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); } private Dictionary GetModelsBuilderSettings() diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs index bc5cce9df1..dfd13ff101 100644 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs @@ -1,4 +1,6 @@ -using System; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Net; @@ -13,6 +15,7 @@ namespace Umbraco.Web.Common.Profiler private readonly WebProfiler _profiler; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + private readonly List _terminate = new List(); public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, IUmbracoApplicationLifetime umbracoApplicationLifetime) @@ -44,13 +47,19 @@ namespace Umbraco.Web.Common.Profiler public void Terminate() { + _umbracoApplicationLifetime.ApplicationInit -= InitializeApplication; + foreach (var t in _terminate) t(); } private void InitializeApplication(object sender, EventArgs args) { - _umbracoRequestLifetime.RequestStart += - (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); - _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); + void requestStart(object sender, HttpContext context) => _profiler.UmbracoApplicationBeginRequest(context); + _umbracoRequestLifetime.RequestStart += requestStart; + _terminate.Add(() => _umbracoRequestLifetime.RequestStart -= requestStart); + + void requestEnd(object sender, HttpContext context) => _profiler.UmbracoApplicationEndRequest(context); + _umbracoRequestLifetime.RequestEnd += requestEnd; + _terminate.Add(() => _umbracoRequestLifetime.RequestEnd -= requestEnd); // Stop the profiling of the booting process _profiler.StopBoot(); diff --git a/src/Umbraco.Web/Compose/AuditEventsComponent.cs b/src/Umbraco.Web/Compose/AuditEventsComponent.cs index 51c47233c7..9842ec21a3 100644 --- a/src/Umbraco.Web/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/AuditEventsComponent.cs @@ -47,7 +47,19 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; + + UserService.SavedUser -= OnSavedUser; + UserService.DeletedUser -= OnDeletedUser; + UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; + + MemberService.Saved -= OnSavedMember; + MemberService.Deleted -= OnDeletedMember; + MemberService.AssignedRoles -= OnAssignedRoles; + MemberService.RemovedRoles -= OnRemovedRoles; + MemberService.Exported -= OnMemberExported; + } public static IUser UnknownUser(IGlobalSettings globalSettings) => new User(globalSettings) { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index dcb5fac32d..e4f9679f4c 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -38,7 +38,19 @@ namespace Umbraco.Web.Compose } public void Terminate() - { } + { + //BackOfficeUserManager.AccountLocked -= ; + //BackOfficeUserManager.AccountUnlocked -= ; + BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; + //BackOfficeUserManager.LoginRequiresVerification -= ; + BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; + BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; + BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; + BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; + //BackOfficeUserManager.ResetAccessFailedCount -= ; + } private IUser GetPerformingUser(int userId) { diff --git a/src/Umbraco.Web/Logging/WebProfilerComponent.cs b/src/Umbraco.Web/Logging/WebProfilerComponent.cs index 2959e12ad7..2edeea6a1b 100755 --- a/src/Umbraco.Web/Logging/WebProfilerComponent.cs +++ b/src/Umbraco.Web/Logging/WebProfilerComponent.cs @@ -35,7 +35,9 @@ namespace Umbraco.Web.Logging } public void Terminate() - { } + { + UmbracoApplicationBase.ApplicationInit -= InitializeApplication; + } private void InitializeApplication(object sender, EventArgs args) { From 734b222e1b436acab077b026ea38c336ceb164d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 00:30:06 +1000 Subject: [PATCH 08/14] cleanup --- src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index a4fa47009f..4cb26d5ae5 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -21,11 +21,6 @@ namespace Umbraco.Examine /// public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex, IDisposable { - - void IDisposable.Dispose() - { - base.Dispose(); - } protected ILocalizationService LanguageService { get; } #region Constructors From 1c83c688626a09c86c29c0bdeacdfefd7aa4e602 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 01:30:47 +1000 Subject: [PATCH 09/14] Fixes integration tests localization text --- src/Umbraco.Core/IO/IOHelper.cs | 3 +- .../Implement/LanguageRepository.cs | 2 +- src/Umbraco.Tests.Common/TestHelperBase.cs | 2 +- .../Testing/IntegrationTestComposer.cs | 44 +++++ src/Umbraco.Tests/TestHelpers/TestObjects.cs | 169 ------------------ .../UmbracoCoreServiceCollectionExtensions.cs | 2 +- 6 files changed, 48 insertions(+), 174 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index a8a34e2e93..cb074e67e5 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Reflection; using System.IO; using System.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Strings; @@ -14,7 +13,7 @@ namespace Umbraco.Core.IO { private readonly IHostingEnvironment _hostingEnvironment; - public IOHelper(IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings) + public IOHelper(IHostingEnvironment hostingEnvironment) { _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 245ff10f7f..8ba34c7cdb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override ILanguage PerformGet(int id) { - throw new NotSupportedException(); // not required since policy is full dataset + return PerformGetAll(id).FirstOrDefault(); } protected override IEnumerable PerformGetAll(params int[] ids) diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index cb8b6618ee..8a4ed3f40e 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -82,7 +82,7 @@ namespace Umbraco.Tests.Common get { if (_ioHelper == null) - _ioHelper = new IOHelper(GetHostingEnvironment(), SettingsForTests.GenerateMockGlobalSettings()); + _ioHelper = new IOHelper(GetHostingEnvironment()); return _ioHelper; } } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 7cf114bfc3..e9b38741e2 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -1,10 +1,17 @@ using Moq; +using NUnit.Framework; +using System; +using System.IO; +using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Core.WebAssets; using Umbraco.Examine; using Umbraco.Web.Compose; @@ -38,6 +45,43 @@ namespace Umbraco.Tests.Integration.Testing // ensure all lucene indexes are using RAM directory (no file system) composition.RegisterUnique(); + + // replace this service so that it can lookup the correct file locations + composition.RegisterUnique(GetLocalizedTextService); + } + + /// + /// Used to register a replacement for where the file sources are the ones within the netcore project so + /// we don't need to copy files + /// + /// + private ILocalizedTextService GetLocalizedTextService(IFactory factory) + { + var configs = factory.GetInstance(); + var logger = factory.GetInstance(); + var appCaches = factory.GetInstance(); + + var localizedTextService = new LocalizedTextService( + new Lazy(() => + { + // get the src folder + var currFolder = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while(!currFolder.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)) + { + currFolder = currFolder.Parent; + } + var netcoreUI = currFolder.GetDirectories("Umbraco.Web.UI.NetCore", SearchOption.TopDirectoryOnly).First(); + var mainLangFolder = new DirectoryInfo(Path.Combine(netcoreUI.FullName, configs.Global().UmbracoPath.TrimStart("~/"), "config", "lang")); + + return new LocalizedTextServiceFileSources( + logger, + appCaches, + mainLangFolder); + + }), + logger); + + return localizedTextService; } // replace the default so there is no background index rebuilder diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 7356018c58..c4e1cf4274 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -1,29 +1,17 @@ using System; -using System.IO; -using System.Linq; using Moq; using NPoco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; -using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Core.Strings; using Umbraco.Persistance.SqlCe; -using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.TestHelpers @@ -70,163 +58,6 @@ namespace Umbraco.Tests.TestHelpers return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); } - public void RegisterServices(IRegister register) - { } - - /// - /// Gets a ServiceContext. - /// - /// - /// - /// A cache. - /// A logger. - /// An io helper. - /// - /// - /// An event messages factory. - /// Some url segment providers. - /// An Umbraco Version. - /// A container. - /// A ServiceContext. - /// Should be used sparingly for integration tests only - for unit tests - /// just mock the services to be passed to the ctor of the ServiceContext. - public ServiceContext GetServiceContext(IScopeProvider scopeProvider, IScopeAccessor scopeAccessor, - AppCaches cache, - ILogger logger, - IIOHelper ioHelper, - IGlobalSettings globalSettings, - IContentSettings contentSettings, - IEventMessagesFactory eventMessagesFactory, - UrlSegmentProviderCollection urlSegmentProviders, - IUmbracoVersion umbracoVersion, - IHostingEnvironment hostingEnvironment, - IFactory factory = null) - { - if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); - if (scopeAccessor == null) throw new ArgumentNullException(nameof(scopeAccessor)); - if (cache == null) throw new ArgumentNullException(nameof(cache)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); - - var scheme = Mock.Of(); - - var shortStringHelper = Mock.Of(); - - var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, logger, shortStringHelper); - - var externalLoginService = GetLazyService(factory, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var publicAccessService = GetLazyService(factory, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var domainService = GetLazyService(factory, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var auditService = GetLazyService(factory, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - - var localizedTextService = GetLazyService(factory, c => new LocalizedTextService( - new Lazy(() => - { - var mainLangFolder = new DirectoryInfo(ioHelper.MapPath(Current.Configs.Global().UmbracoPath + "/config/lang/")); - var appPlugins = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.Config + "/lang/")); - - var pluginLangFolders = appPlugins.Exists == false - ? Enumerable.Empty() - : appPlugins.GetDirectories() - .SelectMany(x => x.GetDirectories("Lang")) - .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - - //user defined langs that overwrite the default, these should not be used by plugin creators - var userLangFolders = configLangFolder.Exists == false - ? Enumerable.Empty() - : configLangFolder - .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return new LocalizedTextServiceFileSources( - logger, - cache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - - }), - logger)); - - var runtimeState = Mock.Of(); - var idkMap = new IdKeyMap(scopeProvider); - - var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - - var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); - var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); - var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), ioHelper, localizedTextService.Value, localizationService.Value, TestHelper.ShortStringHelper)); - var propertyValidationService = new Lazy(() => new PropertyValidationService(propertyEditorCollection, dataTypeService.Value, localizedTextService.Value)); - var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), propertyValidationService, TestHelper.ShortStringHelper)); - var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, ioHelper, GetRepo(c), globalSettings, contentSettings, TestHelper.EmailSender)); - var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), TestHelper.GetHostingEnvironment())); - var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberService = GetLazyService(factory, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaService = GetLazyService(factory, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), TestHelper.ShortStringHelper)); - var contentTypeService = GetLazyService(factory, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaTypeService = GetLazyService(factory, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), TestHelper.ShortStringHelper, globalSettings, hostingEnvironment)); - - var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(factory, c => new EntityService(scopeProvider, logger, eventMessagesFactory, idkMap, GetRepo(c))); - - var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - var packagingService = GetLazyService(factory, c => - { - var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value), globalSettings); - return new PackagingService( - auditService.Value, - new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, hostingEnvironment, - new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders, TestHelper.ShortStringHelper, propertyEditorCollection), logger, umbracoVersion, globalSettings, "createdPackages.config"), - new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, hostingEnvironment, - new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders, TestHelper.ShortStringHelper, propertyEditorCollection), logger, umbracoVersion, globalSettings, "installedPackages.config"), - new PackageInstallation( - new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection, scopeProvider, shortStringHelper, GetGlobalSettings(), localizedTextService.Value), - new PackageFileInstallation(compiledPackageXmlParser, ioHelper, new ProfilingLogger(logger, new TestProfiler())), - compiledPackageXmlParser, Mock.Of(), - Mock.Of(x => x.ApplicationPhysicalPath == ioHelper.MapPath("~"))), - ioHelper); - }); - var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var keyValueService = GetLazyService(factory, c => new KeyValueService(scopeProvider, GetRepo(c))); - var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); - - return new ServiceContext( - publicAccessService, - domainService, - auditService, - localizedTextService, - tagService, - contentService, - userService, - memberService, - mediaService, - contentTypeService, - mediaTypeService, - dataTypeService, - fileService, - localizationService, - packagingService, - serverRegistrationService, - entityService, - relationService, - macroService, - memberTypeService, - memberGroupService, - notificationService, - externalLoginService, - redirectUrlService, - consentService, - keyValueService, - contentTypeServiceBaseFactory); - } - private Lazy GetLazyService(IFactory container, Func ctor) where T : class { diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index b5d581afd7..fd24122b3d 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -325,7 +325,7 @@ namespace Umbraco.Extensions var globalSettings = configs.Global(); hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment); - ioHelper = new IOHelper(hostingEnvironment, globalSettings); + ioHelper = new IOHelper(hostingEnvironment); logger = AddLogger(services, hostingEnvironment, loggingConfiguration); backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); From 855a58a3cc048eecb2ddf359a5ca35b5136be705 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 01:40:46 +1000 Subject: [PATCH 10/14] fix tests --- .../Controllers/UsersControllerTests.cs | 13 ++++--------- .../Controllers/UsersController.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs index e2d66373ec..c001eb2d92 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers } [Test] - public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException() + public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_Zero_Users_Message() { var userId = 42; // Must not exist var url = PrepareUrl(x => x.PostUnlockUsers(new []{userId})); @@ -158,18 +158,15 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers var response = await Client.PostAsync(url, new StringContent(string.Empty)); var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); - Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings + var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings { ContractResolver = new IgnoreRequiredAttributesResolver() }); Assert.Multiple(() => { - var expected = new InvalidOperationException(); - Assert.IsNotNull(actual); - Assert.AreEqual(expected.GetType(), actual.ExceptionType); - Assert.AreEqual(expected.Message, actual.ExceptionMessage); + Assert.AreEqual($"Unlocked 0 users", actual.Message); }); } @@ -230,8 +227,6 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers userService.Save(user); } - - var url = PrepareUrl(x => x.PostUnlockUsers(users.Select(x=>x.Id).ToArray())); // Act diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 75f7a6e512..1359244055 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -739,11 +739,16 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task PostUnlockUsers([FromQuery]int[] userIds) { if (userIds.Length <= 0) return Ok(); + var notFound = new List(); foreach (var u in userIds) { var user = await _backOfficeUserManager.FindByIdAsync(u.ToString()); - if (user == null) throw new InvalidOperationException(); + if (user == null) + { + notFound.Add(u); + continue; + } var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); if (unlockResult.Succeeded == false) @@ -760,7 +765,7 @@ namespace Umbraco.Web.BackOffice.Controllers } return new UmbracoNotificationSuccessResponse( - _localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {userIds.Length.ToString()})); + _localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {(userIds.Length - notFound.Count).ToString()})); } [AdminUsersAuthorize("userIds")] From 123e3cb22b5776bdfb8e3ca9b4b5782f75387f25 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Sep 2020 10:12:55 +1000 Subject: [PATCH 11/14] fix build --- src/Umbraco.Web/UmbracoApplicationBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 1c447b38aa..55ef6747a0 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data\\Logs"), Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "config\\serilog.config"), Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "config\\serilog.user.config")); - var ioHelper = new IOHelper(hostingEnvironment, globalSettings); + var ioHelper = new IOHelper(hostingEnvironment); var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration); var configs = configFactory.Create(); From ed2fa27b18336655b94b076c3a9e75ff71703bfb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 4 Sep 2020 09:26:24 +0200 Subject: [PATCH 12/14] Fix issue with login url Signed-off-by: Bjarke Berg --- src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs index e3f335150e..f2babdb07c 100644 --- a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs @@ -56,7 +56,7 @@ namespace Umbraco.Extensions { return linkGenerator.GetPathByAction(nameof(InstallApiController.GetSetup), ControllerExtensions.GetControllerName(), - new { area = Constants.Web.Mvc.InstallArea }); + new { area = Constants.Web.Mvc.InstallArea }).TrimEnd(nameof(InstallApiController.GetSetup)); } /// From d69c93e4eea871ac5029462423c86525e46e7be7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 4 Sep 2020 09:32:35 +0200 Subject: [PATCH 13/14] https://github.com/umbraco/Umbraco-CMS/issues/8814 - Decoreated endpoints with DetermineAmbiguousActionByPassingParametersAttribute Signed-off-by: Bjarke Berg --- src/Umbraco.Web.BackOffice/Controllers/EntityController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 433cf6f345..1afe9bccf4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -248,6 +248,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// UDI of the entity to fetch URL for /// The culture to fetch the URL for /// The URL or path to the item + [DetermineAmbiguousActionByPassingParameters] public HttpResponseMessage GetUrl(Udi udi, string culture = "*") { var intId = _entityService.GetId(udi); @@ -281,6 +282,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// We are not restricting this with security because there is no sensitive data /// + [DetermineAmbiguousActionByPassingParameters] public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null) { culture = culture ?? ClientCulture(); From a58859ade636d784d9a6a8bff394fe21676a88dc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 4 Sep 2020 09:39:46 +0200 Subject: [PATCH 14/14] updated version to 0.5.0-alpha002 Signed-off-by: Bjarke Berg --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 59c75192b9..26e46eab0a 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("0.5.0")] -[assembly: AssemblyInformationalVersion("0.5.0-alpha001")] +[assembly: AssemblyInformationalVersion("0.5.0-alpha002")]