Move to Minimal Hosting Model in a backwards compatible way (#14656)
* Use minimal hosting model * Make CoreRuntime backward compatible to the old hosting model * Remove unneccessary methods from interface again * Pushed the timeout for E2E test to 120 minutes instead of 60 * Updated the preview version from 6 to 7 * Explicitly call BootUmbracoAsync * Add CreateUmbracoBuilder extension method * Do not add IRuntime as hosted service when using WebApplication/WebApplicationBuilder * Set StaticServiceProvider.Instance before booting * Ensure Umbraco is booted and StaticServiceProvider.Instance is set before configuring middleware * Do not enable static web assets on production environments * Removed root namespace from viewImports --------- Co-authored-by: Andreas Zerbst <andr317c@live.dk> Co-authored-by: Ronald Barendse <ronald@barend.se>
This commit is contained in:
@@ -335,6 +335,7 @@ stages:
|
||||
# E2E Tests
|
||||
- job:
|
||||
displayName: E2E Tests
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
Umbraco__CMS__Unattended__UnattendedUserName: Playwright Test
|
||||
Umbraco__CMS__Unattended__UnattendedUserPassword: UmbracoAcceptance123!
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Exceptions;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
@@ -16,6 +15,7 @@ using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Extensions;
|
||||
using ComponentCollection = Umbraco.Cms.Core.Composing.ComponentCollection;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Runtime;
|
||||
|
||||
@@ -152,12 +152,6 @@ public class CoreRuntime : IRuntime
|
||||
// Store token, so we can re-use this during restart
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
// Just in-case HostBuilder.ConfigureUmbracoDefaults() isn't used (e.g. upgrade from 9 and ignored advice).
|
||||
if (StaticServiceProvider.Instance == null!)
|
||||
{
|
||||
StaticServiceProvider.Instance = _serviceProvider!;
|
||||
}
|
||||
|
||||
if (isRestarting == false)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, args)
|
||||
@@ -170,8 +164,8 @@ public class CoreRuntime : IRuntime
|
||||
AcquireMainDom();
|
||||
|
||||
// Notify for unattended install
|
||||
await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken);
|
||||
DetermineRuntimeLevel();
|
||||
await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken);
|
||||
DetermineRuntimeLevel();
|
||||
|
||||
if (!State.UmbracoCanBoot())
|
||||
{
|
||||
@@ -182,8 +176,7 @@ public class CoreRuntime : IRuntime
|
||||
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(StartAsync)}");
|
||||
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)}");
|
||||
}
|
||||
|
||||
// If level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade
|
||||
@@ -194,8 +187,7 @@ public class CoreRuntime : IRuntime
|
||||
case RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors:
|
||||
if (State.BootFailedException == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered");
|
||||
throw new InvalidOperationException($"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered");
|
||||
}
|
||||
|
||||
// We cannot continue here, the exception will be rethrown by BootFailedMiddelware
|
||||
@@ -210,33 +202,29 @@ public class CoreRuntime : IRuntime
|
||||
}
|
||||
|
||||
// Initialize the components
|
||||
_components.Initialize();
|
||||
_components.Initialize();
|
||||
|
||||
await _eventAggregator.PublishAsync(
|
||||
new UmbracoApplicationStartingNotification(State.Level, isRestarting),
|
||||
cancellationToken);
|
||||
await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level, isRestarting), cancellationToken);
|
||||
|
||||
if (isRestarting == false)
|
||||
{
|
||||
// Add application started and stopped notifications last (to ensure they're always published after starting)
|
||||
_hostApplicationLifetime?.ApplicationStarted.Register(() =>
|
||||
_eventAggregator.Publish(new UmbracoApplicationStartedNotification(false)));
|
||||
_hostApplicationLifetime?.ApplicationStopped.Register(() =>
|
||||
_eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false)));
|
||||
_hostApplicationLifetime?.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false)));
|
||||
_hostApplicationLifetime?.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StopAsync(CancellationToken cancellationToken, bool isRestarting)
|
||||
{
|
||||
_components.Terminate();
|
||||
await _eventAggregator.PublishAsync(
|
||||
new UmbracoApplicationStoppingNotification(isRestarting),
|
||||
cancellationToken);
|
||||
await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(isRestarting), cancellationToken);
|
||||
}
|
||||
|
||||
private void AcquireMainDom()
|
||||
{
|
||||
using DisposableTimer? timer = !_profilingLogger.IsEnabled(Core.Logging.LogLevel.Debug) ? null : _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired.");
|
||||
using DisposableTimer? timer = !_profilingLogger.IsEnabled(LogLevel.Debug)
|
||||
? null
|
||||
: _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired.");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -257,8 +245,9 @@ public class CoreRuntime : IRuntime
|
||||
return;
|
||||
}
|
||||
|
||||
using DisposableTimer? timer = !_profilingLogger.IsEnabled(Core.Logging.LogLevel.Debug) ? null :
|
||||
_profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
|
||||
using DisposableTimer? timer = !_profilingLogger.IsEnabled(LogLevel.Debug)
|
||||
? null
|
||||
: _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -274,6 +263,7 @@ public class CoreRuntime : IRuntime
|
||||
{
|
||||
_logger.LogDebug("Configure database factory for upgrades.");
|
||||
}
|
||||
|
||||
_databaseFactory.ConfigureForUpgrade();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +142,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
sp,
|
||||
sp.GetRequiredService<IApplicationDiscriminator>()));
|
||||
|
||||
builder.Services.AddHostedService(factory => factory.GetRequiredService<IRuntime>());
|
||||
|
||||
builder.Services.AddSingleton<DatabaseSchemaCreatorFactory>();
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor
|
||||
.Singleton<IDatabaseProviderMetadata, CustomConnectionStringDatabaseProviderMetadata>());
|
||||
|
||||
@@ -9,6 +9,8 @@ using Serilog.Context;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Exceptions;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
@@ -30,7 +32,21 @@ public static class ApplicationBuilderExtensions
|
||||
/// Configures and use services required for using Umbraco
|
||||
/// </summary>
|
||||
public static IUmbracoApplicationBuilder UseUmbraco(this IApplicationBuilder app)
|
||||
=> new UmbracoApplicationBuilder(app);
|
||||
{
|
||||
// Ensure Umbraco is booted and StaticServiceProvider.Instance is set before continuing
|
||||
IRuntimeState runtimeState = app.ApplicationServices.GetRequiredService<IRuntimeState>();
|
||||
if (runtimeState.Level == RuntimeLevel.Unknown)
|
||||
{
|
||||
throw new BootFailedException("The runtime level is unknown, please make sure Umbraco is booted by adding `await app.BootUmbracoAsync();` just after `WebApplication app = builder.Build();` in your Program.cs file.");
|
||||
}
|
||||
|
||||
if (StaticServiceProvider.Instance is null)
|
||||
{
|
||||
throw new BootFailedException("StaticServiceProvider.Instance is not set, please make sure ConfigureUmbracoDefaults() is added in your Program.cs file.");
|
||||
}
|
||||
|
||||
return new UmbracoApplicationBuilder(app);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if Umbraco <see cref="IRuntimeState" /> is greater than <see cref="RuntimeLevel.BootFailed" />
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="WebApplicationBuilder" />.
|
||||
/// </summary>
|
||||
public static class WebApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IUmbracoBuilder" /> and registers basic Umbraco services.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
/// <returns>
|
||||
/// The Umbraco builder.
|
||||
/// </returns>
|
||||
public static IUmbracoBuilder CreateUmbracoBuilder(this WebApplicationBuilder builder)
|
||||
{
|
||||
// Configure Umbraco defaults, but ignore decorated host builder and
|
||||
// don't add runtime as hosted service (this is replaced by the explicit BootUmbracoAsync)
|
||||
builder.Host.ConfigureUmbracoDefaults(false);
|
||||
|
||||
// Do not enable static web assets on production environments,
|
||||
// because the files are already copied to the publish output folder.
|
||||
if (builder.Configuration.GetRuntimeMode() != RuntimeMode.Production)
|
||||
{
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
}
|
||||
|
||||
return builder.Services.AddUmbraco(builder.Environment, builder.Configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="WebApplication" />.
|
||||
/// </summary>
|
||||
public static class WebApplicationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the <see cref="IRuntime" /> to ensure Umbraco is ready for middleware to be added.
|
||||
/// </summary>
|
||||
/// <param name="app">The application.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public static async Task BootUmbracoAsync(this WebApplication app)
|
||||
{
|
||||
// Set static IServiceProvider before booting
|
||||
StaticServiceProvider.Instance = app.Services;
|
||||
|
||||
// Ensure the Umbraco runtime is started before middleware is added and stopped when performing a graceful shutdown
|
||||
IRuntime umbracoRuntime = app.Services.GetRequiredService<IRuntime>();
|
||||
CancellationTokenRegistration cancellationTokenRegistration = app.Lifetime.ApplicationStopping.Register((_, token) => umbracoRuntime.StopAsync(token), null);
|
||||
|
||||
await umbracoRuntime.StartAsync(cancellationTokenRegistration.Token);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Hosting;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
@@ -15,6 +17,9 @@ public static class HostBuilderExtensions
|
||||
/// Configures an existing <see cref="IHostBuilder" /> with defaults for an Umbraco application.
|
||||
/// </summary>
|
||||
public static IHostBuilder ConfigureUmbracoDefaults(this IHostBuilder builder)
|
||||
=> builder.ConfigureUmbracoDefaults(true);
|
||||
|
||||
internal static IHostBuilder ConfigureUmbracoDefaults(this IHostBuilder builder, bool addRuntimeHostedService)
|
||||
{
|
||||
#if DEBUG
|
||||
builder.ConfigureAppConfiguration(config
|
||||
@@ -26,10 +31,16 @@ public static class HostBuilderExtensions
|
||||
#endif
|
||||
builder.ConfigureLogging(x => x.ClearProviders());
|
||||
|
||||
if (addRuntimeHostedService)
|
||||
{
|
||||
// Add the Umbraco IRuntime as hosted service
|
||||
builder.ConfigureServices(services => services.AddHostedService(factory => factory.GetRequiredService<IRuntime>()));
|
||||
}
|
||||
|
||||
return new UmbracoHostBuilderDecorator(builder, OnHostBuilt);
|
||||
}
|
||||
|
||||
// Runs before any IHostedService starts (including generic web host).
|
||||
// Runs before any IHostedService starts (including generic web host)
|
||||
private static void OnHostBuilt(IHost host) =>
|
||||
StaticServiceProvider.Instance = host.Services;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
namespace Umbraco.Cms.Web.UI
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
=> CreateHostBuilder(args)
|
||||
.Build()
|
||||
.Run();
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureUmbracoDefaults()
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStaticWebAssets();
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
builder.CreateUmbracoBuilder()
|
||||
.AddBackOffice()
|
||||
.AddWebsite()
|
||||
.AddDeliveryApi()
|
||||
.AddComposers()
|
||||
.Build();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
await app.BootUmbracoAsync();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
#if (UseHttpsRedirect)
|
||||
app.UseHttpsRedirection();
|
||||
#endif
|
||||
|
||||
app.UseUmbraco()
|
||||
.WithMiddleware(u =>
|
||||
{
|
||||
u.UseBackOffice();
|
||||
u.UseWebsite();
|
||||
})
|
||||
.WithEndpoints(u =>
|
||||
{
|
||||
u.UseInstallerEndpoints();
|
||||
u.UseBackOfficeEndpoints();
|
||||
u.UseWebsiteEndpoints();
|
||||
});
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
namespace Umbraco.Cms.Web.UI
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Startup" /> class.
|
||||
/// </summary>
|
||||
/// <param name="webHostEnvironment">The web hosting environment.</param>
|
||||
/// <param name="config">The configuration.</param>
|
||||
/// <remarks>
|
||||
/// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337.
|
||||
/// </remarks>
|
||||
public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config)
|
||||
{
|
||||
_env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the services.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddUmbraco(_env, _config)
|
||||
.AddBackOffice()
|
||||
.AddWebsite()
|
||||
.AddDeliveryApi()
|
||||
.AddComposers()
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application.
|
||||
/// </summary>
|
||||
/// <param name="app">The application builder.</param>
|
||||
/// <param name="env">The web hosting environment.</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
#if (UseHttpsRedirect)
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
#endif
|
||||
|
||||
app.UseUmbraco()
|
||||
.WithMiddleware(u =>
|
||||
{
|
||||
u.UseBackOffice();
|
||||
u.UseWebsite();
|
||||
})
|
||||
.WithEndpoints(u =>
|
||||
{
|
||||
u.UseInstallerEndpoints();
|
||||
u.UseBackOfficeEndpoints();
|
||||
u.UseWebsiteEndpoints();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@using Umbraco.Extensions
|
||||
@using Umbraco.Cms.Web.UI
|
||||
@using Umbraco.Cms.Web.Common.PublishedModels
|
||||
@using Umbraco.Cms.Web.Common.Views
|
||||
@using Umbraco.Cms.Core.Models.PublishedContent
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
<Link>UmbracoProject\Program.cs</Link>
|
||||
<PackagePath>UmbracoProject</PackagePath>
|
||||
</Content>
|
||||
<Content Include="..\src\Umbraco.Web.UI\Startup.cs">
|
||||
<Link>UmbracoProject\Startup.cs</Link>
|
||||
<PackagePath>UmbracoProject</PackagePath>
|
||||
</Content>
|
||||
<Content Include="..\src\Umbraco.Web.UI\Views\Partials\blocklist\**">
|
||||
<Link>UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<PackagePath>UmbracoProject\Views\Partials\blocklist</PackagePath>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Build
|
||||
############################################
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0.100-preview.6-jammy AS build
|
||||
FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0.100-preview.7-jammy AS build
|
||||
|
||||
COPY nuget.config .
|
||||
|
||||
|
||||
Reference in New Issue
Block a user