diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index a1e06bc1f8..73db382812 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -1,3 +1,5 @@ +using System; +using System.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -16,7 +18,16 @@ namespace Umbraco.Core public static ILogger CreateLogger() { - return _loggerFactory?.CreateLogger() ?? new NullLogger(); + try + { + return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + } + catch (ObjectDisposedException) + { + // TODO: Weird, investigate, ScopedRepositoryTests.FullDataSetRepositoryCachePolicy etc + Debugger.Break(); + return NullLoggerFactory.Instance.CreateLogger(); + } } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index a0c1718c07..67fd358a8a 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -1,7 +1,9 @@ using System; +using Microsoft.Extensions.Logging; using Umbraco.Core.Composing; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; namespace Umbraco.Core.Runtime { @@ -9,31 +11,39 @@ namespace Umbraco.Core.Runtime { public IRuntimeState State { get; } + private readonly ILogger _logger; private readonly ComponentCollection _components; private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; private readonly IApplicationShutdownRegistry _applicationShutdownRegistry; private readonly IProfilingLogger _profilingLogger; private readonly IMainDom _mainDom; + private readonly IUmbracoDatabaseFactory _databaseFactory; public CoreRuntime( + ILogger logger, IRuntimeState state, ComponentCollection components, IUmbracoBootPermissionChecker umbracoBootPermissionChecker, IApplicationShutdownRegistry applicationShutdownRegistry, IProfilingLogger profilingLogger, - IMainDom mainDom) + IMainDom mainDom, + IUmbracoDatabaseFactory databaseFactory) { State = state; + _logger = logger; _components = components; _umbracoBootPermissionChecker = umbracoBootPermissionChecker; _applicationShutdownRegistry = applicationShutdownRegistry; _profilingLogger = profilingLogger; _mainDom = mainDom; + _databaseFactory = databaseFactory; } public void Start() { + DetermineRuntimeLevel(); + if (State.Level <= RuntimeLevel.BootFailed) throw new InvalidOperationException($"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}"); @@ -71,5 +81,32 @@ namespace Umbraco.Core.Runtime } } } + + private void DetermineRuntimeLevel() + { + using var timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined."); + + try + { + State.DetermineRuntimeLevel(); + + _logger.LogDebug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", State.Level, State.Reason); + + if (State.Level == RuntimeLevel.Upgrade) + { + _logger.LogDebug("Configure database factory for upgrades."); + _databaseFactory.ConfigureForUpgrade(); + } + } + catch + { + + // BOO a cast, yay no CoreRuntimeBootstrapper + ((RuntimeState)State).Level = RuntimeLevel.BootFailed; + ((RuntimeState)State).Reason = RuntimeLevelReason.BootFailedOnException; + timer?.Fail(); + throw; + } + } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntimeBootstrapper.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntimeBootstrapper.cs deleted file mode 100644 index 27277b5229..0000000000 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntimeBootstrapper.cs +++ /dev/null @@ -1,280 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core.Builder; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Runtime -{ - /// - /// Bootstraps the Core Umbraco runtime. - /// - /// Does not handle any of the web-related aspects of Umbraco (startup, etc). It - /// should be possible to use this runtime in console apps. - public class CoreRuntimeBootstrapper - { - // 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 GlobalSettings _globalSettings; - private readonly ConnectionStrings _connectionStrings; - - public CoreRuntimeBootstrapper( - GlobalSettings globalSettings, - ConnectionStrings connectionStrings, - IUmbracoVersion umbracoVersion, - IIOHelper ioHelper, - IProfiler profiler, - IUmbracoBootPermissionChecker umbracoBootPermissionChecker, - IHostingEnvironment hostingEnvironment, - IBackOfficeInfo backOfficeInfo, - IDbProviderFactoryCreator dbProviderFactoryCreator, - IMainDom mainDom, - ITypeFinder typeFinder, - AppCaches appCaches) - { - _globalSettings = globalSettings; - _connectionStrings = connectionStrings; - - IOHelper = ioHelper; - AppCaches = appCaches; - UmbracoVersion = umbracoVersion; - Profiler = profiler; - HostingEnvironment = hostingEnvironment; - BackOfficeInfo = backOfficeInfo; - DbProviderFactoryCreator = dbProviderFactoryCreator; - - _umbracoBootPermissionChecker = umbracoBootPermissionChecker; - MainDom = mainDom; - TypeFinder = typeFinder; - } - - - protected IBackOfficeInfo BackOfficeInfo { get; } - - public IDbProviderFactoryCreator DbProviderFactoryCreator { get; } - - /// - /// Gets the profiler. - /// - protected IProfiler Profiler { get; } - - /// - /// Gets the - /// - protected ITypeFinder TypeFinder { get; } - - /// - /// Gets the - /// - protected IIOHelper IOHelper { get; } - - protected IHostingEnvironment HostingEnvironment { get; } - public AppCaches AppCaches { get; } - public IUmbracoVersion UmbracoVersion { get; } - - /// - public IRuntimeState State => _state; - - public IMainDom MainDom { get; } - - /// - public virtual void Configure(IUmbracoBuilder builder) - { - if (builder is null) throw new ArgumentNullException(nameof(builder)); - - - // create and register the essential services - // ie the bare minimum required to boot - - // the boot loader boots using a container scope, so anything that is PerScope will - // be disposed after the boot loader has booted, and anything else will remain. - // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else - // the container will fail to create a scope since there is no http context when - // the application starts. - // the boot loader is kept in the runtime for as long as Umbraco runs, and components - // are NOT disposed - which is not a big deal as long as they remain lightweight - // objects. - - var umbracoVersion = new UmbracoVersion(); - var logger = builder.BuilderLoggerFactory.CreateLogger(); - var profilingLogger = new ProfilingLogger(logger, Profiler); - - using (var timer = profilingLogger.TraceDuration( - $"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}.", - "Booted.", - "Boot failed.")) - { - - logger.LogInformation("Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'.", - HostingEnvironment?.SiteName, - HostingEnvironment?.ApplicationId, - HostingEnvironment?.ApplicationPhysicalPath, - NetworkHelper.MachineName); - logger.LogDebug("Runtime: {Runtime}", GetType().FullName); - - AppDomain.CurrentDomain.SetData("DataDirectory", HostingEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data)); - - // application environment - ConfigureUnhandledException(logger); - try - { - // run handlers - OnRuntimeBoot(profilingLogger); - - // TODO: Don't do this, UmbracoBuilder ctor should handle it... - builder.TypeLoader = new TypeLoader(TypeFinder, AppCaches.RuntimeCache, - new DirectoryInfo(HostingEnvironment.LocalTempPath), - builder.BuilderLoggerFactory.CreateLogger(), profilingLogger); - - builder.Services.AddUnique(logger); - builder.Services.AddUnique(builder.BuilderLoggerFactory); - builder.Services.AddUnique(_umbracoBootPermissionChecker); - builder.Services.AddUnique(Profiler); - builder.Services.AddUnique(profilingLogger); - builder.Services.AddUnique(MainDom); - builder.Services.AddUnique(AppCaches); - builder.Services.AddUnique(AppCaches.RequestCache); - builder.Services.AddUnique(builder.TypeLoader); - builder.Services.AddUnique(TypeFinder); - builder.Services.AddUnique(IOHelper); - builder.Services.AddUnique(UmbracoVersion); - builder.Services.AddUnique(DbProviderFactoryCreator); - builder.Services.AddUnique(HostingEnvironment); - builder.Services.AddUnique(BackOfficeInfo); - builder.Services.AddUnique(); - - // NOTE: This instance of IUmbracoDatabaseFactory is only used to determine runtime state. - var bootstrapDatabaseFactory = CreateBootstrapDatabaseFactory(builder.BuilderLoggerFactory); - - // after bootstrapping we let the container wire up for us. - builder.Services.AddUnique(); - builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); - builder.Services.AddUnique(factory => factory.GetRequiredService().BulkSqlInsertProvider); - - // re-create the state object with the essential services - _state = new RuntimeState(_globalSettings, UmbracoVersion, bootstrapDatabaseFactory, builder.BuilderLoggerFactory.CreateLogger()); - builder.Services.AddUnique(_state); - - // run handlers - OnRuntimeEssentials(builder, AppCaches, builder.TypeLoader, bootstrapDatabaseFactory); - - // determine our runtime level - DetermineRuntimeLevel(bootstrapDatabaseFactory, logger, profilingLogger); - } - catch (Exception e) - { - var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); - - if (_state != null) - { - _state.Level = RuntimeLevel.BootFailed; - _state.BootFailedException = bfe; - } - - timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves - - Debugger.Break(); - } - } - } - - - protected virtual void ConfigureUnhandledException(ILogger logger) - { - //take care of unhandled exceptions - there is nothing we can do to - // prevent the launch process to go down but at least we can try - // and log the exception - AppDomain.CurrentDomain.UnhandledException += (_, args) => - { - var exception = (Exception)args.ExceptionObject; - var isTerminating = args.IsTerminating; // always true? - - var msg = "Unhandled exception in AppDomain"; - if (isTerminating) msg += " (terminating)"; - msg += "."; - logger.LogError(exception, msg); - }; - } - - - - private void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger, IProfilingLogger profilingLogger) - { - using var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined."); - - try - { - _state.DetermineRuntimeLevel(); - - logger.LogDebug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); - - if (_state.Level == RuntimeLevel.Upgrade) - { - logger.LogDebug("Configure database factory for upgrades."); - databaseFactory.ConfigureForUpgrade(); - } - } - catch - { - _state.Level = RuntimeLevel.BootFailed; - _state.Reason = RuntimeLevelReason.BootFailedOnException; - timer?.Fail(); - throw; - } - } - - #region Getters - - // getters can be implemented by runtimes inheriting from CoreRuntime - - /// - /// Creates the database factory. - /// - /// This is strictly internal, for tests only. - protected internal virtual IUmbracoDatabaseFactory CreateBootstrapDatabaseFactory(ILoggerFactory runtimeLoggerFactory) - => new UmbracoDatabaseFactory( - runtimeLoggerFactory.CreateLogger(), - runtimeLoggerFactory, - Options.Create(_globalSettings), - Options.Create(_connectionStrings), - new Lazy(() => new MapperCollection(Enumerable.Empty())), - DbProviderFactoryCreator); - - - #endregion - - #region Events - - protected void OnRuntimeBoot(IProfilingLogger profilingLogger) - { - RuntimeBooting?.Invoke(this, profilingLogger); - } - - protected void OnRuntimeEssentials(IUmbracoBuilder builder, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) - { - RuntimeEssentials?.Invoke(this, new RuntimeEssentialsEventArgs(builder, databaseFactory)); - } - - public event TypedEventHandler RuntimeBooting; - public event TypedEventHandler RuntimeEssentials; - - #endregion - - } -} diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index cb2358d083..ed7ff9adb8 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -2,6 +2,7 @@ using System.Threading; using Semver; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Exceptions; @@ -32,9 +33,9 @@ namespace Umbraco.Core /// /// Initializes a new instance of the class. /// - public RuntimeState(GlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger) + public RuntimeState(IOptions globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - _globalSettings = globalSettings; + _globalSettings = globalSettings.Value; _umbracoVersion = umbracoVersion; _databaseFactory = databaseFactory; _logger = logger; diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index b9368da89b..90937eda01 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -92,6 +92,9 @@ <_Parameter1>DynamicProxyGenAssembly2 + + <_Parameter1>Umbraco.Web.Common + diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 10621862f0..c30adc7e72 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -44,81 +44,6 @@ namespace Umbraco.Tests.Integration MyComposer.Reset(); } - /// - /// Manually configure the containers/dependencies and call Boot on Core runtime - /// - [Test] - public void Boot_Core_Runtime() - { - var services = new ServiceCollection().AddLazySupport(); - - // Special case since we are not using the Generic Host, we need to manually add an AspNetCore service to the container - services.AddTransient(x => Mock.Of()); - - var testHelper = new TestHelper(); - - var globalSettings = new GlobalSettings(); - var connectionStrings = new ConnectionStrings(); - - // TODO: found these registration were necessary here (as we haven't called the HostBuilder?), as dependencies for ComponentCollection - // are not resolved. Need to check this if these explicit registrations are the best way to handle this. - - services.AddTransient(x => Options.Create(globalSettings)); - services.AddTransient(x => Options.Create(connectionStrings)); - services.AddTransient(x => Options.Create(new ContentSettings())); - services.AddTransient(x => Options.Create(new CoreDebugSettings())); - services.AddTransient(x => Options.Create(new NuCacheSettings())); - services.AddTransient(x => Options.Create(new RequestHandlerSettings())); - services.AddTransient(x => Options.Create(new UserPasswordConfigurationSettings())); - services.AddTransient(x => Options.Create(new WebRoutingSettings())); - services.AddTransient(x => Options.Create(new ModelsBuilderSettings())); - services.AddTransient(x => Options.Create(new RouteOptions())); - services.AddTransient(x => Options.Create(new IndexCreatorSettings())); - services.AddRouting(); // LinkGenerator - services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); - - // Create the core runtime - var bootstrapper = new CoreRuntimeBootstrapper( - globalSettings, - connectionStrings, - testHelper.GetUmbracoVersion(), - testHelper.IOHelper, - testHelper.Profiler, - testHelper.UmbracoBootPermissionChecker, - testHelper.GetHostingEnvironment(), - testHelper.GetBackOfficeInfo(), - testHelper.DbProviderFactoryCreator, - testHelper.MainDom, - testHelper.GetTypeFinder(), - AppCaches.NoCache - ); - - var builder = new UmbracoBuilder(services, Mock.Of(), testHelper.ConsoleLoggerFactory); - bootstrapper.Configure(builder); - builder.AddComposers(); - builder.Build(); - - Assert.IsTrue(bootstrapper.MainDom.IsMainDom); - Assert.IsNull(bootstrapper.State.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, bootstrapper.State.Level); - Assert.IsTrue(MyComposer.IsComposed); - Assert.IsFalse(MyComponent.IsInit); - Assert.IsFalse(MyComponent.IsTerminated); - - var container = services.BuildServiceProvider(); - - var runtime = container.GetRequiredService(); - - runtime.Start(); - - Assert.IsTrue(MyComponent.IsInit); - Assert.IsFalse(MyComponent.IsTerminated); - - runtime.Terminate(); - - Assert.IsTrue(MyComponent.IsTerminated); - } - /// /// Calling AddUmbracoCore to configure the container /// @@ -139,7 +64,7 @@ namespace Umbraco.Tests.Integration var builder = new UmbracoBuilder(services, hostContext.Configuration, testHelper.ConsoleLoggerFactory); builder.AddConfiguration(); - builder.AddUmbracoCore(webHostEnvironment, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), hostContext.Configuration, UmbracoCoreServiceCollectionExtensions.GetCoreRuntime); + builder.AddUmbracoCore(webHostEnvironment, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), hostContext.Configuration, UmbracoCoreServiceCollectionExtensions.ConfigureSomeMorebits); }); var host = await hostBuilder.StartAsync(); @@ -151,7 +76,6 @@ namespace Umbraco.Tests.Integration Assert.IsFalse(mainDom.IsMainDom); // We haven't "Started" the runtime yet Assert.IsNull(runtimeState.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); Assert.IsFalse(MyComponent.IsInit); // We haven't "Started" the runtime yet await host.StopAsync(); @@ -180,7 +104,7 @@ namespace Umbraco.Tests.Integration var builder = new UmbracoBuilder(services, hostContext.Configuration, testHelper.ConsoleLoggerFactory); builder.AddConfiguration() - .AddUmbracoCore(webHostEnvironment, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), hostContext.Configuration, UmbracoCoreServiceCollectionExtensions.GetCoreRuntime) + .AddUmbracoCore(webHostEnvironment, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), hostContext.Configuration, UmbracoCoreServiceCollectionExtensions.ConfigureSomeMorebits) .Build(); services.AddRouting(); // LinkGenerator @@ -198,7 +122,6 @@ namespace Umbraco.Tests.Integration Assert.IsTrue(mainDom.IsMainDom); Assert.IsNull(runtimeState.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); Assert.IsTrue(MyComponent.IsInit); await host.StopAsync(); diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs index fb5b494704..30f44443a1 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -16,9 +16,9 @@ namespace Umbraco.Tests.Integration.TestServerTest /// /// /// - public static IUmbracoBuilder AddTestCore(this IUmbracoBuilder builder, TestHelper testHelper, - Action dbInstallEventHandler) + public static IUmbracoBuilder AddTestCore(this IUmbracoBuilder builder, TestHelper testHelper) { + return builder.AddUmbracoCore( testHelper.GetWebHostEnvironment(), typeof(UmbracoBuilderExtensions).Assembly, @@ -26,9 +26,10 @@ namespace Umbraco.Tests.Integration.TestServerTest testHelper.GetLoggingConfiguration(), builder.Config, // TODO: Yep that's extremely ugly - (globalSettings, connectionStrings, umbVersion, ioHelper, loggerFactory, profiler, hostingEnv, backOfficeInfo, typeFinder, appCaches, dbProviderFactoryCreator) => + (lambdaBuilder, globalSettings, connectionStrings, umbVersion, ioHelper, loggerFactory, profiler, hostingEnv, backOfficeInfo, typeFinder, appCaches, dbProviderFactoryCreator) => { - var runtime = UmbracoIntegrationTest.CreateTestRuntime( + UmbracoIntegrationTest.ConfigureSomeMorebitsForTests( + lambdaBuilder, globalSettings, connectionStrings, umbVersion, @@ -40,10 +41,8 @@ namespace Umbraco.Tests.Integration.TestServerTest typeFinder, appCaches, dbProviderFactoryCreator, - testHelper.MainDom, // SimpleMainDom - dbInstallEventHandler); // DB Installation event handler - - return runtime; + testHelper.MainDom); // SimpleMainDom + }); } } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b2e1b858d6..79ab21bbb1 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -21,6 +21,8 @@ using Umbraco.Web; using Umbraco.Web.Common.Builder; using Umbraco.Web.Common.Controllers; using Microsoft.Extensions.Hosting; +using Umbraco.Core.Persistence; +using Umbraco.Core.Runtime; using Umbraco.Web.BackOffice.Controllers; namespace Umbraco.Tests.Integration.TestServerTest @@ -70,6 +72,7 @@ namespace Umbraco.Tests.Integration.TestServerTest // call startup builder.Configure(app => { + UseTestLocalDb(app.ApplicationServices); Services = app.ApplicationServices; Configure(app); }); @@ -129,7 +132,7 @@ namespace Umbraco.Tests.Integration.TestServerTest var umbracoBuilder = services.AddUmbraco(TestHelper.GetWebHostEnvironment(), Configuration); umbracoBuilder .AddConfiguration() - .AddTestCore(TestHelper, UseTestLocalDb) // This is the important one! + .AddTestCore(TestHelper) // This is the important one! .AddWebComponents() .AddRuntimeMinifier() .AddBackOffice() diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 7c5b6d4032..8747fde7b7 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -26,12 +26,14 @@ using System.Collections.Generic; using Microsoft.Extensions.Configuration; using System.Data.SqlClient; using System.Data.Common; +using System.Diagnostics; using System.IO; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Serilog; +using Umbraco.Core.Builder; using Umbraco.Web.Common.Builder; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; @@ -110,25 +112,28 @@ namespace Umbraco.Tests.Integration.Testing private ILoggerFactory CreateLoggerFactory() { - ILoggerFactory factory; - var testOptions = TestOptionAttributeBase.GetTestOptions(); - switch (testOptions.Logger) + try { - case UmbracoTestOptions.Logger.Mock: - factory = NullLoggerFactory.Instance; - break; - case UmbracoTestOptions.Logger.Serilog: - factory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddSerilog(); }); - break; - case UmbracoTestOptions.Logger.Console: - factory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddConsole(); }); - break; - default: - throw new NotSupportedException($"Logger option {testOptions.Logger} is not supported."); + var testOptions = TestOptionAttributeBase.GetTestOptions(); + switch (testOptions.Logger) + { + case UmbracoTestOptions.Logger.Mock: + return NullLoggerFactory.Instance; + case UmbracoTestOptions.Logger.Serilog: + return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddSerilog(); }); + case UmbracoTestOptions.Logger.Console: + return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddConsole(); }); + } + } + catch + { + // ignored + Debugger.Break(); } - return factory; + return NullLoggerFactory.Instance; } + /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// @@ -174,14 +179,22 @@ namespace Umbraco.Tests.Integration.Testing /// /// /// - public CoreRuntimeBootstrapper CreateTestRuntime( + public void ConfigureSomeMorebitsForTests( + IUmbracoBuilder builder, GlobalSettings globalSettings, ConnectionStrings connectionStrings, - IUmbracoVersion umbracoVersion, IIOHelper ioHelper, - ILoggerFactory loggerFactory, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) + IUmbracoVersion umbracoVersion, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IProfiler profiler, + Core.Hosting.IHostingEnvironment hostingEnvironment, + IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, + AppCaches appCaches, + IDbProviderFactoryCreator dbProviderFactoryCreator) { - var runtime = CreateTestRuntime( + ConfigureSomeMorebitsForTests( + builder, globalSettings, connectionStrings, umbracoVersion, @@ -193,11 +206,8 @@ namespace Umbraco.Tests.Integration.Testing typeFinder, appCaches, dbProviderFactoryCreator, - TestHelper.MainDom, // SimpleMainDom - UseTestLocalDb // DB Installation event handler + TestHelper.MainDom // SimpleMainDom ); - - return runtime; } /// @@ -218,31 +228,50 @@ namespace Umbraco.Tests.Integration.Testing /// The event handler used for DB installation /// /// - public static CoreRuntimeBootstrapper CreateTestRuntime( + public static void ConfigureSomeMorebitsForTests( + IUmbracoBuilder builder, GlobalSettings globalSettings, ConnectionStrings connectionStrings, - IUmbracoVersion umbracoVersion, IIOHelper ioHelper, - ILoggerFactory loggerFactory, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator, - IMainDom mainDom, Action eventHandler) + IUmbracoVersion umbracoVersion, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IProfiler profiler, + Core.Hosting.IHostingEnvironment hostingEnvironment, + IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, + AppCaches appCaches, + IDbProviderFactoryCreator dbProviderFactoryCreator, + IMainDom mainDom) { - var runtime = new CoreRuntimeBootstrapper( - globalSettings, - connectionStrings, - umbracoVersion, - ioHelper, - profiler, - Mock.Of(), - hostingEnvironment, - backOfficeInfo, - dbProviderFactoryCreator, - mainDom, - typeFinder, - appCaches); + // TODO: Don't do this, UmbracoBuilder ctor should handle it... + builder.TypeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, + new DirectoryInfo(hostingEnvironment.LocalTempPath), + builder.BuilderLoggerFactory.CreateLogger(), + new ProfilingLogger(builder.BuilderLoggerFactory.CreateLogger(),profiler) ); - runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args); + var logger = builder.BuilderLoggerFactory.CreateLogger(); + builder.Services.AddUnique(Mock.Of()); + builder.Services.AddUnique(profiler); + builder.Services.AddUnique(new ProfilingLogger(logger, profiler)); + builder.Services.AddUnique(mainDom); + builder.Services.AddUnique(appCaches); + builder.Services.AddUnique(appCaches.RequestCache); + builder.Services.AddUnique(builder.TypeLoader); + builder.Services.AddUnique(typeFinder); + builder.Services.AddUnique(ioHelper); + builder.Services.AddUnique(umbracoVersion); + builder.Services.AddUnique(dbProviderFactoryCreator); + builder.Services.AddUnique(hostingEnvironment); + builder.Services.AddUnique(backOfficeInfo); + builder.Services.AddUnique(); - return runtime; + // after bootstrapping we let the container wire up for us. + builder.Services.AddUnique(); + builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); + builder.Services.AddUnique(factory => factory.GetRequiredService().BulkSqlInsertProvider); + + // re-create the state object with the essential services + builder.Services.AddUnique(); } #endregion @@ -265,7 +294,7 @@ namespace Umbraco.Tests.Integration.Testing GetAppCaches(), TestHelper.GetLoggingConfiguration(), Configuration, - CreateTestRuntime + ConfigureSomeMorebitsForTests ); services.AddSignalR(); @@ -290,6 +319,8 @@ namespace Umbraco.Tests.Integration.Testing public virtual void Configure(IApplicationBuilder app) { + UseTestLocalDb(app.ApplicationServices); + //get the currently set options var testOptions = TestOptionAttributeBase.GetTestOptions(); if (testOptions.Boot) @@ -319,24 +350,15 @@ namespace Umbraco.Tests.Integration.Testing private static readonly object _dbLocker = new object(); private static LocalDbTestDatabase _dbInstance; - /// - /// Event handler for the to install the database - /// - /// - /// - protected void UseTestLocalDb(CoreRuntimeBootstrapper runtimeBootstrapper, RuntimeEssentialsEventArgs args) + protected void UseTestLocalDb(IServiceProvider serviceProvider) { - // This will create a db, install the schema and ensure the app is configured to run - InstallTestLocalDb(args.DatabaseFactory, TestHelper.ConsoleLoggerFactory, runtimeBootstrapper.State, TestHelper.WorkingDirectory); - TestDBConnectionString = args.DatabaseFactory.ConnectionString; - InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString; + var state = serviceProvider.GetRequiredService() as RuntimeState; + var databaseFactory = serviceProvider.GetRequiredService(); - // Re-configure IOptions now that we have a test db - // This is what will be resolved first time IUmbracoDatabaseFactory is resolved from container (e.g. post CoreRuntime bootstrap) - args.Builder.Services.Configure((x) => - { - x.UmbracoConnectionString = new ConfigConnectionString(Constants.System.UmbracoConnectionName, TestDBConnectionString); - }); + // This will create a db, install the schema and ensure the app is configured to run + InstallTestLocalDb(databaseFactory, TestHelper.ConsoleLoggerFactory, state, TestHelper.WorkingDirectory); + TestDBConnectionString = databaseFactory.ConnectionString; + InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString; } /// diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs index cc5ed4ff80..2c769837dc 100644 --- a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs @@ -104,7 +104,7 @@ namespace Umbraco.Web.Common.Builder appCaches, loggingConfig, builder.Config, - UmbracoCoreServiceCollectionExtensions.GetCoreRuntime); + UmbracoCoreServiceCollectionExtensions.ConfigureSomeMorebits); return builder; } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 4ad23f6e50..33fab47715 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -36,7 +36,7 @@ namespace Umbraco.Extensions { public static class UmbracoCoreServiceCollectionExtensions { - + /// /// Adds the Umbraco Back Core requirements @@ -48,18 +48,18 @@ namespace Umbraco.Extensions /// /// /// - /// Delegate to create an + /// Delegate to create an /// /// public static IUmbracoBuilder AddUmbracoCore( this IUmbracoBuilder builder, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, - AppCaches appCaches, + AppCaches appCaches, ILoggingConfiguration loggingConfiguration, IConfiguration configuration, //TODO: Yep that's extremely ugly - Func getRuntimeBootstrapper) + Action configureSomeMoreBits) { if (builder is null) throw new ArgumentNullException(nameof(builder)); if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly)); @@ -108,12 +108,13 @@ namespace Umbraco.Extensions var profiler = GetWebProfiler(hostingEnvironment); builder.Services.AddLogger(loggingConfiguration, configuration); - var loggerFactory = builder.Services.BuildServiceProvider().GetService(); + var loggerFactory = builder.BuilderLoggerFactory; var umbracoVersion = new UmbracoVersion(); var typeFinder = CreateTypeFinder(loggerFactory, profiler, webHostEnvironment, entryAssembly, typeFinderSettings); - var bootstrapper = getRuntimeBootstrapper( + configureSomeMoreBits( + builder, globalSettings.CurrentValue, connectionStrings.Value, umbracoVersion, @@ -126,8 +127,6 @@ namespace Umbraco.Extensions appCaches, dbProviderFactoryCreator); - bootstrapper.Configure(builder); - builder.AddComposers(); return builder; @@ -239,10 +238,19 @@ namespace Umbraco.Extensions return new TypeFinder(loggerFactory.CreateLogger(), new DefaultUmbracoAssemblyProvider(entryAssembly), runtimeHash, new TypeFinderConfig(typeFinderSettings)); } - internal static CoreRuntimeBootstrapper GetCoreRuntime( - GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILoggerFactory loggerFactory, - IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) + internal static void ConfigureSomeMorebits( + IUmbracoBuilder builder, + GlobalSettings globalSettings, + ConnectionStrings connectionStrings, + IUmbracoVersion umbracoVersion, + IIOHelper ioHelper, + ILoggerFactory loggerFactory, + IProfiler profiler, + IHostingEnvironment hostingEnvironment, + IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, + AppCaches appCaches, + IDbProviderFactoryCreator dbProviderFactoryCreator) { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = globalSettings.MainDomLock; @@ -254,21 +262,51 @@ namespace Umbraco.Extensions var mainDom = new MainDom(loggerFactory.CreateLogger(), mainDomLock); - var coreRuntime = new CoreRuntimeBootstrapper( - globalSettings, - connectionStrings, - umbracoVersion, - ioHelper, - profiler, - new AspNetCoreBootPermissionsChecker(), - hostingEnvironment, - backOfficeInfo, - dbProviderFactoryCreator, - mainDom, - typeFinder, - appCaches); - return coreRuntime; + var logger = builder.BuilderLoggerFactory.CreateLogger(); + var profilingLogger = new ProfilingLogger(logger, profiler); + + AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data)); + + // application environment + AppDomain.CurrentDomain.UnhandledException += (_, args) => + { + var exception = (Exception)args.ExceptionObject; + var isTerminating = args.IsTerminating; // always true? + + var msg = "Unhandled exception in AppDomain"; + if (isTerminating) msg += " (terminating)"; + msg += "."; + logger.LogError(exception, msg); + }; + + // TODO: Don't do this, UmbracoBuilder ctor should handle it... + builder.TypeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, + new DirectoryInfo(hostingEnvironment.LocalTempPath), + builder.BuilderLoggerFactory.CreateLogger(), profilingLogger); + + builder.Services.AddUnique(new AspNetCoreBootPermissionsChecker()); + builder.Services.AddUnique(profiler); + builder.Services.AddUnique(profilingLogger); + builder.Services.AddUnique(mainDom); + builder.Services.AddUnique(appCaches); + builder.Services.AddUnique(appCaches.RequestCache); + builder.Services.AddUnique(builder.TypeLoader); + builder.Services.AddUnique(typeFinder); + builder.Services.AddUnique(ioHelper); + builder.Services.AddUnique(umbracoVersion); + builder.Services.AddUnique(dbProviderFactoryCreator); + builder.Services.AddUnique(hostingEnvironment); + builder.Services.AddUnique(backOfficeInfo); + builder.Services.AddUnique(); + + // after bootstrapping we let the container wire up for us. + builder.Services.AddUnique(); + builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); + builder.Services.AddUnique(factory => factory.GetRequiredService().BulkSqlInsertProvider); + + // re-create the state object with the essential services + builder.Services.AddUnique(); } diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 39f6225b54..032ee9bb4f 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -19,48 +19,8 @@ namespace Umbraco.Web /// public class UmbracoApplication : UmbracoApplicationBase { - protected override CoreRuntimeBootstrapper GetRuntime(GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, ILoggerFactory loggerFactory, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - { - var dbProviderFactoryCreator = new UmbracoDbProviderFactoryCreator(); - // Determine if we should use the sql main dom or the default - var appSettingMainDomLock = globalSettings.MainDomLock; - - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" || isWindows == false - ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbProviderFactoryCreator, hostingEnvironment) - : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); - - var mainDom = new MainDom(loggerFactory.CreateLogger(), mainDomLock); - - // Commented out as part of .NET Core transition as the HttpRequestAppCache constructor has changed to - // to match the change in the type of the HTTP context Items collection. - //// var requestCache = new HttpRequestAppCache(() => HttpContext.Current != null ? HttpContext.Current.Items : null); - IRequestCache requestCache = null; - var appCaches = new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), - requestCache, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - - var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker(); - - return new CoreRuntimeBootstrapper( - globalSettings, - connectionStrings, - umbracoVersion, - ioHelper, - profiler, - umbracoBootPermissionChecker, - hostingEnvironment, - backOfficeInfo, - dbProviderFactoryCreator, - mainDom, - GetTypeFinder(hostingEnvironment, logger, profiler), - appCaches - ); - } - } } diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 6ec22c2d24..c416d87e67 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -129,11 +129,6 @@ namespace Umbraco.Web Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()), runtimeHash); } - /// - /// Gets a runtime. - /// - protected abstract CoreRuntimeBootstrapper GetRuntime(GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, ILoggerFactory loggerFactory, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo); - /// /// Gets the application register. /// @@ -177,17 +172,17 @@ namespace Umbraco.Web // create the register for the application, and boot // the boot manager is responsible for registrations var register = GetRegister(globalSettings); - var boostrapper = GetRuntime( - _globalSettings, - _connectionStrings, - umbracoVersion, - _ioHelper, - _logger, - _loggerFactory, - null, // TODO get from somewhere that isn't Current. - null, // TODO get from somewhere that isn't Current. - null // TODO get from somewhere that isn't Current. - ); + //var boostrapper = GetRuntime( + // _globalSettings, + // _connectionStrings, + // umbracoVersion, + // _ioHelper, + // _logger, + // _loggerFactory, + // null, // TODO get from somewhere that isn't Current. + // null, // TODO get from somewhere that isn't Current. + // null // TODO get from somewhere that isn't Current. + // ); //_factory = Current.Factory = _runtime.Configure(register);