diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index 38da246cc1..565b2563e6 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -46,12 +46,6 @@ namespace Umbraco.Core /// This is eg "http://www.example.com". Uri ApplicationUrl { get; } - /// - /// Gets the Umbraco application virtual path. - /// - /// This is either "/" or eg "/virtual". - string ApplicationVirtualPath { get; } - /// /// Gets the runtime level of execution. /// diff --git a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs index eb74d37590..02e5a6f5ee 100644 --- a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs @@ -1,6 +1,7 @@ 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.Persistence; @@ -15,6 +16,9 @@ namespace Umbraco.Core /// /// Registers essential services. /// + /// + /// These services are all either created by the runtime or used to construct the runtime + /// public static void RegisterEssentials(this Composition composition, ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, IMainDom mainDom, @@ -25,8 +29,10 @@ namespace Umbraco.Core ITypeFinder typeFinder, IIOHelper ioHelper, IUmbracoVersion umbracoVersion, - IDbProviderFactoryCreator dbProviderFactoryCreator) - { + IDbProviderFactoryCreator dbProviderFactoryCreator, + IHostingEnvironment hostingEnvironment, + IBackOfficeInfo backOfficeInfo) + { composition.RegisterUnique(logger); composition.RegisterUnique(profiler); composition.RegisterUnique(profilingLogger); @@ -42,6 +48,8 @@ namespace Umbraco.Core composition.RegisterUnique(umbracoVersion); composition.RegisterUnique(dbProviderFactoryCreator); composition.RegisterUnique(factory => factory.GetInstance().BulkSqlInsertProvider); + composition.RegisterUnique(hostingEnvironment); + composition.RegisterUnique(backOfficeInfo); } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 4a9441e535..5cd51a174a 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Runtime Configs.Global(), new Lazy(() => mainDom), new Lazy(() => _factory.GetInstance()), - UmbracoVersion,HostingEnvironment, BackOfficeInfo) + UmbracoVersion, HostingEnvironment, BackOfficeInfo) { Level = RuntimeLevel.Boot }; @@ -82,7 +82,6 @@ namespace Umbraco.Core.Runtime protected IBackOfficeInfo BackOfficeInfo { get; } public IDbProviderFactoryCreator DbProviderFactoryCreator { get; } - //public IBulkSqlInsertProvider BulkSqlInsertProvider { get; } /// /// Gets the profiler. @@ -176,21 +175,9 @@ namespace Umbraco.Core.Runtime // type finder/loader var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); - // 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(Logger, - Configs.Global(), - new Lazy(() => _factory.GetInstance()), - new Lazy(() => _factory.GetInstance()), - UmbracoVersion, HostingEnvironment, BackOfficeInfo) - { - Level = RuntimeLevel.Boot - }; - // 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); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index eadb9fed21..420fba7ffc 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -43,8 +43,6 @@ namespace Umbraco.Core _umbracoVersion = umbracoVersion; _hostingEnvironment = hostingEnvironment; _backOfficeInfo = backOfficeInfo; - - ApplicationVirtualPath = _hostingEnvironment.ApplicationVirtualPath; } /// @@ -84,9 +82,6 @@ namespace Umbraco.Core /// public Uri ApplicationUrl { get; private set; } - /// - public string ApplicationVirtualPath { get; } - /// public string CurrentMigrationState { get; internal set; } diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs index d190d1165d..f440944ab7 100644 --- a/src/Umbraco.Tests.Integration/ContainerTests.cs +++ b/src/Umbraco.Tests.Integration/ContainerTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Integration var testHelper = new TestHelper(); var runtimeState = Mock.Of(); var umbracoDatabaseFactory = Mock.Of(); - var dbProviderFactoryCreator = Mock.Of(); + var dbProviderFactoryCreator = Mock.Of(); var typeLoader = testHelper.GetMockedTypeLoader(); // Register in the container @@ -45,7 +45,8 @@ namespace Umbraco.Tests.Integration testHelper.Logger, runtimeState, testHelper.GetConfigs(), testHelper.IOHelper, testHelper.AppCaches); composition.RegisterEssentials(testHelper.Logger, testHelper.Profiler, testHelper.Logger, testHelper.MainDom, testHelper.AppCaches, umbracoDatabaseFactory, typeLoader, runtimeState, testHelper.GetTypeFinder(), - testHelper.IOHelper, testHelper.GetUmbracoVersion(), dbProviderFactoryCreator); + testHelper.IOHelper, testHelper.GetUmbracoVersion(), dbProviderFactoryCreator, + testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo()); // Cross wire - this would be called by the Host Builder at the very end of ConfigureServices var lightInjectServiceProvider = serviceProviderFactory.CreateServiceProvider(umbracoContainer.Container); diff --git a/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs b/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs deleted file mode 100644 index 9df7270350..0000000000 --- a/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Umbraco.Tests.Integration -{ - /// - /// Executes arbitrary code on start/end - /// - public class DelegateHostedService : IHostedService - { - private readonly Action _start; - private readonly Action _end; - - public static DelegateHostedService Create(Action start, Action end) => new DelegateHostedService(start, end); - - private DelegateHostedService(Action start, Action end) - { - _start = start; - _end = end; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _start(); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _end?.Invoke(); - return Task.CompletedTask; - } - } - - -} diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index fb7f6b427e..dae4d0cbb8 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -9,6 +9,7 @@ using Moq; using NUnit.Framework; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Composing.LightInject; @@ -28,6 +29,13 @@ namespace Umbraco.Tests.Integration [TestFixture] public class RuntimeTests { + [TearDown] + public void TearDown() + { + MyComponent.Reset(); + MyComposer.Reset(); + } + [OneTimeTearDown] public void FixtureTearDown() { @@ -73,12 +81,11 @@ namespace Umbraco.Tests.Integration /// Calling AddUmbracoCore to configure the container and boot the core runtime within a generic host /// [Test] - public void AddUmbracoCore() + public async Task AddUmbracoCore() { var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); - var host = Host.CreateDefaultBuilder() - .UseTestLifetime() + var hostBuilder = new HostBuilder() .UseUmbraco(serviceProviderFactory) .ConfigureServices((hostContext, services) => { @@ -89,38 +96,61 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(); services.AddUmbracoCore(umbracoContainer, GetType().Assembly); + }); - // Run tests - services.AddHostedService(x => DelegateHostedService.Create(() => - { - // assert results - var runtimeState = umbracoContainer.GetInstance(); - var mainDom = umbracoContainer.GetInstance(); + var host = await hostBuilder.StartAsync(); - Assert.IsTrue(mainDom.IsMainDom); - Assert.IsNull(runtimeState.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - Assert.IsTrue(MyComposer.IsComposed); + // assert results + var runtimeState = umbracoContainer.GetInstance(); + var mainDom = umbracoContainer.GetInstance(); - }, null)); - }) - .Build(); + Assert.IsTrue(mainDom.IsMainDom); + Assert.IsNull(runtimeState.BootFailedException); + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + Assert.IsTrue(MyComponent.IsInit); + Assert.IsFalse(MyComponent.IsTerminated); - // NOTE: host.Run() could be used but that requires more work by manually calling IHostApplicationLifetime.StopApplication(). In these cases we don't need to wind down. - host.Start(); + await host.StopAsync(); + + Assert.IsTrue(MyComponent.IsTerminated); } + [Ignore("This test just shows that resolving services from the container before the host is done resolves 2 different instances")] [Test] - public void UseUmbracoCore() + public async Task BuildServiceProvider() + { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + + IHostApplicationLifetime lifetime1 = null; + + var hostBuilder = new HostBuilder() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + lifetime1 = services.BuildServiceProvider().GetRequiredService(); + }); + + var host = await hostBuilder.StartAsync(); + + var lifetime2 = host.Services.GetRequiredService(); + + lifetime1.StopApplication(); + Assert.IsTrue(lifetime1.ApplicationStopping.IsCancellationRequested); + Assert.AreEqual(lifetime1.ApplicationStopping.IsCancellationRequested, lifetime2.ApplicationStopping.IsCancellationRequested); + + } + + [Test] + public async Task UseUmbracoCore() { var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); - var host = Host.CreateDefaultBuilder() + var hostBuilder = new HostBuilder() //TODO: Need to have a configured umb version for the runtime state .UseLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb")) - .UseTestLifetime() + //.UseTestLifetime() .UseUmbraco(serviceProviderFactory) .ConfigureServices((hostContext, services) => { @@ -129,33 +159,26 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(); services.AddUmbracoCore(umbracoContainer, GetType().Assembly); + }); - // Run tests - services.AddHostedService(x => DelegateHostedService.Create(() => - { - var runtimeState = (RuntimeState)umbracoContainer.GetInstance(); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + var host = await hostBuilder.StartAsync(); - var dbBuilder = umbracoContainer.GetInstance(); - Assert.IsNotNull(dbBuilder); + var runtimeState = (RuntimeState)umbracoContainer.GetInstance(); + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - var canConnect = dbBuilder.CanConnectToDatabase; - Assert.IsTrue(canConnect); + var dbBuilder = umbracoContainer.GetInstance(); + Assert.IsNotNull(dbBuilder); - var dbResult = dbBuilder.CreateSchemaAndData(); - Assert.IsTrue(dbResult.Success); + var canConnect = dbBuilder.CanConnectToDatabase; + Assert.IsTrue(canConnect); - var dbFactory = umbracoContainer.GetInstance(); - var profilingLogger = umbracoContainer.GetInstance(); - runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + var dbResult = dbBuilder.CreateSchemaAndData(); + Assert.IsTrue(dbResult.Success); - }, null)); - }) - .Build(); - - // NOTE: host.Run() could be used but that requires more work by manually calling IHostApplicationLifetime.StopApplication(). In these cases we don't need to wind down. - host.Start(); + var dbFactory = umbracoContainer.GetInstance(); + var profilingLogger = umbracoContainer.GetInstance(); + runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); } private LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) @@ -181,6 +204,11 @@ namespace Umbraco.Tests.Integration IsComposed = true; } + public static void Reset() + { + IsComposed = false; + } + public static bool IsComposed { get; private set; } } @@ -205,6 +233,12 @@ namespace Umbraco.Tests.Integration { IsTerminated = true; } + + public static void Reset() + { + IsTerminated = false; + IsInit = false; + } } } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 0cb3f3e2e1..99a5352a17 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -80,7 +80,7 @@ namespace Umbraco.Tests.Runtimes // create the register and the composition var register = TestHelper.GetRegister(); var composition = new Composition(register, typeLoader, profilingLogger, runtimeState, configs, ioHelper, appCaches); - composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator); + 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); @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Runtimes var register = TestHelper.GetRegister(); var composition = new Composition(register, typeLoader, profilingLogger, runtimeState, configs, ioHelper, appCaches); var umbracoVersion = TestHelper.GetUmbracoVersion(); - composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator); + 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); diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs index 1f06a818d6..a79838bd3e 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs @@ -1,5 +1,7 @@ using System; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Umbraco.Web.BackOffice.AspNetCore { @@ -7,12 +9,10 @@ namespace Umbraco.Web.BackOffice.AspNetCore { public static IApplicationBuilder UseUmbracoBackOffice(this IApplicationBuilder app) { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } + if (app == null) throw new ArgumentNullException(nameof(app)); return app; } + } } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs similarity index 82% rename from src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs rename to src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs index 44fb9bc14f..6c3cecad28 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs @@ -6,14 +6,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Umbraco.Composing; using Umbraco.Configuration; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; @@ -24,9 +21,13 @@ namespace Umbraco.Web.BackOffice.AspNetCore { - public static class UmbracoBackOfficeServiceCollectionExtensions + public static class UmbracoCoreServiceCollectionExtensions { - + /// + /// Adds the Umbraco Configuration requirements + /// + /// + /// public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services) { var serviceProvider = services.BuildServiceProvider(); @@ -45,13 +46,10 @@ namespace Umbraco.Web.BackOffice.AspNetCore /// - /// Adds the Umbraco Back Core requirements + /// Adds the Umbraco Back Core requirements /// /// /// - /// - /// Must be called after all services are added to the application because we are cross-wiring the container (currently) - /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services) { if (!UmbracoServiceProviderFactory.IsActive) @@ -62,6 +60,13 @@ namespace Umbraco.Web.BackOffice.AspNetCore return services.AddUmbracoCore(umbContainer, Assembly.GetEntryAssembly()); } + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IRegister umbContainer, Assembly entryAssembly) { if (services is null) throw new ArgumentNullException(nameof(services)); @@ -90,6 +95,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore backOfficeInfo, typeFinder); + hostingEnvironment.RegisterObject(new CoreRuntimeShutdown(coreRuntime)); + var factory = coreRuntime.Boot(umbContainer); return services; @@ -124,8 +131,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore out ILogger logger, out Configs configs, out IIOHelper ioHelper, out Core.Hosting.IHostingEnvironment hostingEnvironment, out IBackOfficeInfo backOfficeInfo, out IProfiler profiler) { - // TODO: This isn't the best to have to resolve the services now but to avoid this will - // require quite a lot of re-work. + // TODO: Resolving services before the Host is done configuring this way means that the services resolved + // are not going to be the same instances that are going to be used within the application! var serviceProvider = services.BuildServiceProvider(); var httpContextAccessor = serviceProvider.GetRequiredService(); @@ -162,5 +169,29 @@ namespace Umbraco.Web.BackOffice.AspNetCore // nothing to check } } + + /// + /// Ensures the runtime is shutdown when the application is shutting down + /// + private class CoreRuntimeShutdown : IRegisteredObject + { + public CoreRuntimeShutdown(IRuntime runtime) + { + _runtime = runtime; + } + + private bool _completed = false; + private readonly IRuntime _runtime; + + public void Stop(bool immediate) + { + if (!_completed) + { + _completed = true; + _runtime.Terminate(); + } + + } + } } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index b81cafdb40..81aac30d2c 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -72,8 +72,6 @@ namespace Umbraco.Web.Runtime composition.Register(Lifetime.Singleton); - composition.Register(); - composition.Register(); composition.Register(Lifetime.Singleton); composition.Register(); composition.Register(Lifetime.Singleton); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e8aaecdf4d..5eb60cb629 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -585,4 +585,4 @@ - + \ No newline at end of file