diff --git a/src/Umbraco.Core/Composing/Composition.cs b/src/Umbraco.Core/Composing/Composition.cs index 9a696c2dc0..05c7554eab 100644 --- a/src/Umbraco.Core/Composing/Composition.cs +++ b/src/Umbraco.Core/Composing/Composition.cs @@ -33,13 +33,13 @@ namespace Umbraco.Core.Composing /// An IOHelper public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState, Configs configs, IIOHelper ioHelper, AppCaches appCaches) { - _register = register; - TypeLoader = typeLoader; - Logger = logger; - RuntimeState = runtimeState; - Configs = configs; - IOHelper = ioHelper; - AppCaches = appCaches; + _register = register ?? throw new ArgumentNullException(nameof(register)); + TypeLoader = typeLoader ?? throw new ArgumentNullException(nameof(typeLoader)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + RuntimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); + Configs = configs ?? throw new ArgumentNullException(nameof(configs)); + IOHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); + AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); } #region Services diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs index d5355c136f..2099778185 100644 --- a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs +++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs @@ -13,8 +13,9 @@ namespace Umbraco.Core.Composing /// /// public static IHostBuilder UseUmbraco(this IHostBuilder builder) - { - return builder.UseServiceProviderFactory(new UmbracoServiceProviderFactory()); - } + => builder.UseUmbraco(new UmbracoServiceProviderFactory()); + + public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory) + => builder.UseServiceProviderFactory(umbracoServiceProviderFactory); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 17192fd69b..8d5922028c 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Migrations.Install IIOHelper ioHelper, IUmbracoVersion umbracoVersion, IDbProviderFactoryCreator dbProviderFactoryCreator, - IConfigManipulator configManipulator) + IConfigManipulator configManipulator) { _scopeProvider = scopeProvider; _globalSettings = globalSettings; diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index 9318b223df..67cfcded92 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -176,6 +176,8 @@ namespace Umbraco.Core.Runtime // Grid config is not a real config file as we know them composition.RegisterUnique(); + // Config manipulator + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 6c1a06ab6b..4a9441e535 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -115,6 +115,8 @@ namespace Umbraco.Core.Runtime /// public virtual IFactory Boot(IRegister register) { + if (register is null) throw new ArgumentNullException(nameof(register)); + // create and register the essential services // ie the bare minimum required to boot @@ -152,6 +154,9 @@ namespace Umbraco.Core.Runtime /// protected virtual IFactory Boot(IRegister register, DisposableTimer timer) { + if (register is null) throw new ArgumentNullException(nameof(register)); + if (timer is null) throw new ArgumentNullException(nameof(timer)); + Composition composition = null; try diff --git a/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs b/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs new file mode 100644 index 0000000000..9df7270350 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/DelegateHostedService.cs @@ -0,0 +1,38 @@ +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/Implementations/HostBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Implementations/HostBuilderExtensions.cs new file mode 100644 index 0000000000..2d86122770 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/HostBuilderExtensions.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; +using System.IO; +using Umbraco.Core.Persistence; +using Umbraco.Tests.Integration.Testing; + +namespace Umbraco.Tests.Integration +{ + public static class HostBuilderExtensions + { + + + /// + /// Ensures the lifetime of the host ends as soon as code executes + /// + /// + /// + public static IHostBuilder UseTestLifetime(this IHostBuilder hostBuilder) + { + hostBuilder.ConfigureServices((context, collection) => collection.AddSingleton()); + return hostBuilder; + } + + public static IHostBuilder UseLocalDb(this IHostBuilder hostBuilder, string dbFilePath) + { + // Need to register SqlClient manually + // TODO: Move this to someplace central + DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance); + + hostBuilder.ConfigureAppConfiguration(x => + { + if (!Directory.Exists(dbFilePath)) + Directory.CreateDirectory(dbFilePath); + + var dbName = Guid.NewGuid().ToString("N"); + var instance = TestLocalDb.EnsureLocalDbInstanceAndDatabase(dbName, dbFilePath); + + x.AddInMemoryCollection(new[] + { + new KeyValuePair("ConnectionStrings:umbracoDbDSN", instance.GetConnectionString(dbName)) + }); + }); + return hostBuilder; + } + + + } + + +} diff --git a/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs b/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs deleted file mode 100644 index 9081c4ccb4..0000000000 --- a/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Data.Common; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Tests.Integration.Implementations -{ - public class TestDbProviderFactoryCreator : IDbProviderFactoryCreator - { - public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName) - { - throw new System.NotImplementedException(); - } - - public void CreateDatabase() - { - throw new System.NotImplementedException(); - } - - public DbProviderFactory CreateFactory() - { - throw new System.NotImplementedException(); - } - - public DbProviderFactory CreateFactory(string providerName) - { - throw new System.NotImplementedException(); - } - - public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index 5254892b23..4c37903b0c 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Moq; +using System.Data.Common; using System.Net; using Umbraco.Core; using Umbraco.Core.Cache; @@ -60,7 +61,7 @@ namespace Umbraco.Tests.Integration.Implementations public IWebHostEnvironment GetWebHostEnvironment() => _hostEnvironment; - public override IDbProviderFactoryCreator DbProviderFactoryCreator => new TestDbProviderFactoryCreator(); + public override IDbProviderFactoryCreator DbProviderFactoryCreator => new SqlServerDbProviderFactoryCreator("System.Data.SqlClient", DbProviderFactories.GetFactory); public override IBulkSqlInsertProvider BulkSqlInsertProvider => new SqlServerBulkSqlInsertProvider(); diff --git a/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs b/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs new file mode 100644 index 0000000000..063644490b --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Tests.Integration +{ + /// + /// Ensures the host lifetime ends as soon as code execution is done + /// + public class TestLifetime : IHostLifetime + { + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + public Task WaitForStartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + +} diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 4b5cb30544..198fd4831c 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -7,19 +7,36 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; +using System.Collections.Generic; +using System.IO; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.LightInject; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; using Umbraco.Tests.Common; using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; using Umbraco.Web.BackOffice.AspNetCore; +using static Umbraco.Core.Migrations.Install.DatabaseBuilder; namespace Umbraco.Tests.Integration { + [TestFixture] public class RuntimeTests { + [OneTimeTearDown] + public void FixtureTearDown() + { + TestLocalDb.Cleanup(); + } + + /// + /// Manually configure the containers/dependencies and call Boot on Core runtime + /// [Test] public void BootCoreRuntime() { @@ -52,40 +69,107 @@ namespace Umbraco.Tests.Integration Assert.IsTrue(MyComponent.IsTerminated); } + /// + /// Calling AddUmbracoCore to configure the container and boot the core runtime within a generic host + /// [Test] public void AddUmbracoCore() { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + + var host = Host.CreateDefaultBuilder() + .UseTestLifetime() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + var testHelper = new TestHelper(); + + AddRequiredNetCoreServices(services, testHelper); + + // 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(); + + Assert.IsTrue(mainDom.IsMainDom); + Assert.IsNull(runtimeState.BootFailedException); + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + Assert.IsTrue(MyComposer.IsComposed); + + }, 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(); + } + + + [Test] + public void UseUmbracoCore() + { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); - // MSDI - var services = new ServiceCollection(); - // These services are required + var host = Host.CreateDefaultBuilder() + //TODO: Need to have a configured umb version for the runtime state + .UseLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb")) + .UseTestLifetime() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + AddRequiredNetCoreServices(services, testHelper); + + // 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 dbBuilder = umbracoContainer.GetInstance(); + Assert.IsNotNull(dbBuilder); + + var canConnect = dbBuilder.CanConnectToDatabase; + Assert.IsTrue(canConnect); + + var dbResult = dbBuilder.CreateSchemaAndData(); + Assert.IsTrue(dbResult.Success); + + var dbFactory = umbracoContainer.GetInstance(); + var profilingLogger = umbracoContainer.GetInstance(); + runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + }, 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(); + } + + private LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) + { + var container = new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings()); + serviceProviderFactory = new UmbracoServiceProviderFactory(container); + var umbracoContainer = serviceProviderFactory.GetContainer(); + return umbracoContainer; + } + + private void AddRequiredNetCoreServices(IServiceCollection services, TestHelper testHelper) + { services.AddSingleton(x => testHelper.GetHttpContextAccessor()); services.AddSingleton(x => testHelper.GetWebHostEnvironment()); - services.AddSingleton(x => Mock.Of()); - - // LightInject / Umbraco - var container = new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings()); - var serviceProviderFactory = new UmbracoServiceProviderFactory(container); - var umbracoContainer = serviceProviderFactory.GetContainer(); - - // Some IConfiguration must exist in the container first - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddEnvironmentVariables(); - services.AddSingleton(x => configurationBuilder.Build()); - - // Add it! - services.AddUmbracoConfiguration(); - services.AddUmbracoCore(umbracoContainer, GetType().Assembly); - - // assert results - var runtimeState = umbracoContainer.GetInstance(); - var mainDom = umbracoContainer.GetInstance(); - - Assert.IsTrue(mainDom.IsMainDom); - Assert.IsNull(runtimeState.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - Assert.IsTrue(MyComposer.IsComposed); } [RuntimeLevel(MinLevel = RuntimeLevel.Install)] diff --git a/src/Umbraco.Tests.Integration/Testing/TestLocalDb.cs b/src/Umbraco.Tests.Integration/Testing/TestLocalDb.cs new file mode 100644 index 0000000000..8ee326783b --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/TestLocalDb.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Umbraco.Core.Persistence; + +namespace Umbraco.Tests.Integration.Testing +{ + public static class TestLocalDb + { + private const string LocalDbInstanceName = "UmbTests"; + + private static LocalDb LocalDb { get; } = new LocalDb(); + + // TODO: We need to borrow logic from this old branch, this is the latest commit at the old branch where we had LocalDb + // working for tests. There's a lot of hoops to jump through to make it work 'fast'. Turns out it didn't actually run as + // fast as SqlCe due to the dropping/creating of DB instances since that is faster in SqlCe but this code was all heavily + // optimized to go as fast as possible. + // see https://github.com/umbraco/Umbraco-CMS/blob/3a8716ac7b1c48b51258724337086cd0712625a1/src/Umbraco.Tests/TestHelpers/LocalDbTestDatabase.cs + internal static LocalDb.Instance EnsureLocalDbInstanceAndDatabase(string dbName, string dbFilePath) + { + if (!LocalDb.InstanceExists(LocalDbInstanceName) && !LocalDb.CreateInstance(LocalDbInstanceName)) + { + throw new InvalidOperationException( + $"Failed to create LocalDb instance {LocalDbInstanceName}, assuming LocalDb is not really available."); + } + + var instance = LocalDb.GetInstance(LocalDbInstanceName); + + if (instance == null) + { + throw new InvalidOperationException( + $"Failed to get LocalDb instance {LocalDbInstanceName}, assuming LocalDb is not really available."); + } + + instance.CreateDatabase(dbName, dbFilePath); + + return instance; + } + + public static void Cleanup() + { + var instance = LocalDb.GetInstance(LocalDbInstanceName); + if (instance != null) + { + instance.DropDatabases(); + } + } + } +} diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 55b3e8cdca..320db33568 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -15,6 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs index 697e8b7dc4..d668b8c1bc 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs @@ -13,6 +13,7 @@ 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; @@ -63,26 +64,30 @@ namespace Umbraco.Web.BackOffice.AspNetCore public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IRegister umbContainer, Assembly entryAssembly) { + if (services is null) throw new ArgumentNullException(nameof(services)); + if (umbContainer is null) throw new ArgumentNullException(nameof(umbContainer)); + if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly)); + services.AddSingleton(); - CreateCompositionRoot(services); + CreateCompositionRoot(services, out var logger, out var configs, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler); // TODO: Get rid of this 'Current' requirement - var globalSettings = Current.Configs.Global(); + var globalSettings = configs.Global(); var umbracoVersion = new UmbracoVersion(globalSettings); // TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however // this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now. - var typeFinder = new TypeFinder(Current.Logger, new DefaultUmbracoAssemblyProvider(entryAssembly)); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(entryAssembly)); var coreRuntime = GetCoreRuntime( - Current.Configs, + configs, umbracoVersion, - Current.IOHelper, - Current.Logger, - Current.Profiler, - Current.HostingEnvironment, - Current.BackOfficeInfo, + ioHelper, + logger, + profiler, + hostingEnvironment, + backOfficeInfo, typeFinder); var factory = coreRuntime.Boot(umbContainer); @@ -115,7 +120,9 @@ namespace Umbraco.Web.BackOffice.AspNetCore return coreRuntime; } - private static void CreateCompositionRoot(IServiceCollection services) + private static void CreateCompositionRoot(IServiceCollection services, + 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. @@ -123,9 +130,12 @@ namespace Umbraco.Web.BackOffice.AspNetCore var httpContextAccessor = serviceProvider.GetRequiredService(); var webHostEnvironment = serviceProvider.GetRequiredService(); + // TODO: I'm unsure about this, by doing this it means we are resolving a "Different" instance to the one + // that controls the whole app because the instances comes from a different service provider. This + // could cause some issues with shutdowns, etc... we need to investigate. var hostApplicationLifetime = serviceProvider.GetRequiredService(); - var configs = serviceProvider.GetService(); + configs = serviceProvider.GetService(); if (configs == null) throw new InvalidOperationException($"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}"); @@ -133,17 +143,15 @@ namespace Umbraco.Web.BackOffice.AspNetCore var coreDebug = configs.CoreDebug(); var globalSettings = configs.Global(); - var hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment, httpContextAccessor, hostApplicationLifetime); - var ioHelper = new IOHelper(hostingEnvironment, globalSettings); - var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, + hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment, httpContextAccessor, hostApplicationLifetime); + ioHelper = new IOHelper(hostingEnvironment, globalSettings); + logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetCoreSessionIdResolver(httpContextAccessor), () => serviceProvider.GetService(), coreDebug, ioHelper, new AspNetCoreMarchal()); - var backOfficeInfo = new AspNetCoreBackOfficeInfo(configs.Global()); - var profiler = new LogProfiler(logger); - - Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); + backOfficeInfo = new AspNetCoreBackOfficeInfo(configs.Global()); + profiler = new LogProfiler(logger); } private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker