diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 59b970ebbc..71b9ac3ef9 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -181,8 +181,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest app.UseUmbraco() .WithMiddleware(u => { - u.WithBackOffice(); - u.WithWebsite(); + u.UseBackOffice(); + u.UseWebsite(); }) .WithEndpoints(u => { diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs index 59cefa0574..13a951b743 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.BackOffice.cs @@ -12,7 +12,7 @@ using Umbraco.Cms.Web.Common.Extensions; namespace Umbraco.Extensions { /// - /// extensions for Umbraco + /// extensions for Umbraco /// public static partial class UmbracoApplicationBuilderExtensions { @@ -21,7 +21,7 @@ namespace Umbraco.Extensions /// /// /// - public static IUmbracoMiddlewareBuilder WithBackOffice(this IUmbracoMiddlewareBuilder builder) + public static IUmbracoApplicationBuilderContext UseBackOffice(this IUmbracoApplicationBuilderContext builder) { KeepAliveSettings keepAliveSettings = builder.ApplicationServices.GetRequiredService>().Value; IHostingEnvironment hostingEnvironment = builder.ApplicationServices.GetRequiredService(); @@ -33,7 +33,7 @@ namespace Umbraco.Extensions return builder; } - public static IUmbracoEndpointBuilder UseBackOfficeEndpoints(this IUmbracoEndpointBuilder app) + public static IUmbracoEndpointBuilderContext UseBackOfficeEndpoints(this IUmbracoEndpointBuilderContext app) { // NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization if (app == null) diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs index 2be8c6bb28..d61b955efc 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Installer.cs @@ -13,7 +13,7 @@ namespace Umbraco.Extensions /// /// Enables the Umbraco installer /// - public static IUmbracoEndpointBuilder UseInstallerEndpoints(this IUmbracoEndpointBuilder app) + public static IUmbracoEndpointBuilderContext UseInstallerEndpoints(this IUmbracoEndpointBuilderContext app) { if (!app.RuntimeState.UmbracoCanBoot()) { diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs index 014f81fe8c..012205575a 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoApplicationBuilder.Preview.cs @@ -6,11 +6,11 @@ using Umbraco.Cms.Web.Common.ApplicationBuilder; namespace Umbraco.Extensions { /// - /// extensions for Umbraco + /// extensions for Umbraco /// public static partial class UmbracoApplicationBuilderExtensions { - public static IUmbracoEndpointBuilder UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilder app) + public static IUmbracoEndpointBuilderContext UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilderContext app) { PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService(); previewRoutes.CreateRoutes(app.EndpointRouteBuilder); diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs index 090ef52790..edb103c7c3 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilder.cs @@ -1,22 +1,21 @@ using System; -using Microsoft.AspNetCore.Builder; -using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { public interface IUmbracoApplicationBuilder { /// - /// Called to include umbraco middleware + /// EXPERT/OPTIONAL call to replace the default middlewares that Umbraco installs by default. /// - /// + /// /// - IUmbracoApplicationBuilder WithMiddleware(Action configureUmbraco); + IUmbracoApplicationBuilder WithCustomDefaultMiddleware(Action configureUmbracoMiddleware); /// - /// Final call during app building to configure endpoints + /// Called to include umbraco middleware /// - /// - void WithEndpoints(Action configureUmbraco); + /// + /// + IUmbracoEndpointBuilder WithMiddleware(Action configureUmbracoMiddleware); } } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs new file mode 100644 index 0000000000..23346e7aa8 --- /dev/null +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderContext.cs @@ -0,0 +1,10 @@ +using System; + +namespace Umbraco.Cms.Web.Common.ApplicationBuilder +{ + + public interface IUmbracoApplicationBuilderContext : IUmbracoApplicationBuilderServices + { + Action RegisterDefaultRequiredMiddleware { get; } + } +} diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs similarity index 80% rename from src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs rename to src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs index 78d7f28ab9..9df3e29a54 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoMiddlewareBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoApplicationBuilderServices.cs @@ -1,13 +1,13 @@ -using System; +using System; using Microsoft.AspNetCore.Builder; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { - public interface IUmbracoMiddlewareBuilder + public interface IUmbracoApplicationBuilderServices { - IRuntimeState RuntimeState { get; } - IServiceProvider ApplicationServices { get; } IApplicationBuilder AppBuilder { get; } + IServiceProvider ApplicationServices { get; } + IRuntimeState RuntimeState { get; } } } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs index 31507477ae..58e0b8fec2 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilder.cs @@ -1,13 +1,13 @@ -using Microsoft.AspNetCore.Routing; +using System; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { - - /// - /// A builder to allow encapsulating the enabled routing features in Umbraco - /// - public interface IUmbracoEndpointBuilder : IUmbracoMiddlewareBuilder - { - IEndpointRouteBuilder EndpointRouteBuilder { get; } + public interface IUmbracoEndpointBuilder + { + /// + /// Final call during app building to configure endpoints + /// + /// + void WithEndpoints(Action configureUmbraco); } } diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs new file mode 100644 index 0000000000..6122c9ef2e --- /dev/null +++ b/src/Umbraco.Web.Common/ApplicationBuilder/IUmbracoEndpointBuilderContext.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Routing; + +namespace Umbraco.Cms.Web.Common.ApplicationBuilder +{ + + /// + /// A builder to allow encapsulating the enabled routing features in Umbraco + /// + public interface IUmbracoEndpointBuilderContext : IUmbracoApplicationBuilderServices + { + IEndpointRouteBuilder EndpointRouteBuilder { get; } + } +} diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index b7acc45d22..9ea1bee0a1 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -2,50 +2,130 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.DependencyInjection; using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.ApplicationBuilder { /// /// A builder to allow encapsulating the enabled endpoints in Umbraco /// - internal class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoMiddlewareBuilder + public class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoEndpointBuilder, IUmbracoApplicationBuilderContext { - public UmbracoApplicationBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder) + private Action _customMiddlewareRegistration; + + public UmbracoApplicationBuilder(IApplicationBuilder appBuilder) { - ApplicationServices = services; - RuntimeState = runtimeState; - AppBuilder = appBuilder; + ApplicationServices = appBuilder.ApplicationServices; + RuntimeState = appBuilder.ApplicationServices.GetRequiredService(); + AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder)); + RegisterDefaultRequiredMiddleware = RegisterRequiredMiddleware; } public IServiceProvider ApplicationServices { get; } public IRuntimeState RuntimeState { get; } public IApplicationBuilder AppBuilder { get; } + public Action RegisterDefaultRequiredMiddleware { get; set; } - public IUmbracoApplicationBuilder WithMiddleware(Action configureUmbraco) + /// + public IUmbracoApplicationBuilder WithCustomDefaultMiddleware(Action configureUmbracoMiddleware) { + _customMiddlewareRegistration = configureUmbracoMiddleware; + return this; + } + + /// + public IUmbracoEndpointBuilder WithMiddleware(Action configureUmbracoMiddleware) + { + if (configureUmbracoMiddleware is null) + { + throw new ArgumentNullException(nameof(configureUmbracoMiddleware)); + } + IOptions startupOptions = ApplicationServices.GetRequiredService>(); + + RunPrePipeline(startupOptions.Value); + + if (_customMiddlewareRegistration != null) + { + _customMiddlewareRegistration(this); + } + else + { + RegisterRequiredMiddleware(); + } + RunPostPipeline(startupOptions.Value); - configureUmbraco(this); + configureUmbracoMiddleware(this); return this; } - public void WithEndpoints(Action configureUmbraco) + /// + public void WithEndpoints(Action configureUmbraco) { IOptions startupOptions = ApplicationServices.GetRequiredService>(); RunPreEndpointsPipeline(startupOptions.Value); AppBuilder.UseEndpoints(endpoints => { - var umbAppBuilder = (IUmbracoEndpointBuilder)ActivatorUtilities.CreateInstance( + var umbAppBuilder = (IUmbracoEndpointBuilderContext)ActivatorUtilities.CreateInstance( ApplicationServices, new object[] { AppBuilder, endpoints }); configureUmbraco(umbAppBuilder); }); } + /// + /// Registers the default required middleware to run Umbraco + /// + /// + private void RegisterRequiredMiddleware() + { + AppBuilder.UseUmbracoCore(); + AppBuilder.UseUmbracoRequestLogging(); + + // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed + // before endpoint routing middleware. + AppBuilder.UseUmbracoRouting(); + + AppBuilder.UseStatusCodePages(); + + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. + // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? + AppBuilder.UseImageSharp(); + AppBuilder.UseStaticFiles(); + AppBuilder.UseUmbracoPlugins(); + + // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one + // will execute after endpoint routing. The ordering of everything is quite important here, see + // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0 + // where we need to have UseAuthentication and UseAuthorization proceeding this call but before + // endpoints are defined. + AppBuilder.UseRouting(); + AppBuilder.UseAuthentication(); + AppBuilder.UseAuthorization(); + + // This must come after auth because the culture is based on the auth'd user + AppBuilder.UseRequestLocalization(); + + // Must be called after UseRouting and before UseEndpoints + AppBuilder.UseSession(); + + // DO NOT PUT ANY UseEndpoints declarations here!! Those must all come very last in the pipeline, + // endpoints are terminating middleware. All of our endpoints are declared in ext of IUmbracoApplicationBuilder + } + + private void RunPrePipeline(UmbracoPipelineOptions startupOptions) + { + foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters) + { + filter.OnPrePipeline(AppBuilder); + } + } + private void RunPostPipeline(UmbracoPipelineOptions startupOptions) { foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters) diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs index 56d856a22a..86e8f3e957 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoEndpointBuilder.cs @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder /// /// A builder to allow encapsulating the enabled endpoints in Umbraco /// - internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilder + internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilderContext { public UmbracoEndpointBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder, IEndpointRouteBuilder endpointRouteBuilder) { diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index efc95a59f7..e4ebdf3c0c 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.Context; -using SixLabors.ImageSharp.Web.DependencyInjection; using StackExchange.Profiling; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -26,63 +25,7 @@ namespace Umbraco.Extensions /// Configures and use services required for using Umbraco /// public static IUmbracoApplicationBuilder UseUmbraco(this IApplicationBuilder app) - { - // TODO: Should we do some checks like this to verify that the corresponding "Add" methods have been called for the - // corresponding "Use" methods? - // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Mvc/Mvc.Core/src/Builder/MvcApplicationBuilderExtensions.cs#L132 - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - IOptions startupOptions = app.ApplicationServices.GetRequiredService>(); - app.RunPrePipeline(startupOptions.Value); - - app.UseUmbracoCore(); - app.UseUmbracoRequestLogging(); - - // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed - // before endpoint routing middleware. - app.UseUmbracoRouting(); - - app.UseStatusCodePages(); - - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. - // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? - app.UseImageSharp(); - app.UseStaticFiles(); - app.UseUmbracoPlugins(); - - // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one - // will execute after endpoint routing. The ordering of everything is quite important here, see - // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0 - // where we need to have UseAuthentication and UseAuthorization proceeding this call but before - // endpoints are defined. - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - - // This must come after auth because the culture is based on the auth'd user - app.UseRequestLocalization(); - - // Must be called after UseRouting and before UseEndpoints - app.UseSession(); - - // DO NOT PUT ANY UseEndpoints declarations here!! Those must all come very last in the pipeline, - // endpoints are terminating middleware. All of our endpoints are declared in ext of IUmbracoApplicationBuilder - - return ActivatorUtilities.CreateInstance( - app.ApplicationServices, - new object[] { app }); - } - - private static void RunPrePipeline(this IApplicationBuilder app, UmbracoPipelineOptions startupOptions) - { - foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters) - { - filter.OnPrePipeline(app); - } - } + => new UmbracoApplicationBuilder(app); /// /// Returns true if Umbraco is greater than diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs index 0d8c7df72b..74b67c36a6 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoApplicationBuilder.RuntimeMinification.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.Common.Extensions /// /// Enables runtime minification for Umbraco /// - public static IUmbracoEndpointBuilder UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilder app) + public static IUmbracoEndpointBuilderContext UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilderContext app) { if (app == null) { diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 0419a8c0e4..73eeef864f 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -62,8 +62,8 @@ namespace Umbraco.Cms.Web.UI.NetCore app.UseUmbraco() .WithMiddleware(u => { - u.WithBackOffice(); - u.WithWebsite(); + u.UseBackOffice(); + u.UseWebsite(); }) .WithEndpoints(u => { diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs index 10cbbfdc4d..4be9f2f2bd 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoApplicationBuilder.Website.cs @@ -17,7 +17,7 @@ namespace Umbraco.Extensions /// /// /// - public static IUmbracoMiddlewareBuilder WithWebsite(this IUmbracoMiddlewareBuilder builder) + public static IUmbracoApplicationBuilderContext UseWebsite(this IUmbracoApplicationBuilderContext builder) { builder.AppBuilder.UseMiddleware(); return builder; @@ -26,7 +26,7 @@ namespace Umbraco.Extensions /// /// Sets up routes for the front-end umbraco website /// - public static IUmbracoEndpointBuilder UseWebsiteEndpoints(this IUmbracoEndpointBuilder builder) + public static IUmbracoEndpointBuilderContext UseWebsiteEndpoints(this IUmbracoEndpointBuilderContext builder) { if (builder == null) {