diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 0068b4d0eb..386b08a41d 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -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! diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 19c947481d..81929290cd 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -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("Acquiring MainDom.", "Acquired."); + using DisposableTimer? timer = !_profilingLogger.IsEnabled(LogLevel.Debug) + ? null + : _profilingLogger.DebugDuration("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("Determining runtime level.", "Determined."); + using DisposableTimer? timer = !_profilingLogger.IsEnabled(LogLevel.Debug) + ? null + : _profilingLogger.DebugDuration("Determining runtime level.", "Determined."); try { @@ -274,6 +263,7 @@ public class CoreRuntime : IRuntime { _logger.LogDebug("Configure database factory for upgrades."); } + _databaseFactory.ConfigureForUpgrade(); } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 0fa1d7fc4b..74977c9969 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -142,8 +142,6 @@ public static partial class UmbracoBuilderExtensions sp, sp.GetRequiredService())); - builder.Services.AddHostedService(factory => factory.GetRequiredService()); - builder.Services.AddSingleton(); builder.Services.TryAddEnumerable(ServiceDescriptor .Singleton()); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 08a22cfda3..244fe39a50 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -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 /// 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(); + 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); + } /// /// Returns true if Umbraco is greater than diff --git a/src/Umbraco.Web.Common/Extensions/WebApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/WebApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..d0040bfe47 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/WebApplicationBuilderExtensions.cs @@ -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; + +/// +/// Extension methods for . +/// +public static class WebApplicationBuilderExtensions +{ + /// + /// Creates an and registers basic Umbraco services. + /// + /// The builder. + /// + /// The Umbraco builder. + /// + 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); + } +} diff --git a/src/Umbraco.Web.Common/Extensions/WebApplicationExtensions.cs b/src/Umbraco.Web.Common/Extensions/WebApplicationExtensions.cs new file mode 100644 index 0000000000..69b3e7882d --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/WebApplicationExtensions.cs @@ -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; + +/// +/// Extension methods for . +/// +public static class WebApplicationExtensions +{ + /// + /// Starts the to ensure Umbraco is ready for middleware to be added. + /// + /// The application. + /// + /// A representing the asynchronous operation. + /// + 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(); + CancellationTokenRegistration cancellationTokenRegistration = app.Lifetime.ApplicationStopping.Register((_, token) => umbracoRuntime.StopAsync(token), null); + + await umbracoRuntime.StartAsync(cancellationTokenRegistration.Token); + } +} diff --git a/src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs b/src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs index 7d80f53980..6b2dafa240 100644 --- a/src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs @@ -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 with defaults for an Umbraco application. /// 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())); + } + 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; } diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs index cebb7b0370..780ac4d53e 100644 --- a/src/Umbraco.Web.UI/Program.cs +++ b/src/Umbraco.Web.UI/Program.cs @@ -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(); - }); - } +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(); diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs deleted file mode 100644 index 1d7f49b1e5..0000000000 --- a/src/Umbraco.Web.UI/Startup.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace Umbraco.Cms.Web.UI -{ - public class Startup - { - private readonly IWebHostEnvironment _env; - private readonly IConfiguration _config; - - /// - /// Initializes a new instance of the class. - /// - /// The web hosting environment. - /// The configuration. - /// - /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337. - /// - public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config) - { - _env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); - _config = config ?? throw new ArgumentNullException(nameof(config)); - } - - /// - /// Configures the services. - /// - /// The services. - /// - /// 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.AddUmbraco(_env, _config) - .AddBackOffice() - .AddWebsite() - .AddDeliveryApi() - .AddComposers() - .Build(); - } - - /// - /// Configures the application. - /// - /// The application builder. - /// The web hosting environment. - 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(); - }); - } - } -} diff --git a/src/Umbraco.Web.UI/Views/_ViewImports.cshtml b/src/Umbraco.Web.UI/Views/_ViewImports.cshtml index 2d6f535107..91d671eaa1 100644 --- a/src/Umbraco.Web.UI/Views/_ViewImports.cshtml +++ b/src/Umbraco.Web.UI/Views/_ViewImports.cshtml @@ -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 diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 38848d398a..5413f1ee0f 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -19,10 +19,6 @@ UmbracoProject\Program.cs UmbracoProject - - UmbracoProject\Startup.cs - UmbracoProject - UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension) UmbracoProject\Views\Partials\blocklist diff --git a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker b/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker index 9fcdb4f4ba..2844f7bdc7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker +++ b/tests/Umbraco.Tests.AcceptanceTest/misc/umbraco-linux.docker @@ -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 .