diff --git a/.gitignore b/.gitignore index 8ff5a8ef25..5390da67dd 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ build/temp/ /src/ApiDocs/api/* /src/Umbraco.Web.UI.NetCore/wwwroot/Media/* /src/Umbraco.Web.UI.NetCore/wwwroot/is-cache/* +/src/Umbraco.Tests.Integration/App_Data/* 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.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 61d7cff240..f7c81d6d7e 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -18,6 +18,7 @@ namespace Umbraco.Core.Composing { "Umbraco.Core", "Umbraco.Web", + "Umbraco.Web.BackOffice", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.ModelsBuilder.Embedded", diff --git a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs new file mode 100644 index 0000000000..93441f1a47 --- /dev/null +++ b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Hosting +{ + public interface IApplicationShutdownRegistry + { + void RegisterObject(IRegisteredObject registeredObject); + void UnregisterObject(IRegisteredObject registeredObject); + } +} diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs index 5b97d8e4f3..b12ffbc138 100644 --- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs +++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs @@ -19,8 +19,5 @@ namespace Umbraco.Core.Hosting Version IISVersion { get; } string MapPath(string path); string ToAbsolute(string virtualPath, string root); - - void RegisterObject(IRegisteredObject registeredObject); - void UnregisterObject(IRegisteredObject registeredObject); } } diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index 10d5b10955..3e5361a1c1 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -1,5 +1,6 @@ namespace Umbraco.Net { + // TODO: This shouldn't be in this namespace? public interface IUmbracoApplicationLifetime { /// diff --git a/src/Umbraco.Core/Runtime/IMainDom.cs b/src/Umbraco.Core/Runtime/IMainDom.cs index 444fc1c7d0..93a560ff7d 100644 --- a/src/Umbraco.Core/Runtime/IMainDom.cs +++ b/src/Umbraco.Core/Runtime/IMainDom.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Hosting; // TODO: Can't change namespace due to breaking changes, change in netcore namespace Umbraco.Core @@ -16,10 +17,17 @@ namespace Umbraco.Core /// Gets a value indicating whether the current domain is the main domain. /// /// - /// When the first call is made to this there will generally be some logic executed to acquire a distributed lock lease. + /// Acquire must be called first else this will always return false /// bool IsMainDom { get; } + /// + /// Tries to acquire the MainDom, returns true if successful else false + /// + /// + /// + bool Acquire(IApplicationShutdownRegistry hostingEnvironment); + /// /// Registers a resource that requires the current AppDomain to be the main domain to function. /// diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 2c56852095..81db1b700d 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Runtime #region Vars private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private IApplicationShutdownRegistry _hostingEnvironment; private readonly IMainDomLock _mainDomLock; // our own lock for local consistency @@ -42,17 +42,25 @@ namespace Umbraco.Core.Runtime #region Ctor // initializes a new instance of MainDom - public MainDom(ILogger logger, IHostingEnvironment hostingEnvironment, IMainDomLock systemLock) + public MainDom(ILogger logger, IMainDomLock systemLock) { - hostingEnvironment.RegisterObject(this); - _logger = logger; - _hostingEnvironment = hostingEnvironment; _mainDomLock = systemLock; } #endregion + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + + return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => + { + hostingEnvironment.RegisterObject(this); + return Acquire(); + }); + } + /// /// Registers a resource that requires the current AppDomain to be the main domain to function. /// @@ -180,10 +188,9 @@ namespace Umbraco.Core.Runtime /// Gets a value indicating whether the current domain is the main domain. /// /// - /// The lazy initializer call will only call the Acquire callback when it's not been initialized, else it will just return - /// the value from _isMainDom which means when we set _isMainDom to false again after being signaled, this will return false; + /// Acquire must be called first else this will always return false /// - public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); + public bool IsMainDom => _isMainDom; // IRegisteredObject void IRegisteredObject.Stop(bool immediate) @@ -193,7 +200,7 @@ namespace Umbraco.Core.Runtime // The web app is stopping, need to wind down Dispose(true); - _hostingEnvironment.UnregisterObject(this); + _hostingEnvironment?.UnregisterObject(this); } #region IDisposable Support diff --git a/src/Umbraco.Core/Scheduling/KeepAlive.cs b/src/Umbraco.Core/Scheduling/KeepAlive.cs index 515251b105..1c4ef075ae 100644 --- a/src/Umbraco.Core/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Core/Scheduling/KeepAlive.cs @@ -11,18 +11,22 @@ namespace Umbraco.Web.Scheduling { public class KeepAlive : RecurringTaskBase { - private readonly IRuntimeState _runtime; + private readonly IRuntimeState _runtimeState; + private readonly IMainDom _mainDom; private readonly IKeepAliveSettings _keepAliveSettings; private readonly IProfilingLogger _logger; + private readonly IServerRegistrar _serverRegistrar; private static HttpClient _httpClient; public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IKeepAliveSettings keepAliveSettings, IProfilingLogger logger) + IRuntimeState runtimeState, IMainDom mainDom, IKeepAliveSettings keepAliveSettings, IProfilingLogger logger, IServerRegistrar serverRegistrar) : base(runner, delayMilliseconds, periodMilliseconds) { - _runtime = runtime; + _runtimeState = runtimeState; + _mainDom = mainDom; _keepAliveSettings = keepAliveSettings; _logger = logger; + _serverRegistrar = serverRegistrar; if (_httpClient == null) _httpClient = new HttpClient(); } @@ -30,7 +34,7 @@ namespace Umbraco.Web.Scheduling public override async Task PerformRunAsync(CancellationToken token) { // not on replicas nor unknown role servers - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -41,7 +45,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down @@ -54,7 +58,7 @@ namespace Umbraco.Web.Scheduling { if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}")) { - var umbracoAppUrl = _runtime.ApplicationUrl.ToString(); + var umbracoAppUrl = _runtimeState.ApplicationUrl.ToString(); if (umbracoAppUrl.IsNullOrWhiteSpace()) { _logger.Warn("No umbracoApplicationUrl for service (yet), skip."); diff --git a/src/Umbraco.Core/Scheduling/TempFileCleanup.cs b/src/Umbraco.Core/Scheduling/TempFileCleanup.cs index aefaf605db..90bf4ee9eb 100644 --- a/src/Umbraco.Core/Scheduling/TempFileCleanup.cs +++ b/src/Umbraco.Core/Scheduling/TempFileCleanup.cs @@ -14,26 +14,26 @@ namespace Umbraco.Web.Scheduling { private readonly DirectoryInfo[] _tempFolders; private readonly TimeSpan _age; - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; public TempFileCleanup(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, IEnumerable tempFolders, TimeSpan age, - IRuntimeState runtime, IProfilingLogger logger) + IMainDom mainDom, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { //SystemDirectories.TempFileUploads _tempFolders = tempFolders.ToArray(); _age = age; - _runtime = runtime; + _mainDom = mainDom; _logger = logger; } public override bool PerformRun() { // ensure we do not run if not main domain - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Core/Services/IRuntime.cs b/src/Umbraco.Core/Services/IRuntime.cs index e846342dbc..4715068073 100644 --- a/src/Umbraco.Core/Services/IRuntime.cs +++ b/src/Umbraco.Core/Services/IRuntime.cs @@ -13,13 +13,15 @@ namespace Umbraco.Core /// /// The application register. /// The application factory. - IFactory Boot(IRegister register); + IFactory Configure(IRegister register); /// /// Gets the runtime state. /// IRuntimeState State { get; } + void Start(); + /// /// Terminates the runtime. /// diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index 38da246cc1..4b5e26651b 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -25,33 +25,12 @@ namespace Umbraco.Core /// SemVersion SemanticVersion { get; } - /// - /// Gets a value indicating whether the application is running in debug mode. - /// - bool Debug { get; } - - /// - /// Gets a value indicating whether the runtime is the current main domain. - /// - bool IsMainDom { get; } - - /// - /// Get the server's current role. - /// - ServerRole ServerRole { get; } - /// /// Gets the Umbraco application url. /// /// 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. /// @@ -77,6 +56,5 @@ namespace Umbraco.Core /// BootFailedException BootFailedException { get; } - IMainDom MainDom { get; } } } diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index 87cc7bcff1..e6bdda67d7 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Hosting; namespace Umbraco.Core { @@ -16,6 +17,9 @@ namespace Umbraco.Core /// public bool IsMainDom { get; private set; } = true; + // always acquire + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) => true; + /// public bool Register(Action release, int weight = 100) => Register(null, release, weight); diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index 78b9589a2e..70b79fbeb0 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -28,17 +28,18 @@ namespace Umbraco.Web private readonly IRequestAccessor _requestAccessor; public BatchedDatabaseServerMessenger( - IRuntimeState runtime, + IMainDom mainDom, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, + IServerRegistrar serverRegistrar, DatabaseServerMessengerOptions options, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, IRequestCache requestCache, IRequestAccessor requestAccessor) - : base(runtime, scopeProvider, sqlContext, proflog, true, options, hostingEnvironment, cacheRefreshers) + : base(mainDom, scopeProvider, sqlContext, proflog, serverRegistrar, true, options, hostingEnvironment, cacheRefreshers) { _databaseFactory = databaseFactory; _requestCache = requestCache; diff --git a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 2a24e6f318..6c745f3877 100644 --- a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -91,7 +91,6 @@ namespace Umbraco.Web.Compose private readonly BackgroundTaskRunner _processTaskRunner; private bool _started; private IBackgroundTask[] _tasks; - private IndexRebuilder _indexRebuilder; private readonly IRequestAccessor _requestAccessor; public DatabaseServerRegistrarAndMessengerComponent( @@ -100,14 +99,12 @@ namespace Umbraco.Web.Compose IServerMessenger serverMessenger, IServerRegistrationService registrationService, ILogger logger, - IHostingEnvironment hostingEnvironment, - IndexRebuilder indexRebuilder, + IApplicationShutdownRegistry hostingEnvironment, IRequestAccessor requestAccessor) { _runtime = runtime; _logger = logger; _registrationService = registrationService; - _indexRebuilder = indexRebuilder; _requestAccessor = requestAccessor; // create task runner for DatabaseServerRegistrar diff --git a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs index d4e22bbfde..a2ee650595 100644 --- a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Compose { public sealed class ManifestWatcherComponent : IComponent { - private readonly IRuntimeState _runtimeState; + private readonly IHostingEnvironment _hosting; private readonly ILogger _logger; private readonly IIOHelper _ioHelper; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; @@ -19,9 +19,9 @@ namespace Umbraco.Core.Compose // package.manifest chances and restarts the application on any change private ManifestWatcher _mw; - public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger, IIOHelper ioHelper, IUmbracoApplicationLifetime umbracoApplicationLifetime) + public ManifestWatcherComponent(IHostingEnvironment hosting, ILogger logger, IIOHelper ioHelper, IUmbracoApplicationLifetime umbracoApplicationLifetime) { - _runtimeState = runtimeState; + _hosting = hosting; _logger = logger; _ioHelper = ioHelper; _umbracoApplicationLifetime = umbracoApplicationLifetime; @@ -29,7 +29,7 @@ namespace Umbraco.Core.Compose public void Initialize() { - if (_runtimeState.Debug == false) return; + if (_hosting.IsDebugMode == false) return; //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) // return; 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/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/Intall/InstallHelper.cs b/src/Umbraco.Infrastructure/Intall/InstallHelper.cs index a5b4f71d8e..cf4b174cde 100644 --- a/src/Umbraco.Infrastructure/Intall/InstallHelper.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallHelper.cs @@ -9,10 +9,10 @@ using Umbraco.Core.Cookie; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models; +using Umbraco.Net; using Umbraco.Core.Persistence; using Umbraco.Core.Serialization; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install diff --git a/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs index cc269408f4..4e14da30b7 100644 --- a/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs +++ b/src/Umbraco.Infrastructure/Intall/InstallSteps/StarterKitInstallStep.cs @@ -2,8 +2,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Umbraco.Core.Services; using Umbraco.Net; +using Umbraco.Core.Services; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps 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..1bf954e4c1 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Runtime public override void Compose(Composition composition) { base.Compose(composition); - + // composers composition .ComposeRepositories() @@ -120,10 +120,11 @@ namespace Umbraco.Core.Runtime // project composition.RegisterUnique(factory => new DatabaseServerMessenger( - factory.GetInstance(), + factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), + factory.GetInstance(), true, new DatabaseServerMessengerOptions(), factory.GetInstance(), factory.GetInstance() @@ -176,6 +177,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..139e9e9b67 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Reflection; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -12,7 +11,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime { @@ -25,12 +23,11 @@ namespace Umbraco.Core.Runtime { private ComponentCollection _components; private IFactory _factory; - private RuntimeState _state; + private readonly RuntimeState _state; private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; private readonly IGlobalSettings _globalSettings; private readonly IConnectionStrings _connectionStrings; - public CoreRuntime( Configs configs, IUmbracoVersion umbracoVersion, @@ -65,11 +62,7 @@ namespace Umbraco.Core.Runtime // 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(() => mainDom), - new Lazy(() => _factory.GetInstance()), - UmbracoVersion,HostingEnvironment, BackOfficeInfo) + _state = new RuntimeState(Logger, Configs.Global(), UmbracoVersion, BackOfficeInfo) { Level = RuntimeLevel.Boot }; @@ -81,13 +74,13 @@ namespace Umbraco.Core.Runtime protected ILogger Logger { get; } protected IBackOfficeInfo BackOfficeInfo { get; } + public IDbProviderFactoryCreator DbProviderFactoryCreator { get; } - //public IBulkSqlInsertProvider BulkSqlInsertProvider { get; } /// /// Gets the profiler. /// - protected IProfiler Profiler { get; set; } + protected IProfiler Profiler { get; } /// /// Gets the profiling logger. @@ -110,11 +103,13 @@ namespace Umbraco.Core.Runtime /// public IRuntimeState State => _state; - public IMainDom MainDom { get; private set; } + public IMainDom MainDom { get; } /// - public virtual IFactory Boot(IRegister register) + public virtual IFactory Configure(IRegister register) { + if (register is null) throw new ArgumentNullException(nameof(register)); + // create and register the essential services // ie the bare minimum required to boot @@ -139,25 +134,24 @@ namespace Umbraco.Core.Runtime // application environment ConfigureUnhandledException(); - ConfigureApplicationRootPath(); - - Boot(register, timer); + return _factory = Configure(register, timer); } - - return _factory; } /// - /// Boots the runtime within a timer. + /// Configure the runtime within a timer. /// - protected virtual IFactory Boot(IRegister register, DisposableTimer timer) + private IFactory Configure(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; + IFactory factory = null; try { - // throws if not full-trust - _umbracoBootPermissionChecker.ThrowIfNotPermissions(); + // run handlers RuntimeOptions.DoRuntimeBoot(ProfilingLogger); @@ -171,31 +165,12 @@ 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); - - // register runtime-level services - // there should be none, really - this is here "just in case" - Compose(composition); - - // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - AcquireMainDom(MainDom); + // register ourselves (TODO: Should we put this in RegisterEssentials?) + composition.Register(_ => this, Lifetime.Singleton); // determine our runtime level DetermineRuntimeLevel(databaseFactory, ProfilingLogger); @@ -213,13 +188,7 @@ namespace Umbraco.Core.Runtime composers.Compose(); // create the factory - _factory = composition.CreateFactory(); - - // create & initialize the components - _components = _factory.GetInstance(); - _components.Initialize(); - - + factory = composition.CreateFactory(); } catch (Exception e) { @@ -236,11 +205,11 @@ namespace Umbraco.Core.Runtime // if something goes wrong above, we may end up with no factory // meaning nothing can get the runtime state, etc - so let's try // to make sure we have a factory - if (_factory == null) + if (factory == null) { try { - _factory = composition?.CreateFactory(); + factory = composition?.CreateFactory(); } catch { /* yea */ } } @@ -254,7 +223,29 @@ namespace Umbraco.Core.Runtime // throw a BootFailedException for every requests. } - return _factory; + return factory; + } + + public void Start() + { + // throws if not full-trust + _umbracoBootPermissionChecker.ThrowIfNotPermissions(); + + ConfigureApplicationRootPath(); + + // 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)}"); + + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate + AcquireMainDom(MainDom, _factory.GetInstance()); + + // create & initialize the components + _components = _factory.GetInstance(); + _components.Initialize(); } protected virtual void ConfigureUnhandledException() @@ -281,13 +272,13 @@ namespace Umbraco.Core.Runtime IOHelper.SetRootDirectory(path); } - private bool AcquireMainDom(IMainDom mainDom) + private bool AcquireMainDom(IMainDom mainDom, IApplicationShutdownRegistry applicationShutdownRegistry) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - return mainDom.IsMainDom; + return mainDom.Acquire(applicationShutdownRegistry); } catch { @@ -346,12 +337,6 @@ namespace Umbraco.Core.Runtime _components?.Terminate(); } - /// - /// Composes the runtime. - /// - public virtual void Compose(Composition composition) - { - } #region Getters @@ -392,5 +377,6 @@ namespace Umbraco.Core.Runtime #endregion + } } diff --git a/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs index 2f45a3e437..fc2a019023 100644 --- a/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs @@ -40,7 +40,7 @@ namespace Umbraco.Web.Runtime } /// - public override IFactory Boot(IRegister register) + public override IFactory Configure(IRegister register) { var profilingLogger = new ProfilingLogger(Logger, Profiler); @@ -57,7 +57,7 @@ namespace Umbraco.Web.Runtime NetworkHelper.MachineName); Logger.Debug("Runtime: {Runtime}", GetType().FullName); - var factory = base.Boot(register); + var factory = base.Configure(register); // 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 diff --git a/src/Umbraco.Infrastructure/RuntimeOptions.cs b/src/Umbraco.Infrastructure/RuntimeOptions.cs index 23abd474a4..562a7e3c5c 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(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + internal static void DoRuntimeEssentials(IFactory factory) { if (_onEssentials== null) return; foreach (var action in _onEssentials) - action(composition, appCaches, typeLoader, databaseFactory); + action(factory); } /// @@ -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 eadb9fed21..e0099c5e7e 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -22,46 +22,22 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly IGlobalSettings _globalSettings; private readonly ConcurrentHashSet _applicationUrls = new ConcurrentHashSet(); - private readonly Lazy _mainDom; - private readonly Lazy _serverRegistrar; private readonly IUmbracoVersion _umbracoVersion; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IBackOfficeInfo _backOfficeInfo; /// /// Initializes a new instance of the class. /// public RuntimeState(ILogger logger, IGlobalSettings globalSettings, - Lazy mainDom, Lazy serverRegistrar, IUmbracoVersion umbracoVersion, - IHostingEnvironment hostingEnvironment, + IUmbracoVersion umbracoVersion, IBackOfficeInfo backOfficeInfo) { _logger = logger; _globalSettings = globalSettings; - _mainDom = mainDom; - _serverRegistrar = serverRegistrar; _umbracoVersion = umbracoVersion; - _hostingEnvironment = hostingEnvironment; _backOfficeInfo = backOfficeInfo; - - ApplicationVirtualPath = _hostingEnvironment.ApplicationVirtualPath; } - /// - /// Gets the server registrar. - /// - /// - /// This is NOT exposed in the interface. - /// - private IServerRegistrar ServerRegistrar => _serverRegistrar.Value; - - /// - /// Gets the application MainDom. - /// - /// - /// This is NOT exposed in the interface as MainDom is internal. - /// - public IMainDom MainDom => _mainDom.Value; /// public Version Version => _umbracoVersion.Current; @@ -72,26 +48,14 @@ namespace Umbraco.Core /// public SemVersion SemanticVersion => _umbracoVersion.SemanticVersion; - /// - public bool Debug => _hostingEnvironment.IsDebugMode; - - /// - public bool IsMainDom => MainDom.IsMainDom; - - /// - public ServerRole ServerRole => ServerRegistrar.GetCurrentServerRole(); - /// public Uri ApplicationUrl { get; private set; } /// - public string ApplicationVirtualPath { get; } + public string CurrentMigrationState { get; private set; } /// - public string CurrentMigrationState { get; internal set; } - - /// - public string FinalMigrationState { get; internal set; } + public string FinalMigrationState { get; private set; } /// public RuntimeLevel Level { get; internal set; } = RuntimeLevel.Unknown; @@ -253,7 +217,7 @@ namespace Umbraco.Core Reason = RuntimeLevelReason.UpgradeMigrations; } - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + private bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion, _globalSettings)); var stateValueKey = upgrader.StateValueKey; diff --git a/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs index 0b5b81319f..2cda289591 100644 --- a/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Infrastructure/Scheduling/BackgroundTaskRunner.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web.Scheduling private readonly string _logPrefix; private readonly BackgroundTaskRunnerOptions _options; private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private readonly object _locker = new object(); private readonly BufferBlock _tasks = new BufferBlock(new DataflowBlockOptions()); @@ -105,7 +105,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, hostingEnvironment, hook) { } @@ -116,7 +116,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(string name, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(string name, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(name, new BackgroundTaskRunnerOptions(), logger, hostingEnvironment, hook) { } @@ -127,7 +127,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) : this(typeof(T).FullName, options, logger, hostingEnvironment, hook) { } @@ -139,7 +139,7 @@ namespace Umbraco.Web.Scheduling /// A logger. /// The hosting environment /// An optional main domain hook. - public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, IHostingEnvironment hostingEnvironment, MainDomHook hook = null) + public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, IApplicationShutdownRegistry hostingEnvironment, MainDomHook hook = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs index 04c1571b3b..e7692b851a 100644 --- a/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/Scheduling/HealthCheckNotifier.cs @@ -11,22 +11,27 @@ namespace Umbraco.Web.Scheduling { public class HealthCheckNotifier : RecurringTaskBase { - private readonly IRuntimeState _runtimeState; + private readonly IMainDom _mainDom; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IProfilingLogger _logger; private readonly IHealthChecksSettings _healthChecksSettingsConfig; + private readonly IServerRegistrar _serverRegistrar; + private readonly IRuntimeState _runtimeState; public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, - IRuntimeState runtimeState, IProfilingLogger logger, IHealthChecksSettings healthChecksSettingsConfig) + IMainDom mainDom, IProfilingLogger logger, IHealthChecksSettings healthChecksSettingsConfig, IServerRegistrar serverRegistrar, + IRuntimeState runtimeState) : base(runner, delayMilliseconds, periodMilliseconds) { _healthChecks = healthChecks; _notifications = notifications; - _runtimeState = runtimeState; + _mainDom = mainDom; _logger = logger; _healthChecksSettingsConfig = healthChecksSettingsConfig; + _serverRegistrar = serverRegistrar; + _runtimeState = runtimeState; } public override async Task PerformRunAsync(CancellationToken token) @@ -34,7 +39,7 @@ namespace Umbraco.Web.Scheduling if (_runtimeState.Level != RuntimeLevel.Run) return true; // repeat... - switch (_runtimeState.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -45,7 +50,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtimeState.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs b/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs index 563d79a193..aaf09dbe8f 100644 --- a/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/Scheduling/LogScrubber.cs @@ -11,17 +11,19 @@ namespace Umbraco.Web.Scheduling public class LogScrubber : RecurringTaskBase { - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IAuditService _auditService; private readonly ILoggingSettings _settings; private readonly IProfilingLogger _logger; private readonly IScopeProvider _scopeProvider; public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IAuditService auditService, ILoggingSettings settings, IScopeProvider scopeProvider, IProfilingLogger logger) + IMainDom mainDom, IServerRegistrar serverRegistrar, IAuditService auditService, ILoggingSettings settings, IScopeProvider scopeProvider, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { - _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _auditService = auditService; _settings = settings; _scopeProvider = scopeProvider; @@ -53,7 +55,7 @@ namespace Umbraco.Web.Scheduling public override bool PerformRun() { - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -64,7 +66,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs index b074704033..fea16999fd 100644 --- a/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/Scheduling/ScheduledPublishing.cs @@ -10,16 +10,20 @@ namespace Umbraco.Web.Scheduling public class ScheduledPublishing : RecurringTaskBase { private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IContentService _contentService; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILogger _logger; private readonly IServerMessenger _serverMessenger; public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) + IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger) : base(runner, delayMilliseconds, periodMilliseconds) { _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _contentService = contentService; _umbracoContextFactory = umbracoContextFactory; _logger = logger; @@ -31,7 +35,7 @@ namespace Umbraco.Web.Scheduling if (Suspendable.ScheduledPublishing.CanRun == false) return true; // repeat, later - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.Debug("Does not run on replica servers."); @@ -42,7 +46,7 @@ namespace Umbraco.Web.Scheduling } // ensure we do not run if not main domain, but do NOT lock it - if (_runtime.IsMainDom == false) + if (_mainDom.IsMainDom == false) { _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down diff --git a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs index c8ff67579a..30d0bc7e4a 100644 --- a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs @@ -26,10 +26,12 @@ namespace Umbraco.Web.Scheduling private const int OneHourMilliseconds = 3600000; private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; + private readonly IServerRegistrar _serverRegistrar; private readonly IContentService _contentService; private readonly IAuditService _auditService; private readonly IProfilingLogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private readonly IScopeProvider _scopeProvider; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; @@ -43,7 +45,6 @@ namespace Umbraco.Web.Scheduling private BackgroundTaskRunner _keepAliveRunner; private BackgroundTaskRunner _publishingRunner; - private BackgroundTaskRunner _tasksRunner; private BackgroundTaskRunner _scrubberRunner; private BackgroundTaskRunner _fileCleanupRunner; private BackgroundTaskRunner _healthCheckRunner; @@ -52,15 +53,17 @@ namespace Umbraco.Web.Scheduling private object _locker = new object(); private IBackgroundTask[] _tasks; - public SchedulerComponent(IRuntimeState runtime, + public SchedulerComponent(IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, IAuditService auditService, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IProfilingLogger logger, - IHostingEnvironment hostingEnvironment, IHealthChecksSettings healthChecksSettingsConfig, + IApplicationShutdownRegistry hostingEnvironment, IHealthChecksSettings healthChecksSettingsConfig, IIOHelper ioHelper, IServerMessenger serverMessenger, IRequestAccessor requestAccessor, ILoggingSettings loggingSettings, IKeepAliveSettings keepAliveSettings) { _runtime = runtime; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; _contentService = contentService; _auditService = auditService; _scopeProvider = scopeProvider; @@ -83,7 +86,6 @@ namespace Umbraco.Web.Scheduling // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", _logger, _hostingEnvironment); _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", _logger, _hostingEnvironment); - _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", _logger, _hostingEnvironment); _scrubberRunner = new BackgroundTaskRunner("LogScrubber", _logger, _hostingEnvironment); _fileCleanupRunner = new BackgroundTaskRunner("TempFileCleanup", _logger, _hostingEnvironment); _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", _logger, _hostingEnvironment); @@ -138,7 +140,7 @@ namespace Umbraco.Web.Scheduling { // ping/keepalive // on all servers - var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, keepAliveSettings, _logger); + var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, _mainDom, keepAliveSettings, _logger, _serverRegistrar); _keepAliveRunner.TryAdd(task); return task; } @@ -147,7 +149,7 @@ namespace Umbraco.Web.Scheduling { // scheduled publishing/unpublishing // install on all, will only run on non-replica servers - var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _contentService, _umbracoContextFactory, _logger, _serverMessenger); + var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _mainDom, _serverRegistrar, _contentService, _umbracoContextFactory, _logger, _serverMessenger); _publishingRunner.TryAdd(task); return task; } @@ -173,7 +175,7 @@ namespace Umbraco.Web.Scheduling } var periodInMilliseconds = healthCheckSettingsConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; - var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _runtime, logger, _healthChecksSettingsConfig); + var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _mainDom, logger, _healthChecksSettingsConfig, _serverRegistrar, _runtime); _healthCheckRunner.TryAdd(task); return task; } @@ -182,7 +184,7 @@ namespace Umbraco.Web.Scheduling { // log scrubbing // install on all, will only run on non-replica servers - var task = new LogScrubber(_scrubberRunner, DefaultDelayMilliseconds, LogScrubber.GetLogScrubbingInterval(), _runtime, _auditService, settings, _scopeProvider, _logger); + var task = new LogScrubber(_scrubberRunner, DefaultDelayMilliseconds, LogScrubber.GetLogScrubbingInterval(), _mainDom, _serverRegistrar, _auditService, settings, _scopeProvider, _logger); _scrubberRunner.TryAdd(task); return task; } @@ -194,7 +196,7 @@ namespace Umbraco.Web.Scheduling var task = new TempFileCleanup(_fileCleanupRunner, DefaultDelayMilliseconds, OneHourMilliseconds, new[] { new DirectoryInfo(_ioHelper.MapPath(Constants.SystemDirectories.TempFileUploads)) }, TimeSpan.FromDays(1), //files that are over a day old - _runtime, _logger); + _mainDom, _logger); _scrubberRunner.TryAdd(task); return task; } diff --git a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs index 2c964a2723..1946e2041b 100644 --- a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs @@ -18,10 +18,10 @@ namespace Umbraco.Web.Search private readonly IndexRebuilder _indexRebuilder; private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingEnvironment; private static BackgroundTaskRunner _rebuildOnStartupRunner; - public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IHostingEnvironment hostingEnvironment, IndexRebuilder indexRebuilder) + public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) { _mainDom = mainDom; _logger = logger; @@ -32,8 +32,6 @@ namespace Umbraco.Web.Search /// /// Called to rebuild empty indexes on startup /// - /// - /// /// /// public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 5a46a37d43..c915013162 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -28,10 +28,11 @@ namespace Umbraco.Core.Sync // public class DatabaseServerMessenger : ServerMessengerBase, IDatabaseServerMessenger { - private readonly IRuntimeState _runtime; + private readonly IMainDom _mainDom; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); private readonly IProfilingLogger _profilingLogger; + private readonly IServerRegistrar _serverRegistrar; private readonly IHostingEnvironment _hostingEnvironment; private readonly CacheRefresherCollection _cacheRefreshers; private readonly ISqlContext _sqlContext; @@ -46,14 +47,15 @@ namespace Umbraco.Core.Sync public DatabaseServerMessengerOptions Options { get; } public DatabaseServerMessenger( - IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, + IMainDom mainDom, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IServerRegistrar serverRegistrar, bool distributedEnabled, DatabaseServerMessengerOptions options, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers) : base(distributedEnabled) { ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); _sqlContext = sqlContext; - _runtime = runtime; + _mainDom = mainDom; _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); + _serverRegistrar = serverRegistrar; _hostingEnvironment = hostingEnvironment; _cacheRefreshers = cacheRefreshers; Logger = proflog; @@ -126,7 +128,7 @@ namespace Umbraco.Core.Sync const int weight = 10; - var registered = _runtime.MainDom.Register( + var registered = _mainDom.Register( () => { lock (_locko) @@ -262,7 +264,7 @@ namespace Umbraco.Core.Sync _lastPruned = _lastSync; - switch (_runtime.ServerRole) + switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Single: case ServerRole.Master: diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 5db79de977..3cfefa77ce 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -42,7 +42,7 @@ namespace Umbraco.ModelsBuilder.Embedded private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; private readonly IModelsBuilderConfig _config; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingLifetime; private readonly IIOHelper _ioHelper; private readonly ModelsGenerationError _errors; @@ -51,12 +51,13 @@ namespace Umbraco.ModelsBuilder.Embedded IProfilingLogger logger, IModelsBuilderConfig config, IHostingEnvironment hostingEnvironment, + IApplicationShutdownRegistry hostingLifetime, IIOHelper ioHelper) { _umbracoServices = umbracoServices; _logger = logger; _config = config; - _hostingEnvironment = hostingEnvironment; + _hostingLifetime = hostingLifetime; _ioHelper = ioHelper; _errors = new ModelsGenerationError(config, ioHelper); _ver = 1; // zero is for when we had no version @@ -64,7 +65,7 @@ namespace Umbraco.ModelsBuilder.Embedded RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted; - if (!_hostingEnvironment.IsHosted) return; + if (!hostingEnvironment.IsHosted) return; var modelsDirectory = _config.ModelsDirectoryAbsolute(_ioHelper); if (!Directory.Exists(modelsDirectory)) @@ -73,7 +74,7 @@ namespace Umbraco.ModelsBuilder.Embedded // BEWARE! if the watcher is not properly released then for some reason the // BuildManager will start confusing types - using a 'registered object' here // though we should probably plug into Umbraco's MainDom - which is internal - _hostingEnvironment.RegisterObject(this); + _hostingLifetime.RegisterObject(this); _watcher = new FileSystemWatcher(modelsDirectory); _watcher.Changed += WatcherOnChanged; _watcher.EnableRaisingEvents = true; @@ -677,7 +678,7 @@ namespace Umbraco.ModelsBuilder.Embedded { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); - _hostingEnvironment.UnregisterObject(this); + _hostingLifetime.UnregisterObject(this); } #endregion diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index 536bebee56..67108833c3 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -12,11 +12,11 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Net; using Umbraco.Core.Persistence; using Umbraco.Core.Serialization; using Umbraco.Core.Strings; using Umbraco.Core.Sync; -using Umbraco.Net; using Umbraco.Web; using Umbraco.Web.Routing; @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Common private UriUtility _uriUtility; private IIOHelper _ioHelper; - public TestHelperBase(Assembly entryAssembly) + protected TestHelperBase(Assembly entryAssembly) { SettingsForTests = new SettingsForTests(); MainDom = new SimpleMainDom(); @@ -52,12 +52,8 @@ namespace Umbraco.Tests.Common return new RuntimeState( Mock.Of(), Mock.Of(), - new Lazy(), - new Lazy(), GetUmbracoVersion(), - GetHostingEnvironment(), - GetBackOfficeInfo() - ); + GetBackOfficeInfo()); } public abstract IBackOfficeInfo GetBackOfficeInfo(); @@ -132,6 +128,7 @@ namespace Umbraco.Tests.Common } public abstract IHostingEnvironment GetHostingEnvironment(); + public abstract IApplicationShutdownRegistry GetHostingEnvironmentLifetime(); public abstract IIpResolver GetIpResolver(); diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs index d190d1165d..945eeda2f0 100644 --- a/src/Umbraco.Tests.Integration/ContainerTests.cs +++ b/src/Umbraco.Tests.Integration/ContainerTests.cs @@ -1,4 +1,5 @@ -using LightInject; +using System.Threading.Tasks; +using LightInject; using LightInject.Microsoft.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -37,7 +38,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 +46,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); @@ -67,6 +69,46 @@ namespace Umbraco.Tests.Integration Assertions.AssertContainer(umbracoContainer.Container); } + [Explicit("This test just shows that resolving services from the container before the host is done resolves 2 different instances")] + [Test] + public async Task BuildServiceProvider_Before_Host_Is_Configured() + { + // This is a test to show an anti-pattern used in netcore. This should be avoided in all cases if possible. + // There's a thread about this here: https://github.com/dotnet/aspnetcore/issues/14587 + // For some reason we are not being warned about this with our code analysis since we are using it + // in a couple of places but we should really try to see if we can avoid it. + // The test below shows how it could be possible to resolve an instance and then re-register it as a factory + // so that only one singleton instance is every created, but it's hacky and like Fowler says in that article + // 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 = RuntimeTests.GetUmbracoContainer(out var serviceProviderFactory); + + IHostApplicationLifetime lifetime1 = null; + + var hostBuilder = new HostBuilder() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + // Resolve a service from the netcore container before the host has finished the ConfigureServices sequence + lifetime1 = services.BuildServiceProvider().GetRequiredService(); + + // Re-add as a callback, ensures its the same instance all the way through (hack) + services.AddSingleton(x => lifetime1); + }); + + var host = await hostBuilder.StartAsync(); + + var lifetime2 = host.Services.GetRequiredService(); + var lifetime3 = umbracoContainer.GetInstance(); + + lifetime1.StopApplication(); + Assert.IsTrue(lifetime1.ApplicationStopping.IsCancellationRequested); + Assert.AreEqual(lifetime1.ApplicationStopping.IsCancellationRequested, lifetime2.ApplicationStopping.IsCancellationRequested); + Assert.AreEqual(lifetime1.ApplicationStopping.IsCancellationRequested, lifetime3.ApplicationStopping.IsCancellationRequested); + + } + private class Foo { public Foo() diff --git a/src/Umbraco.Tests.Integration/Implementations/HostBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Implementations/HostBuilderExtensions.cs new file mode 100644 index 0000000000..60cac26349 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/HostBuilderExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Umbraco.Core; +using Umbraco.Tests.Integration.Testing; + +namespace Umbraco.Tests.Integration.Implementations +{ + public static class HostBuilderExtensions + { + + public static IHostBuilder UseLocalDb(this IHostBuilder hostBuilder, string dbFilePath) + { + // Need to register SqlClient manually + // TODO: Move this to someplace central + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, 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:{Constants.System.UmbracoConnectionName}", 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..4a986fa35a 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -3,14 +3,16 @@ 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; using Umbraco.Core.Diagnostics; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; +using Umbraco.Net; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; -using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Web.BackOffice; using Umbraco.Web.BackOffice.AspNetCore; @@ -18,11 +20,11 @@ using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Tests.Integration.Implementations { - public class TestHelper : TestHelperBase { private IBackOfficeInfo _backOfficeInfo; private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingLifetime; private readonly IIpResolver _ipResolver; private readonly IWebHostEnvironment _hostEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; @@ -39,11 +41,12 @@ namespace Umbraco.Tests.Integration.Implementations && x.ContentRootPath == CurrentAssemblyDirectory && x.WebRootPath == CurrentAssemblyDirectory); // same folder for now? - _hostingEnvironment = new AspNetCoreHostingEnvironment( + _hostingEnvironment = new TestHostingEnvironment( SettingsForTests.GetDefaultHostingSettings(), _hostEnvironment, - _httpContextAccessor, - Mock.Of()); + _httpContextAccessor); + + _hostingLifetime = new AspNetCoreApplicationShutdownRegistry(Mock.Of()); Logger = new ProfilingLogger(new ConsoleLogger(new MessageTemplates()), Profiler); } @@ -60,7 +63,7 @@ namespace Umbraco.Tests.Integration.Implementations public IWebHostEnvironment GetWebHostEnvironment() => _hostEnvironment; - public override IDbProviderFactoryCreator DbProviderFactoryCreator => new TestDbProviderFactoryCreator(); + public override IDbProviderFactoryCreator DbProviderFactoryCreator => new SqlServerDbProviderFactoryCreator(Constants.DbProviderNames.SqlServer, DbProviderFactories.GetFactory); public override IBulkSqlInsertProvider BulkSqlInsertProvider => new SqlServerBulkSqlInsertProvider(); @@ -74,7 +77,9 @@ namespace Umbraco.Tests.Integration.Implementations } public override IHostingEnvironment GetHostingEnvironment() => _hostingEnvironment; + public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _hostingLifetime; public override IIpResolver GetIpResolver() => _ipResolver; + } } diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs new file mode 100644 index 0000000000..491b7e5480 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Umbraco.Core.Configuration; +using Umbraco.Web.BackOffice.AspNetCore; + +namespace Umbraco.Tests.Integration.Implementations +{ + + public class TestHostingEnvironment : AspNetCoreHostingEnvironment, Umbraco.Core.Hosting.IHostingEnvironment + { + public TestHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor) : base(hostingSettings, webHostEnvironment, httpContextAccessor) + { + } + + /// + /// Override for tests since we are not hosted + /// + /// + /// This is specifically used by IOHelper and we want this to return false so that the root path is manually calcualted which is what we want for tests. + /// + bool Umbraco.Core.Hosting.IHostingEnvironment.IsHosted { get; } = false; + } +} diff --git a/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs b/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs new file mode 100644 index 0000000000..a18360eff9 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Implementations/TestLifetime.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Umbraco.Tests.Integration.Implementations +{ + /// + /// 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 24786a1591..0e11a29b95 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -7,41 +7,76 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; 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 { + [TearDown] + public void TearDown() + { + MyComponent.Reset(); + MyComposer.Reset(); + } + + [OneTimeTearDown] + public void FixtureTearDown() + { + TestLocalDb.Cleanup(); + } + + /// + /// Manually configure the containers/dependencies and call Boot on Core runtime + /// [Test] - public void BootCoreRuntime() + public void Boot_Core_Runtime() { // LightInject / Umbraco var container = UmbracoServiceProviderFactory.CreateServiceContainer(); var serviceProviderFactory = new UmbracoServiceProviderFactory(container); var umbracoContainer = serviceProviderFactory.GetContainer(); - // Create the core runtime + // Special case since we are not using the Generic Host, we need to manually add an AspNetCore service to the container + umbracoContainer.Register(x => Mock.Of()); + var testHelper = new TestHelper(); + + // Create the core runtime 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()); - // boot it! - var factory = coreRuntime.Boot(umbracoContainer); + // boot it! + var factory = coreRuntime.Configure(umbracoContainer); Assert.IsTrue(coreRuntime.MainDom.IsMainDom); Assert.IsNull(coreRuntime.State.BootFailedException); Assert.AreEqual(RuntimeLevel.Install, coreRuntime.State.Level); Assert.IsTrue(MyComposer.IsComposed); + Assert.IsFalse(MyComponent.IsInit); + Assert.IsFalse(MyComponent.IsTerminated); + + coreRuntime.Start(); + Assert.IsTrue(MyComponent.IsInit); Assert.IsFalse(MyComponent.IsTerminated); @@ -52,40 +87,150 @@ namespace Umbraco.Tests.Integration Assert.IsTrue(MyComponent.IsTerminated); } + /// + /// Calling AddUmbracoCore to configure the container + /// [Test] - public void AddUmbracoCore() + public async Task AddUmbracoCore() { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); - // MSDI - var services = new ServiceCollection(); - // These services are required - services.AddSingleton(x => testHelper.GetHttpContextAccessor()); - services.AddSingleton(x => testHelper.GetWebHostEnvironment()); - services.AddSingleton(x => Mock.Of()); + var hostBuilder = new HostBuilder() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + var webHostEnvironment = testHelper.GetWebHostEnvironment(); + AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); - // LightInject / Umbraco - var container = UmbracoServiceProviderFactory.CreateServiceContainer(); - var serviceProviderFactory = new UmbracoServiceProviderFactory(container); - var umbracoContainer = serviceProviderFactory.GetContainer(); + // Add it! + services.AddUmbracoConfiguration(hostContext.Configuration); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + }); - // 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); + var host = await hostBuilder.StartAsync(); + var app = new ApplicationBuilder(host.Services); // assert results - var runtimeState = umbracoContainer.GetInstance(); - var mainDom = umbracoContainer.GetInstance(); + var runtimeState = app.ApplicationServices.GetRequiredService(); + var mainDom = app.ApplicationServices.GetRequiredService(); + + 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(); + + Assert.IsFalse(MyComponent.IsTerminated); // we didn't "Start" the runtime so nothing was registered for shutdown + } + + /// + /// Calling AddUmbracoCore to configure the container and UseUmbracoCore to start the runtime + /// + /// + [Test] + public async Task UseUmbracoCore() + { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + var testHelper = new TestHelper(); + + var hostBuilder = new HostBuilder() + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + var webHostEnvironment = testHelper.GetWebHostEnvironment(); + AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); + + // Add it! + services.AddUmbracoConfiguration(hostContext.Configuration); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + }); + + var host = await hostBuilder.StartAsync(); + var app = new ApplicationBuilder(host.Services); + + app.UseUmbracoCore(); + + + // assert results + var runtimeState = app.ApplicationServices.GetRequiredService(); + var mainDom = app.ApplicationServices.GetRequiredService(); Assert.IsTrue(mainDom.IsMainDom); Assert.IsNull(runtimeState.BootFailedException); Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - Assert.IsTrue(MyComposer.IsComposed); + Assert.IsTrue(MyComponent.IsInit); + + await host.StopAsync(); + + Assert.IsTrue(MyComponent.IsTerminated); + } + + [Test] + public async Task Install_Database() + { + var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); + var testHelper = new TestHelper(); + + var hostBuilder = new HostBuilder() + //TODO: Need to have a configured umb version for the runtime state + .UseLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb")) + .UseUmbraco(serviceProviderFactory) + .ConfigureServices((hostContext, services) => + { + var webHostEnvironment = testHelper.GetWebHostEnvironment(); + AddRequiredNetCoreServices(services, testHelper, webHostEnvironment); + + // Add it! + services.AddUmbracoConfiguration(hostContext.Configuration); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + }); + + var host = await hostBuilder.StartAsync(); + var app = new ApplicationBuilder(host.Services); + + app.UseUmbracoCore(); + + + var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService(); + Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); + + var dbBuilder = app.ApplicationServices.GetRequiredService(); + Assert.IsNotNull(dbBuilder); + + var canConnect = dbBuilder.CanConnectToDatabase; + Assert.IsTrue(canConnect); + + var dbResult = dbBuilder.CreateSchemaAndData(); + Assert.IsTrue(dbResult.Success); + + // TODO: Get this to work ... but to do that we need to mock or pass in a current umbraco version + //var dbFactory = app.ApplicationServices.GetRequiredService(); + //var profilingLogger = app.ApplicationServices.GetRequiredService(); + //runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); + //Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + } + + internal static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) + { + var container = UmbracoServiceProviderFactory.CreateServiceContainer(); + serviceProviderFactory = new UmbracoServiceProviderFactory(container); + var umbracoContainer = serviceProviderFactory.GetContainer(); + return umbracoContainer; + } + + /// + /// These services need to be manually added because they do not get added by the generic host + /// + /// + /// + /// + private void AddRequiredNetCoreServices(IServiceCollection services, TestHelper testHelper, IWebHostEnvironment webHostEnvironment) + { + services.AddSingleton(x => testHelper.GetHttpContextAccessor()); + // the generic host does add IHostEnvironment but not this one because we are not actually in a web context + services.AddSingleton(x => webHostEnvironment); } [RuntimeLevel(MinLevel = RuntimeLevel.Install)] @@ -97,6 +242,11 @@ namespace Umbraco.Tests.Integration IsComposed = true; } + public static void Reset() + { + IsComposed = false; + } + public static bool IsComposed { get; private set; } } @@ -121,6 +271,12 @@ namespace Umbraco.Tests.Integration { IsTerminated = true; } + + public static void Reset() + { + IsTerminated = false; + IsInit = false; + } } } 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.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 609dcd98b8..bf7cbe40c4 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -41,6 +41,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IEntityXmlSerializer _entitySerializer; private readonly IVariationContextAccessor _variationContextAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IApplicationShutdownRegistry _hostingLifetime; private readonly IHostingEnvironment _hostingEnvironment; #region Constructors @@ -57,6 +58,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache ILogger logger, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, + IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper, ISiteDomainHelper siteDomainHelper, IEntityXmlSerializer entitySerializer, @@ -67,7 +69,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor, documentRepository, mediaRepository, memberRepository, defaultCultureAccessor, - logger, globalSettings, hostingEnvironment, shortStringHelper, siteDomainHelper, entitySerializer, null, mainDom, testing, enableRepositoryEvents) + logger, globalSettings, hostingEnvironment, hostingLifetime, shortStringHelper, siteDomainHelper, entitySerializer, null, mainDom, testing, enableRepositoryEvents) { _umbracoContextAccessor = umbracoContextAccessor; } @@ -84,6 +86,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache ILogger logger, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, + IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper, ISiteDomainHelper siteDomainHelper, IEntityXmlSerializer entitySerializer, @@ -99,7 +102,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _xmlStore = new XmlStore(serviceContext.ContentTypeService, serviceContext.ContentService, scopeProvider, _routesCache, _contentTypeCache, publishedSnapshotAccessor, mainDom, testing, enableRepositoryEvents, - documentRepository, mediaRepository, memberRepository, globalSettings, entitySerializer, hostingEnvironment, shortStringHelper); + documentRepository, mediaRepository, memberRepository, entitySerializer, hostingEnvironment, hostingLifetime, shortStringHelper); _domainService = serviceContext.DomainService; _memberService = serviceContext.MemberService; @@ -113,6 +116,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _siteDomainHelper = siteDomainHelper; _entitySerializer = entitySerializer; _hostingEnvironment = hostingEnvironment; + _hostingLifetime = hostingLifetime; } public override void Dispose() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index 238da68370..dadf8b6824 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -45,9 +45,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IDocumentRepository _documentRepository; private readonly IMediaRepository _mediaRepository; private readonly IMemberRepository _memberRepository; - private readonly IGlobalSettings _globalSettings; private readonly IEntityXmlSerializer _entitySerializer; - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IApplicationShutdownRegistry _hostingLifetime; private readonly IShortStringHelper _shortStringHelper; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly PublishedContentTypeCache _contentTypeCache; @@ -58,7 +57,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private XmlStoreFilePersister _persisterTask; private volatile bool _released; - private bool _withRepositoryEvents; #region Constructors @@ -67,8 +65,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// /// The default constructor will boot the cache, load data from file or database, /// wire events in order to manage changes, etc. public XmlStore(IContentTypeService contentTypeService, IContentService contentService, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IShortStringHelper shortStringHelper) - : this(contentTypeService, contentService, scopeProvider, routesCache, contentTypeCache, publishedSnapshotAccessor, mainDom, false, false, documentRepository, mediaRepository, memberRepository, globalSettings, entitySerializer, hostingEnvironment, shortStringHelper) + IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper) + : this(contentTypeService, contentService, scopeProvider, routesCache, contentTypeCache, publishedSnapshotAccessor, mainDom, false, false, documentRepository, mediaRepository, memberRepository, entitySerializer, hostingEnvironment, hostingLifetime, shortStringHelper) { } // internal for unit tests @@ -76,7 +74,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // TODO: er, we DO have a DB? internal XmlStore(IContentTypeService contentTypeService, IContentService contentService, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, - bool testing, bool enableRepositoryEvents, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IShortStringHelper shortStringHelper) + bool testing, bool enableRepositoryEvents, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IEntityXmlSerializer entitySerializer, IHostingEnvironment hostingEnvironment, IApplicationShutdownRegistry hostingLifetime, IShortStringHelper shortStringHelper) { if (testing == false) EnsureConfigurationIsValid(); @@ -90,12 +88,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _documentRepository = documentRepository; _mediaRepository = mediaRepository; _memberRepository = memberRepository; - _globalSettings = globalSettings; _entitySerializer = entitySerializer; - _hostingEnvironment = hostingEnvironment; + _hostingLifetime = hostingLifetime; _shortStringHelper = shortStringHelper; - _xmlFileName = TestHelper.IOHelper.MapPath(SystemFiles.GetContentCacheXml(_hostingEnvironment)); + _xmlFileName = TestHelper.IOHelper.MapPath(SystemFiles.GetContentCacheXml(hostingEnvironment)); if (testing) { @@ -150,7 +147,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache LongRunning = true, KeepAlive = true, Hosted = false // main domain will take care of stopping the runner (see below) - }, logger, _hostingEnvironment); + }, logger, _hostingLifetime); // create (and add to runner) _persisterTask = new XmlStoreFilePersister(runner, this, logger); @@ -207,7 +204,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; - _withRepositoryEvents = true; } private void ClearEvents() @@ -226,7 +222,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; - _withRepositoryEvents = false; } private void LazyInitializeContent() diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 561a6d945e..f9de3579c2 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -32,8 +32,7 @@ namespace Umbraco.Tests.Routing //create the module var logger = Mock.Of(); var globalSettings = TestObjects.GetGlobalSettings(); - var runtime = new RuntimeState(logger, globalSettings, - new Lazy(), new Lazy(), UmbracoVersion, HostingEnvironment, BackOfficeInfo); + var runtime = new RuntimeState(logger, globalSettings, UmbracoVersion, BackOfficeInfo); _module = new UmbracoInjectedModule ( diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 92ab825ee2..6ccb2c3b35 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -104,11 +104,6 @@ namespace Umbraco.Tests.Runtimes return configs; } - private static IProfiler GetProfiler() - { - return new TestProfiler(); - } - public IRuntime Runtime { get; private set; } protected override IRuntime GetRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) @@ -136,50 +131,14 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } - // FIXME: so how the f* should we do it now? - /* - // pretend we have the proper migration - // else BootFailedException because our mock IUmbracoDatabaseFactory does not provide databases - protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory) + public override IFactory Configure(IRegister container) { - return true; - } - */ + container.Register(Lifetime.Singleton); - // because we don't even have the core runtime component, - // there are a few required stuff that we need to compose - public override void Compose(Composition composition) - { - base.Compose(composition); - - var scopeProvider = Mock.Of(); - Mock.Get(scopeProvider) - .Setup(x => x.CreateScope( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(Mock.Of()); - - composition.RegisterUnique(scopeProvider); - } - - private IMainDom _mainDom; - - public override IFactory Boot(IRegister container) - { - var factory = base.Boot(container); - _mainDom = factory.GetInstance(); + var factory = base.Configure(container); return factory; } - public override void Terminate() - { - base.Terminate(); - } - // runs with only one single component // UmbracoCoreComponent will be force-added too // and that's it diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 0cb3f3e2e1..f167aafb89 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Runtimes var mainDom = new SimpleMainDom(); var umbracoVersion = TestHelper.GetUmbracoVersion(); var backOfficeInfo = TestHelper.GetBackOfficeInfo(); - var runtimeState = new RuntimeState(logger, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance()), umbracoVersion, hostingEnvironment, backOfficeInfo); + var runtimeState = new RuntimeState(logger, null, umbracoVersion, backOfficeInfo); var configs = TestHelper.GetConfigs(); var variationContextAccessor = TestHelper.VariationContextAccessor; @@ -80,11 +80,10 @@ 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); - coreRuntime.Compose(composition); // determine actual runtime level runtimeState.DetermineRuntimeLevel(databaseFactory, logger); @@ -275,11 +274,10 @@ 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); - coreRuntime.Compose(composition); // get the components // all of them? diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index f10b141916..2291584f66 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -19,13 +19,13 @@ namespace Umbraco.Tests.Scheduling public class BackgroundTaskRunnerTests { private ILogger _logger; - private IHostingEnvironment _hostingEnvironment; + private IApplicationShutdownRegistry _hostingEnvironment; [OneTimeSetUp] public void InitializeFixture() { _logger = new ConsoleLogger(new MessageTemplates()); - _hostingEnvironment = TestHelper.GetHostingEnvironment(); + _hostingEnvironment = TestHelper.GetHostingEnvironmentLifetime(); } [Test] @@ -934,7 +934,7 @@ namespace Umbraco.Tests.Scheduling [Test] public void SourceTaskTest() { - var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, _logger, TestHelper.GetHostingEnvironment()); + var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, _logger, TestHelper.GetHostingEnvironmentLifetime()); var task = new SourceTask(); runner.Add(task); diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests2.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests2.cs index c65c7e3efb..01169abce2 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests2.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests2.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Scheduling public async Task ThreadResumeIssue() { var logger = new DebugDiagnosticsLogger(new MessageTemplates()); - var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, logger, TestHelper.GetHostingEnvironment()); + var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, logger, TestHelper.GetHostingEnvironmentLifetime()); var work = new ThreadResumeIssueWorkItem(); runner.Add(work); @@ -77,7 +77,7 @@ namespace Umbraco.Tests.Scheduling public async Task DebuggerInterferenceIssue() { var logger = new DebugDiagnosticsLogger(new MessageTemplates()); - var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, logger, TestHelper.GetHostingEnvironment()); + var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, logger, TestHelper.GetHostingEnvironmentLifetime()); var taskCompleted = false; runner.TaskCompleted += (sender, args) => { diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 05d4de6e23..11393e3e6b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -21,12 +21,12 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Net; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Web; using Umbraco.Web.Hosting; @@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers /// public static class TestHelper { - private static TestHelperInternal _testHelperInternal = new TestHelperInternal(); + private static readonly TestHelperInternal _testHelperInternal = new TestHelperInternal(); private class TestHelperInternal : TestHelperBase { public TestHelperInternal() : base(typeof(TestHelperInternal).Assembly) @@ -62,6 +62,9 @@ namespace Umbraco.Tests.TestHelpers public override IHostingEnvironment GetHostingEnvironment() => new AspNetHostingEnvironment(SettingsForTests.GetDefaultHostingSettings()); + public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() + => new AspNetApplicationShutdownRegistry(); + public override IIpResolver GetIpResolver() => new AspNetIpResolver(); } @@ -320,6 +323,8 @@ namespace Umbraco.Tests.TestHelpers public static IHostingEnvironment GetHostingEnvironment() => _testHelperInternal.GetHostingEnvironment(); + public static IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _testHelperInternal.GetHostingEnvironmentLifetime(); + public static IIpResolver GetIpResolver() => _testHelperInternal.GetIpResolver(); public static IRequestCache GetRequestCache() => _testHelperInternal.GetRequestCache(); diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 5cf2c7b6bd..fbfada118a 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -265,6 +265,7 @@ namespace Umbraco.Tests.TestHelpers Logger, Factory.GetInstance(), HostingEnvironment, + HostingLifetime, ShortStringHelper, new SiteDomainHelper(), Factory.GetInstance(), diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index ca36a6049e..0345aab2da 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -51,10 +51,10 @@ using Umbraco.Web.Templates; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Dictionary; using Umbraco.Core.Models; +using Umbraco.Net; using Umbraco.Core.Request; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Web.AspNet; using Umbraco.Web.Install; @@ -140,6 +140,7 @@ namespace Umbraco.Tests.Testing protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); protected IHostingEnvironment HostingEnvironment { get; } = new AspNetHostingEnvironment(TestHelpers.SettingsForTests.GetDefaultHostingSettings()); + protected IApplicationShutdownRegistry HostingLifetime { get; } = new AspNetApplicationShutdownRegistry(); protected IIpResolver IpResolver => Factory.GetInstance(); protected IBackOfficeInfo BackOfficeInfo => Factory.GetInstance(); protected AppCaches AppCaches => Factory.GetInstance(); diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index 5e5197a28a..c5e9556d05 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -423,6 +423,7 @@ namespace Umbraco.Tests.Web.Mvc new TestDefaultCultureAccessor(), Current.Logger, TestObjects.GetGlobalSettings(), TestHelper.GetHostingEnvironment(), + TestHelper.GetHostingEnvironmentLifetime(), ShortStringHelper, new SiteDomainHelper(), Factory.GetInstance(), diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs new file mode 100644 index 0000000000..92af822836 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using Microsoft.Extensions.Hosting; +using Umbraco.Core; +using Umbraco.Core.Hosting; + +namespace Umbraco.Web.BackOffice.AspNetCore +{ + public class AspNetCoreApplicationShutdownRegistry : IApplicationShutdownRegistry + { + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly ConcurrentDictionary _registeredObjects = + new ConcurrentDictionary(); + + public AspNetCoreApplicationShutdownRegistry(IHostApplicationLifetime hostApplicationLifetime) + { + _hostApplicationLifetime = hostApplicationLifetime; + } + + public void RegisterObject(IRegisteredObject registeredObject) + { + var wrapped = new RegisteredObjectWrapper(registeredObject); + if (!_registeredObjects.TryAdd(registeredObject, wrapped)) + { + throw new InvalidOperationException("Could not register object"); + } + + var cancellationTokenRegistration = _hostApplicationLifetime.ApplicationStopping.Register(() => wrapped.Stop(true)); + wrapped.CancellationTokenRegistration = cancellationTokenRegistration; + } + + public void UnregisterObject(IRegisteredObject registeredObject) + { + if (_registeredObjects.TryGetValue(registeredObject, out var wrapped)) + { + wrapped.CancellationTokenRegistration.Unregister(); + } + } + + + private class RegisteredObjectWrapper + { + private readonly IRegisteredObject _inner; + + public RegisteredObjectWrapper(IRegisteredObject inner) + { + _inner = inner; + } + + public CancellationTokenRegistration CancellationTokenRegistration { get; set; } + + public void Stop(bool immediate) + { + _inner.Stop(immediate); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs new file mode 100644 index 0000000000..fc38e429a0 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Http; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; +using Umbraco.Net; +using Umbraco.Core.Runtime; + +namespace Umbraco.Web.BackOffice.AspNetCore +{ + /// + /// Adds/replaces AspNetCore specific services + /// + [ComposeBefore(typeof(ICoreComposer))] + [ComposeAfter(typeof(CoreInitialComposer))] + public class AspNetCoreComposer : IComposer + { + public void Compose(Composition composition) + { + // AspNetCore specific services + composition.RegisterUnique(); + + // Our own netcore implementations + composition.RegisterUnique(); + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs index 00bc894b0d..6f1298918d 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Globalization; using System.IO; -using System.Threading; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Hosting; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -13,30 +10,29 @@ namespace Umbraco.Web.BackOffice.AspNetCore { public class AspNetCoreHostingEnvironment : Umbraco.Core.Hosting.IHostingEnvironment { - private readonly ConcurrentDictionary _registeredObjects = - new ConcurrentDictionary(); + private readonly IHostingSettings _hostingSettings; private readonly IWebHostEnvironment _webHostEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IHostApplicationLifetime _hostApplicationLifetime; + private string _localTempPath; - public AspNetCoreHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor, IHostApplicationLifetime hostHostApplicationLifetime) + public AspNetCoreHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor) { _hostingSettings = hostingSettings ?? throw new ArgumentNullException(nameof(hostingSettings)); _webHostEnvironment = webHostEnvironment; _httpContextAccessor = httpContextAccessor; - _hostApplicationLifetime = hostHostApplicationLifetime; SiteName = webHostEnvironment.ApplicationName; ApplicationId = AppDomain.CurrentDomain.Id.ToString(); ApplicationPhysicalPath = webHostEnvironment.ContentRootPath; ApplicationVirtualPath = "/"; //TODO how to find this, This is a server thing, not application thing. - IISVersion = new Version(0, 0); // TODO not necessary IIS + IISVersion = new Version(0, 0); // TODO not necessary IIS IsDebugMode = _hostingSettings.DebugMode; } + public bool IsHosted { get; } = true; public string SiteName { get; } public string ApplicationId { get; } @@ -56,7 +52,10 @@ namespace Umbraco.Web.BackOffice.AspNetCore switch (_hostingSettings.LocalTempStorageLocation) { case LocalTempStorage.AspNetTemp: - return _localTempPath = System.IO.Path.Combine(Path.GetTempPath(),ApplicationId, "UmbracoData"); + + // TODO: I don't think this is correct? but also we probably can remove AspNetTemp as an option entirely + // since this is legacy and we shouldn't use it + return _localTempPath = System.IO.Path.Combine(Path.GetTempPath(), ApplicationId, "UmbracoData"); case LocalTempStorage.EnvironmentTemp: @@ -84,8 +83,11 @@ namespace Umbraco.Web.BackOffice.AspNetCore } } - // TODO: This may need to take into account ~/ paths which means the ApplicationVirtualPath and is this the content root or web root? - public string MapPath(string path) => Path.Combine(_webHostEnvironment.WebRootPath, path); + public string MapPath(string path) + { + var newPath = path.TrimStart('~', '/').Replace('/', Path.DirectorySeparatorChar); + return Path.Combine(_webHostEnvironment.WebRootPath, newPath); + } // TODO: Need to take into account 'root' here public string ToAbsolute(string virtualPath, string root) @@ -101,43 +103,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore return applicationPath.Add(segment).Value; } - public void RegisterObject(IRegisteredObject registeredObject) - { - var wrapped = new RegisteredObjectWrapper(registeredObject); - if (!_registeredObjects.TryAdd(registeredObject, wrapped)) - { - throw new InvalidOperationException("Could not register object"); - } - - var cancellationTokenRegistration = _hostApplicationLifetime.ApplicationStopping.Register(() => wrapped.Stop(true)); - wrapped.CancellationTokenRegistration = cancellationTokenRegistration; - } - - public void UnregisterObject(IRegisteredObject registeredObject) - { - if (_registeredObjects.TryGetValue(registeredObject, out var wrapped)) - { - wrapped.CancellationTokenRegistration.Unregister(); - } - } - - - private class RegisteredObjectWrapper - { - private readonly IRegisteredObject _inner; - - public RegisteredObjectWrapper(IRegisteredObject inner) - { - _inner = inner; - } - - public CancellationTokenRegistration CancellationTokenRegistration { get; set; } - - public void Stop(bool immediate) - { - _inner.Stop(immediate); - } - } + } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs index 98e040d338..247666090e 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs @@ -2,7 +2,7 @@ using System; using System.Runtime.InteropServices; using Umbraco.Core.Diagnostics; -namespace Umbraco.Web.BackOffice +namespace Umbraco.Web.BackOffice.AspNetCore { public class AspNetCoreMarchal : IMarchal diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index 20cfef352d..0dca3edec8 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Umbraco.Net; -namespace Umbraco.Web.AspNet +namespace Umbraco.Web.BackOffice.AspNetCore { public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime { diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs index 1f06a818d6..770f4b883f 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeApplicationBuilderExtensions.cs @@ -1,5 +1,10 @@ using System; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; namespace Umbraco.Web.BackOffice.AspNetCore { @@ -7,12 +12,57 @@ 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; } + + /// + /// Start Umbraco + /// + /// + /// + public static IApplicationBuilder UseUmbracoCore(this IApplicationBuilder app) + { + if (app == null) throw new ArgumentNullException(nameof(app)); + + // Register a listener for application shutdown in order to terminate the runtime + var hostLifetime = app.ApplicationServices.GetRequiredService(); + var runtime = app.ApplicationServices.GetRequiredService(); + var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime); + hostLifetime.RegisterObject(runtimeShutdown); + + // Start the runtime! + runtime.Start(); + + return app; + } + + /// + /// Ensures the runtime is shutdown when the application is shutting down + /// + private class CoreRuntimeShutdown : IRegisteredObject + { + public CoreRuntimeShutdown(IRuntime runtime, IApplicationShutdownRegistry hostLifetime) + { + _runtime = runtime; + _hostLifetime = hostLifetime; + } + + private bool _completed = false; + private readonly IRuntime _runtime; + private readonly IApplicationShutdownRegistry _hostLifetime; + + public void Stop(bool immediate) + { + if (!_completed) + { + _completed = true; + _runtime.Terminate(); + _hostLifetime.UnregisterObject(this); + } + + } + } } } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs similarity index 57% rename from src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs rename to src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs index 135ba90b97..d747cb349a 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs @@ -6,13 +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.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; @@ -21,17 +19,18 @@ using Umbraco.Core.Runtime; namespace Umbraco.Web.BackOffice.AspNetCore { - - - public static class UmbracoBackOfficeServiceCollectionExtensions + // TODO: Move to Umbraco.Web.Common + public static class UmbracoCoreServiceCollectionExtensions { - - public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services) + /// + /// Adds the Umbraco Configuration requirements + /// + /// + /// + /// + public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services, IConfiguration configuration) { - var serviceProvider = services.BuildServiceProvider(); - var configuration = serviceProvider.GetService(); - if (configuration == null) - throw new InvalidOperationException($"Could not resolve {typeof(IConfiguration)} from the container"); + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); var configsFactory = new AspNetCoreConfigsFactory(configuration); @@ -44,48 +43,59 @@ 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) + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) { if (!UmbracoServiceProviderFactory.IsActive) throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory"); var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer; - return services.AddUmbracoCore(umbContainer, Assembly.GetEntryAssembly()); + return services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly()); } - public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IRegister umbContainer, Assembly entryAssembly) + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, 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)); + + // Special case! The generic host adds a few default services but we need to manually add this one here NOW because + // we resolve it before the host finishes configuring in the call to CreateCompositionRoot services.AddSingleton(); - CreateCompositionRoot(services); + CreateCompositionRoot(services, webHostEnvironment, 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); + var factory = coreRuntime.Configure(umbContainer); return services; } @@ -107,7 +117,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator) : new MainDomSemaphoreLock(logger, hostingEnvironment); - var mainDom = new MainDom(logger, hostingEnvironment, mainDomLock); + var mainDom = new MainDom(logger, mainDomLock); var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetCoreBootPermissionsChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder); @@ -115,17 +125,16 @@ namespace Umbraco.Web.BackOffice.AspNetCore return coreRuntime; } - private static void CreateCompositionRoot(IServiceCollection services) + private static void CreateCompositionRoot(IServiceCollection services, IWebHostEnvironment webHostEnvironment, + 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: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured var serviceProvider = services.BuildServiceProvider(); var httpContextAccessor = serviceProvider.GetRequiredService(); - var webHostEnvironment = serviceProvider.GetRequiredService(); - 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,18 +142,16 @@ 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); + ioHelper = new IOHelper(hostingEnvironment, globalSettings); + logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetCoreSessionIdResolver(httpContextAccessor), - // need to build a new service provider since the one already resolved above doesn't have the IRequestCache yet + // TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured () => services.BuildServiceProvider().GetService(), coreDebug, ioHelper, new AspNetCoreMarchal()); - var backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); - var profiler = new LogProfiler(logger); - - Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); + backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); + profiler = new LogProfiler(logger); } private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker @@ -154,5 +161,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore // nothing to check } } + + } } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index ab8c6e021c..2753676452 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -16,13 +16,29 @@ namespace Umbraco.Web.UI.BackOffice { public class Startup { + private readonly IWebHostEnvironment _webHostEnvironment; + private readonly IConfiguration _config; + + /// + /// Constructor + /// + /// + /// + /// + /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337 + /// + public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + _webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); + _config = config ?? throw new ArgumentNullException(nameof(config)); + } // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddUmbracoConfiguration(); - services.AddUmbracoCore(); + services.AddUmbracoConfiguration(_config); + services.AddUmbracoCore(_webHostEnvironment); services.AddUmbracoWebsite(); } diff --git a/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs b/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs index f7a198bc3b..6d2ce5e7e4 100644 --- a/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.Website.AspNetCore { public static IServiceCollection AddUmbracoWebsite(this IServiceCollection services) { + // TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured var serviceProvider = services.BuildServiceProvider(); var configs = serviceProvider.GetService(); var imagingSettings = configs.Imaging(); diff --git a/src/Umbraco.Web/AspNet/AspNetApplicationShutdownRegistry.cs b/src/Umbraco.Web/AspNet/AspNetApplicationShutdownRegistry.cs new file mode 100644 index 0000000000..dbd6e9e834 --- /dev/null +++ b/src/Umbraco.Web/AspNet/AspNetApplicationShutdownRegistry.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Concurrent; +using System.Web.Hosting; +using Umbraco.Core.Hosting; +using IRegisteredObject = Umbraco.Core.IRegisteredObject; + +namespace Umbraco.Web.Hosting +{ + public class AspNetApplicationShutdownRegistry : IApplicationShutdownRegistry + { + private readonly ConcurrentDictionary _registeredObjects = + new ConcurrentDictionary(); + + public void RegisterObject(IRegisteredObject registeredObject) + { + var wrapped = new RegisteredObjectWrapper(registeredObject); + if (!_registeredObjects.TryAdd(registeredObject, wrapped)) + { + throw new InvalidOperationException("Could not register object"); + } + HostingEnvironment.RegisterObject(wrapped); + } + + public void UnregisterObject(IRegisteredObject registeredObject) + { + if (_registeredObjects.TryGetValue(registeredObject, out var wrapped)) + { + HostingEnvironment.UnregisterObject(wrapped); + } + } + + private class RegisteredObjectWrapper : System.Web.Hosting.IRegisteredObject + { + private readonly IRegisteredObject _inner; + + public RegisteredObjectWrapper(IRegisteredObject inner) + { + _inner = inner; + } + + public void Stop(bool immediate) + { + _inner.Stop(immediate); + } + } + } +} diff --git a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs index aa07894aa9..a61ad356d5 100644 --- a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs +++ b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs @@ -1,21 +1,15 @@ using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Web; using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; -using IRegisteredObject = Umbraco.Core.IRegisteredObject; namespace Umbraco.Web.Hosting { public class AspNetHostingEnvironment : IHostingEnvironment { - private readonly ConcurrentDictionary _registeredObjects = - new ConcurrentDictionary(); + private readonly IHostingSettings _hostingSettings; private string _localTempPath; @@ -46,23 +40,7 @@ namespace Umbraco.Web.Hosting } public string ToAbsolute(string virtualPath, string root) => VirtualPathUtility.ToAbsolute(virtualPath, root); - public void RegisterObject(IRegisteredObject registeredObject) - { - var wrapped = new RegisteredObjectWrapper(registeredObject); - if (!_registeredObjects.TryAdd(registeredObject, wrapped)) - { - throw new InvalidOperationException("Could not register object"); - } - HostingEnvironment.RegisterObject(wrapped); - } - - public void UnregisterObject(IRegisteredObject registeredObject) - { - if (_registeredObjects.TryGetValue(registeredObject, out var wrapped)) - { - HostingEnvironment.UnregisterObject(wrapped); - } - } + public string LocalTempPath { @@ -101,20 +79,7 @@ namespace Umbraco.Web.Hosting } } } - private class RegisteredObjectWrapper : System.Web.Hosting.IRegisteredObject - { - private readonly IRegisteredObject _inner; - - public RegisteredObjectWrapper(IRegisteredObject inner) - { - _inner = inner; - } - - public void Stop(bool immediate) - { - _inner.Stop(immediate); - } - } + } diff --git a/src/Umbraco.Web/AspNet/AspNetSessionManager.cs b/src/Umbraco.Web/AspNet/AspNetSessionManager.cs index bf7b1c05c3..f81daadb0a 100644 --- a/src/Umbraco.Web/AspNet/AspNetSessionManager.cs +++ b/src/Umbraco.Web/AspNet/AspNetSessionManager.cs @@ -1,6 +1,6 @@ using System.Web; -using Umbraco.Core.Session; using Umbraco.Net; +using Umbraco.Core.Session; namespace Umbraco.Web.AspNet { diff --git a/src/Umbraco.Web/Compose/AuditEventsComponent.cs b/src/Umbraco.Web/Compose/AuditEventsComponent.cs index fba6e754c8..bd2520aa90 100644 --- a/src/Umbraco.Web/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/AuditEventsComponent.cs @@ -7,10 +7,10 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Net; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -using Umbraco.Net; using Umbraco.Web.Security; namespace Umbraco.Core.Compose diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index eec3dcd1e3..70083d47dd 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Mapping; +using Umbraco.Net; using Umbraco.Core.PackageActions; using Umbraco.Core.Packaging; using Umbraco.Core.PropertyEditors; @@ -18,7 +19,6 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; -using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.Cache; using Umbraco.Web.Editors; diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 672b47f1eb..fe552650ea 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -14,11 +14,11 @@ using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Packaging; +using Umbraco.Net; using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Net; using Umbraco.Web.JavaScript; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.Web/Profiling/WebProfilingController.cs b/src/Umbraco.Web/Profiling/WebProfilingController.cs index b37a7eefb8..d8f71b5c85 100644 --- a/src/Umbraco.Web/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web/Profiling/WebProfilingController.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; @@ -18,7 +19,7 @@ namespace Umbraco.Web.Profiling [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] public class WebProfilingController : UmbracoAuthorizedJsonController { - private readonly IRuntimeState _runtimeState; + private readonly IHostingEnvironment _hosting; public WebProfilingController( IGlobalSettings globalSettings, @@ -30,17 +31,18 @@ namespace Umbraco.Web.Profiling IRuntimeState runtimeState, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IHostingEnvironment hosting) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) { - _runtimeState = runtimeState; + _hosting = hosting; } public object GetStatus() { return new { - Enabled = _runtimeState.Debug + Enabled = _hosting.IsDebugMode }; } }} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index b81cafdb40..2a746ffe4c 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -18,7 +18,6 @@ using Umbraco.Core.Runtime; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Sync; -using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.Cache; using Umbraco.Web.Composing.CompositionExtensions; @@ -46,6 +45,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.PropertyEditors; using Umbraco.Examine; using Umbraco.Core.Models; +using Umbraco.Net; using Umbraco.Core.Request; using Umbraco.Core.Session; using Umbraco.Web.AspNet; @@ -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/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs index 8fab9f4ea8..b3f950433c 100644 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs @@ -17,9 +17,9 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Identity; +using Umbraco.Net; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Web.Composing; using Umbraco.Web.Models.Identity; using Constants = Umbraco.Core.Constants; diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs index 354b9c2dde..7de7ba37e5 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -9,9 +9,9 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Identity; +using Umbraco.Net; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Web.Models.Identity; using IPasswordHasher = Microsoft.AspNet.Identity.IPasswordHasher; diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index d2eeebaeaf..b099110911 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -10,10 +10,10 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; +using Umbraco.Net; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Web.Composing; namespace Umbraco.Web.Security.Providers diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e8aaecdf4d..4b4f06ad2a 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -134,6 +134,7 @@ + @@ -585,4 +586,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 9f24da95e3..30d3fe5041 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connectionStrings, dbProviderFactoryCreator) : new MainDomSemaphoreLock(logger, hostingEnvironment); - var mainDom = new MainDom(logger, hostingEnvironment, mainDomLock); + var mainDom = new MainDom(logger, mainDomLock); var requestCache = new HttpRequestAppCache(() => HttpContext.Current?.Items); var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker(); diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index ce5f1304cd..0963ad3c07 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -152,7 +152,9 @@ namespace Umbraco.Web Umbraco.Composing.Current.Profiler, Umbraco.Composing.Current.HostingEnvironment, Umbraco.Composing.Current.BackOfficeInfo); - _factory = Current.Factory = _runtime.Boot(register); + _factory = Current.Factory = _runtime.Configure(register); + + _runtime.Start(); } // called by ASP.NET (auto event wireup) once per app domain diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 6f5fc26f90..9cc651f359 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -150,7 +150,7 @@ namespace Umbraco.Web { var request = GetRequestFromContext(); //NOTE: the request can be null during app startup! - return Current.RuntimeState.Debug + return Current.HostingEnvironment.IsDebugMode && request != null && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false || string.IsNullOrEmpty(request["umbdebug"]) == false diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index a69e6c6f8d..a0b7cdd225 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -7,8 +7,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Mapping; -using Umbraco.Core.Services; using Umbraco.Net; +using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Security; diff --git a/src/Umbraco.Web/UrlHelperExtensions.cs b/src/Umbraco.Web/UrlHelperExtensions.cs index a0bf5d3ded..f0ee309630 100644 --- a/src/Umbraco.Web/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/UrlHelperExtensions.cs @@ -143,12 +143,12 @@ namespace Umbraco.Web //in case the user bypasses the installer and just bumps the web.config or client dependency config //if in debug mode, always burst the cache - if (Current.RuntimeState.Debug) + if (Current.HostingEnvironment.IsDebugMode) { return DateTime.Now.Ticks.ToString(CultureInfo.InvariantCulture).GenerateHash(); } - var version = Current.RuntimeState.SemanticVersion.ToSemanticString(); + var version = Current.UmbracoVersion.SemanticVersion.ToSemanticString(); return $"{version}.{ClientDependencySettings.Instance.Version}".GenerateHash(); } }