diff --git a/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs b/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs
new file mode 100644
index 0000000000..422673a823
--- /dev/null
+++ b/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs
@@ -0,0 +1,16 @@
+namespace Umbraco.Core.Events
+{
+ public class UmbracoApplicationStarting : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runtime level
+ public UmbracoApplicationStarting(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel;
+
+ ///
+ /// Gets the runtime level of execution.
+ ///
+ public RuntimeLevel RuntimeLevel { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs b/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs
new file mode 100644
index 0000000000..bef6f0de19
--- /dev/null
+++ b/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs
@@ -0,0 +1,4 @@
+namespace Umbraco.Core.Events
+{
+ public class UmbracoApplicationStopping : INotification { }
+}
diff --git a/src/Umbraco.Core/Services/IRuntime.cs b/src/Umbraco.Core/Services/IRuntime.cs
index 8a1be721d0..d1254c219f 100644
--- a/src/Umbraco.Core/Services/IRuntime.cs
+++ b/src/Umbraco.Core/Services/IRuntime.cs
@@ -1,22 +1,15 @@
-using System;
+using Microsoft.Extensions.Hosting;
namespace Umbraco.Core
{
///
/// Defines the Umbraco runtime.
///
- public interface IRuntime
+ public interface IRuntime : IHostedService
{
///
/// Gets the runtime state.
///
IRuntimeState State { get; }
-
- void Start();
-
- ///
- /// Terminates the runtime.
- ///
- void Terminate();
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 300dedc1c6..24b2fae683 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
index 126d235ae0..e658405400 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -38,6 +38,7 @@ using Umbraco.Core.Templates;
using Umbraco.Examine;
using Umbraco.Infrastructure.Examine;
using Umbraco.Infrastructure.Media;
+using Umbraco.Infrastructure.Runtime;
using Umbraco.Web;
using Umbraco.Web.Actions;
using Umbraco.Web.Cache;
@@ -66,11 +67,12 @@ namespace Umbraco.Core.Runtime
{
// core's initial composer composes before all core composers
[ComposeBefore(typeof(ICoreComposer))]
- public class CoreInitialComposer : ComponentComposer
+ public class CoreInitialComposer : IComposer
{
- public override void Compose(IUmbracoBuilder builder)
+ ///
+ public void Compose(IUmbracoBuilder builder)
{
- base.Compose(builder);
+ builder.AddNotificationHandler();
// composers
builder
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index cb02a90ebe..55fa3457ed 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -1,7 +1,10 @@
-using System;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Composing;
+using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
@@ -10,71 +13,102 @@ namespace Umbraco.Infrastructure.Runtime
{
public class CoreRuntime : IRuntime
{
- public IRuntimeState State { get; }
-
private readonly ILogger _logger;
+ private readonly ILoggerFactory _loggerFactory;
private readonly ComponentCollection _components;
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
private readonly IProfilingLogger _profilingLogger;
private readonly IMainDom _mainDom;
private readonly IUmbracoDatabaseFactory _databaseFactory;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IHostingEnvironment _hostingEnvironment;
public CoreRuntime(
- ILogger logger,
+ ILoggerFactory loggerFactory,
IRuntimeState state,
ComponentCollection components,
IApplicationShutdownRegistry applicationShutdownRegistry,
IProfilingLogger profilingLogger,
IMainDom mainDom,
- IUmbracoDatabaseFactory databaseFactory)
+ IUmbracoDatabaseFactory databaseFactory,
+ IEventAggregator eventAggregator,
+ IHostingEnvironment hostingEnvironment)
{
State = state;
- _logger = logger;
+ _loggerFactory = loggerFactory;
_components = components;
_applicationShutdownRegistry = applicationShutdownRegistry;
_profilingLogger = profilingLogger;
_mainDom = mainDom;
_databaseFactory = databaseFactory;
- }
-
+ _eventAggregator = eventAggregator;
+ _hostingEnvironment = hostingEnvironment;
- public void Start()
+
+ _logger = _loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Gets the state of the Umbraco runtime.
+ ///
+ public IRuntimeState State { get; }
+
+ ///
+ public async Task StartAsync(CancellationToken cancellationToken)
{
+ StaticApplicationLogging.Initialize(_loggerFactory);
+
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
var exception = (Exception)args.ExceptionObject;
var isTerminating = args.IsTerminating; // always true?
var msg = "Unhandled exception in AppDomain";
- if (isTerminating) msg += " (terminating)";
+
+ if (isTerminating)
+ {
+ msg += " (terminating)";
+ }
+
msg += ".";
+
_logger.LogError(exception, msg);
};
+ AppDomain.CurrentDomain.SetData("DataDirectory", _hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
+
DetermineRuntimeLevel();
if (State.Level <= RuntimeLevel.BootFailed)
+ {
throw new InvalidOperationException($"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}");
+ }
- var hostingEnvironmentLifetime = _applicationShutdownRegistry;
+ IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
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)}");
+ {
+ 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(StartAsync)}");
+ }
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
+ await _eventAggregator.PublishAsync(new UmbracoApplicationStarting(State.Level), cancellationToken);
+
// create & initialize the components
_components.Initialize();
}
- public void Terminate()
+ public async Task StopAsync(CancellationToken cancellationToken)
{
_components.Terminate();
+ await _eventAggregator.PublishAsync(new UmbracoApplicationStopping(), cancellationToken);
+ StaticApplicationLogging.Initialize(null);
}
private void AcquireMainDom()
{
- using (var timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."))
+ using (DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."))
{
try
{
@@ -90,7 +124,7 @@ namespace Umbraco.Infrastructure.Runtime
private void DetermineRuntimeLevel()
{
- using var timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined.");
+ using DisposableTimer timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined.");
try
{
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs b/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
similarity index 67%
rename from src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs
rename to src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
index 73318208b6..5543662464 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs
+++ b/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
@@ -1,25 +1,28 @@
-using Microsoft.Extensions.Options;
-using Umbraco.Core.Composing;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
-namespace Umbraco.Core.Runtime
+namespace Umbraco.Infrastructure.Runtime
{
- public class CoreInitialComponent : IComponent
+ public class EssentialDirectoryCreator : INotificationHandler
{
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly GlobalSettings _globalSettings;
- public CoreInitialComponent(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings)
+ public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings)
{
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings.Value;
}
- public void Initialize()
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
// ensure we have some essential directories
// every other component can then initialize safely
@@ -28,9 +31,8 @@ namespace Umbraco.Core.Runtime
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials));
- }
- public void Terminate()
- { }
+ return Task.CompletedTask;
+ }
}
}
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
index 77ee27b8c4..1e3317262a 100644
--- a/src/Umbraco.Tests.Integration/RuntimeTests.cs
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -33,54 +33,6 @@ namespace Umbraco.Tests.Integration
MyComposer.Reset();
}
- ///
- /// Calling AddUmbracoCore to configure the container
- ///
- [Test]
- public async Task AddUmbracoCore()
- {
- var testHelper = new TestHelper();
-
- var hostBuilder = new HostBuilder()
- .UseUmbraco()
- .ConfigureServices((hostContext, services) =>
- {
- var webHostEnvironment = testHelper.GetWebHostEnvironment();
- services.AddSingleton(testHelper.DbProviderFactoryCreator);
- services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
-
- // Add it!
- var typeLoader = services.AddTypeLoader(
- GetType().Assembly,
- webHostEnvironment,
- testHelper.GetHostingEnvironment(),
- testHelper.ConsoleLoggerFactory,
- AppCaches.NoCache,
- hostContext.Configuration,
- testHelper.Profiler);
-
- var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, testHelper.ConsoleLoggerFactory);
- builder.Services.AddUnique(AppCaches.NoCache);
- builder.AddConfiguration();
- builder.AddUmbracoCore();
- });
-
- var host = await hostBuilder.StartAsync();
- var app = new ApplicationBuilder(host.Services);
-
- // assert results
- 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.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
///
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
index 93769eaaed..c3c01ed977 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
@@ -1,4 +1,4 @@
-
+
using System;
using System.Linq.Expressions;
using System.Net.Http;
@@ -119,15 +119,6 @@ namespace Umbraco.Tests.Integration.TestServerTest
protected LinkGenerator LinkGenerator { get; private set; }
protected WebApplicationFactory Factory { get; private set; }
- [TearDown]
- public override void TearDown()
- {
- base.TearDown();
- base.TerminateCoreRuntime();
-
- Factory.Dispose();
- }
-
#region IStartup
public override void ConfigureServices(IServiceCollection services)
@@ -162,7 +153,5 @@ namespace Umbraco.Tests.Integration.TestServerTest
}
#endregion
-
-
}
}
diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
index 43b2d236c7..f72139576e 100644
--- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
+++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
@@ -71,9 +71,14 @@ namespace Umbraco.Tests.Integration.Testing
foreach (var a in _testTeardown)
a();
}
+
_testTeardown = null;
FirstTestInFixture = false;
FirstTestInSession = false;
+
+ // Ensure CoreRuntime stopped (now it's a HostedService)
+ IHost host = Services.GetRequiredService();
+ host.StopAsync().GetAwaiter().GetResult();
}
[TearDown]
@@ -100,8 +105,6 @@ namespace Umbraco.Tests.Integration.Testing
var app = new ApplicationBuilder(host.Services);
Configure(app);
-
- OnFixtureTearDown(() => host.Dispose());
}
#region Generic Host Builder and Runtime
@@ -135,6 +138,8 @@ namespace Umbraco.Tests.Integration.Testing
///
public virtual IHostBuilder CreateHostBuilder()
{
+
+ var testOptions = TestOptionAttributeBase.GetTestOptions();
var hostBuilder = Host.CreateDefaultBuilder()
// IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work
// if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will
@@ -154,6 +159,12 @@ namespace Umbraco.Tests.Integration.Testing
{
services.AddTransient(_ => CreateLoggerFactory());
ConfigureServices(services);
+
+ if (!testOptions.Boot)
+ {
+ // If boot is false, we don't want the CoreRuntime hosted service to start
+ services.AddUnique(Mock.Of());
+ }
});
return hostBuilder;
}
@@ -215,27 +226,9 @@ namespace Umbraco.Tests.Integration.Testing
{
UseTestLocalDb(app.ApplicationServices);
- //get the currently set options
- var testOptions = TestOptionAttributeBase.GetTestOptions();
- if (testOptions.Boot)
- {
- Services.GetRequiredService().EnsureBackOfficeSecurity();
- Services.GetRequiredService().EnsureUmbracoContext();
- app.UseUmbracoCore(); // Takes 200 ms
-
- OnTestTearDown(TerminateCoreRuntime);
- }
- }
-
- ///
- /// Some IComponents hook onto static events (e.g. Published in ContentService)
- /// If these fire after the components host has been shutdown, errors can occur.
- /// If CoreRuntime.Start() is called We also need to de-register the events.
- ///
- protected void TerminateCoreRuntime()
- {
- Services.GetRequiredService().Terminate();
- StaticApplicationLogging.Initialize(null);
+ Services.GetRequiredService().EnsureBackOfficeSecurity();
+ Services.GetRequiredService().EnsureUmbracoContext();
+ app.UseUmbracoCore();
}
#endregion
diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
index 17c91d433a..4e528bafda 100644
--- a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
@@ -143,11 +143,13 @@ namespace Umbraco.Core.DependencyInjection
builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase());
builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext);
builder.Services.AddUnique();
- builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddHostedService(factory => factory.GetRequiredService());
+
builder.AddComposers();
return builder;
diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
index caf4132664..a245b8121a 100644
--- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
@@ -1,13 +1,11 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
using Serilog.Context;
using Smidge;
using Smidge.Nuglify;
using StackExchange.Profiling;
using Umbraco.Core;
-using Umbraco.Core.Hosting;
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
using Umbraco.Web.Common.Middleware;
@@ -23,9 +21,9 @@ namespace Umbraco.Extensions
///
public static bool UmbracoCanBoot(this IApplicationBuilder app)
{
- var runtime = app.ApplicationServices.GetRequiredService();
+ var state = app.ApplicationServices.GetRequiredService();
// can't continue if boot failed
- return runtime.State.Level > RuntimeLevel.BootFailed;
+ return state.Level > RuntimeLevel.BootFailed;
}
///
@@ -39,25 +37,10 @@ namespace Umbraco.Extensions
if (!app.UmbracoCanBoot()) return app;
- var hostingEnvironment = app.ApplicationServices.GetRequiredService();
- AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
-
- var runtime = app.ApplicationServices.GetRequiredService();
-
- // Register a listener for application shutdown in order to terminate the runtime
- var hostLifetime = app.ApplicationServices.GetRequiredService();
- var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime);
- hostLifetime.RegisterObject(runtimeShutdown);
-
// Register our global threadabort enricher for logging
var threadAbortEnricher = app.ApplicationServices.GetRequiredService();
LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context
- StaticApplicationLogging.Initialize(app.ApplicationServices.GetRequiredService());
-
- // Start the runtime!
- runtime.Start();
-
return app;
}
@@ -121,33 +104,6 @@ namespace Umbraco.Extensions
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/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs
index f10f4491d9..bdf5b40a02 100644
--- a/src/Umbraco.Web/UmbracoApplicationBase.cs
+++ b/src/Umbraco.Web/UmbracoApplicationBase.cs
@@ -46,7 +46,6 @@ namespace Umbraco.Web
protected UmbracoApplicationBase()
{
-
HostingSettings hostingSettings = null;
GlobalSettings globalSettings = null;
SecuritySettings securitySettings = null;
@@ -60,8 +59,6 @@ namespace Umbraco.Web
var backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, ioHelper, _loggerFactory.CreateLogger(), Options.Create(webRoutingSettings));
var profiler = GetWebProfiler(hostingEnvironment);
- StaticApplicationLogging.Initialize(_loggerFactory);
- Logger = NullLogger.Instance;
}
private IProfiler GetWebProfiler(IHostingEnvironment hostingEnvironment)
@@ -87,7 +84,6 @@ namespace Umbraco.Web
_loggerFactory = loggerFactory;
Logger = logger;
- StaticApplicationLogging.Initialize(_loggerFactory);
}
protected ILogger Logger { get; }
@@ -189,7 +185,6 @@ namespace Umbraco.Web
LogContext.Push(new HttpRequestIdEnricher(_factory.GetRequiredService()));
_runtime = _factory.GetRequiredService();
- _runtime.Start();
}
// called by ASP.NET (auto event wireup) once per app domain
@@ -237,7 +232,6 @@ namespace Umbraco.Web
{
if (_runtime != null)
{
- _runtime.Terminate();
_runtime.DisposeIfDisposable();
_runtime = null;