Merge remote-tracking branch 'origin/v9/dev' into v9/bugfix/Refractor_UmbracoContextAccessor

This commit is contained in:
Zeegaan
2021-08-11 14:01:00 +02:00
66 changed files with 1026 additions and 559 deletions

View File

@@ -1,22 +1,21 @@
using System;
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
public interface IUmbracoApplicationBuilder
{
/// <summary>
/// Called to include umbraco middleware
/// EXPERT call to replace the middlewares that Umbraco installs by default with a completely custom pipeline.
/// </summary>
/// <param name="configureUmbraco"></param>
/// <param name="configureUmbracoMiddleware"></param>
/// <returns></returns>
IUmbracoApplicationBuilder WithMiddleware(Action<IUmbracoMiddlewareBuilder> configureUmbraco);
IUmbracoEndpointBuilder WithCustomMiddleware(Action<IUmbracoApplicationBuilderContext> configureUmbracoMiddleware);
/// <summary>
/// Final call during app building to configure endpoints
/// Called to include default middleware to run umbraco.
/// </summary>
/// <param name="configureUmbraco"></param>
void WithEndpoints(Action<IUmbracoEndpointBuilder> configureUmbraco);
/// <param name="configureUmbracoMiddleware"></param>
/// <returns></returns>
IUmbracoEndpointBuilder WithMiddleware(Action<IUmbracoApplicationBuilderContext> configureUmbracoMiddleware);
}
}

View File

@@ -0,0 +1,33 @@
using System;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
/// <summary>
/// The context object used during
/// </summary>
public interface IUmbracoApplicationBuilderContext : IUmbracoApplicationBuilderServices
{
/// <summary>
/// Called to include the core umbraco middleware.
/// </summary>
void UseUmbracoCoreMiddleware();
/// <summary>
/// Manually runs the <see cref="IUmbracoPipelineFilter"/> pre pipeline filters
/// </summary>
void RunPrePipeline();
/// <summary>
/// Manually runs the <see cref="IUmbracoPipelineFilter "/> post pipeline filters
/// </summary>
void RunPostPipeline();
/// <summary>
/// Called to include all of the default umbraco required middleware.
/// </summary>
/// <remarks>
/// If using this method, there is no need to use <see cref="UseUmbracoCoreMiddleware"/>
/// </remarks>
void RegisterDefaultRequiredMiddleware();
}
}

View File

@@ -1,13 +1,16 @@
using System;
using System;
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
public interface IUmbracoMiddlewareBuilder
/// <summary>
/// Services used during the Umbraco building phase.
/// </summary>
public interface IUmbracoApplicationBuilderServices
{
IRuntimeState RuntimeState { get; }
IServiceProvider ApplicationServices { get; }
IApplicationBuilder AppBuilder { get; }
IServiceProvider ApplicationServices { get; }
IRuntimeState RuntimeState { get; }
}
}

View File

@@ -1,13 +1,13 @@
using Microsoft.AspNetCore.Routing;
using System;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
/// <summary>
/// A builder to allow encapsulating the enabled routing features in Umbraco
/// </summary>
public interface IUmbracoEndpointBuilder : IUmbracoMiddlewareBuilder
{
IEndpointRouteBuilder EndpointRouteBuilder { get; }
public interface IUmbracoEndpointBuilder
{
/// <summary>
/// Final call during app building to configure endpoints
/// </summary>
/// <param name="configureUmbraco"></param>
void WithEndpoints(Action<IUmbracoEndpointBuilderContext> configureUmbraco);
}
}

View File

@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Routing;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
/// <summary>
/// A builder to allow encapsulating the enabled routing features in Umbraco
/// </summary>
public interface IUmbracoEndpointBuilderContext : IUmbracoApplicationBuilderServices
{
IEndpointRouteBuilder EndpointRouteBuilder { get; }
}
}

View File

@@ -2,61 +2,144 @@ 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
{
/// <summary>
/// A builder to allow encapsulating the enabled endpoints in Umbraco
/// A builder used to enable middleware and endpoints required for Umbraco to operate.
/// </summary>
internal class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoMiddlewareBuilder
/// <remarks>
/// This helps to ensure that everything is registered in the correct order.
/// </remarks>
public class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoEndpointBuilder, IUmbracoApplicationBuilderContext
{
public UmbracoApplicationBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder)
private readonly IOptions<UmbracoPipelineOptions> _umbracoPipelineStartupOptions;
public UmbracoApplicationBuilder(IApplicationBuilder appBuilder)
{
ApplicationServices = services;
RuntimeState = runtimeState;
AppBuilder = appBuilder;
AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder));
ApplicationServices = appBuilder.ApplicationServices;
RuntimeState = appBuilder.ApplicationServices.GetRequiredService<IRuntimeState>();
_umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
}
public IServiceProvider ApplicationServices { get; }
public IRuntimeState RuntimeState { get; }
public IApplicationBuilder AppBuilder { get; }
public IUmbracoApplicationBuilder WithMiddleware(Action<IUmbracoMiddlewareBuilder> configureUmbraco)
/// <inheritdoc />
public IUmbracoEndpointBuilder WithCustomMiddleware(Action<IUmbracoApplicationBuilderContext> configureUmbracoMiddleware)
{
IOptions<UmbracoPipelineOptions> startupOptions = ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
RunPostPipeline(startupOptions.Value);
if (configureUmbracoMiddleware is null)
{
throw new ArgumentNullException(nameof(configureUmbracoMiddleware));
}
configureUmbraco(this);
configureUmbracoMiddleware(this);
return this;
}
public void WithEndpoints(Action<IUmbracoEndpointBuilder> configureUmbraco)
/// <inheritdoc />
public IUmbracoEndpointBuilder WithMiddleware(Action<IUmbracoApplicationBuilderContext> configureUmbracoMiddleware)
{
if (configureUmbracoMiddleware is null)
{
throw new ArgumentNullException(nameof(configureUmbracoMiddleware));
}
RunPrePipeline();
RegisterDefaultRequiredMiddleware();
RunPostPipeline();
configureUmbracoMiddleware(this);
return this;
}
/// <inheritdoc />
public void WithEndpoints(Action<IUmbracoEndpointBuilderContext> configureUmbraco)
{
IOptions<UmbracoPipelineOptions> startupOptions = ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
RunPreEndpointsPipeline(startupOptions.Value);
RunPreEndpointsPipeline();
AppBuilder.UseEndpoints(endpoints =>
{
var umbAppBuilder = (IUmbracoEndpointBuilder)ActivatorUtilities.CreateInstance<UmbracoEndpointBuilder>(
var umbAppBuilder = (IUmbracoEndpointBuilderContext)ActivatorUtilities.CreateInstance<UmbracoEndpointBuilder>(
ApplicationServices,
new object[] { AppBuilder, endpoints });
configureUmbraco(umbAppBuilder);
});
}
private void RunPostPipeline(UmbracoPipelineOptions startupOptions)
/// <summary>
/// Registers the default required middleware to run Umbraco
/// </summary>
/// <param name="umbracoApplicationBuilderContext"></param>
public void RegisterDefaultRequiredMiddleware()
{
foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters)
UseUmbracoCoreMiddleware();
AppBuilder.UseStatusCodePages();
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
AppBuilder.UseImageSharp();
AppBuilder.UseStaticFiles();
AppBuilder.UseUmbracoPluginsStaticFiles();
// 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
}
public void UseUmbracoCoreMiddleware()
{
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();
}
public void RunPrePipeline()
{
foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
{
filter.OnPrePipeline(AppBuilder);
}
}
public void RunPostPipeline()
{
foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
{
filter.OnPostPipeline(AppBuilder);
}
}
private void RunPreEndpointsPipeline(UmbracoPipelineOptions startupOptions)
private void RunPreEndpointsPipeline()
{
foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters)
foreach (IUmbracoPipelineFilter filter in _umbracoPipelineStartupOptions.Value.PipelineFilters)
{
filter.OnEndpoints(AppBuilder);
}

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder
/// <summary>
/// A builder to allow encapsulating the enabled endpoints in Umbraco
/// </summary>
internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilder
internal class UmbracoEndpointBuilder : IUmbracoEndpointBuilderContext
{
public UmbracoEndpointBuilder(IServiceProvider services, IRuntimeState runtimeState, IApplicationBuilder appBuilder, IEndpointRouteBuilder endpointRouteBuilder)
{

View File

@@ -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
/// </summary>
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<UmbracoPipelineOptions> startupOptions = app.ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
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<UmbracoApplicationBuilder>(
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);
/// <summary>
/// Returns true if Umbraco <see cref="IRuntimeState"/> is greater than <see cref="RuntimeLevel.BootFailed"/>
@@ -158,7 +101,12 @@ namespace Umbraco.Extensions
return app;
}
public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app)
/// <summary>
/// Allow static file access for App_Plugins folders
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseUmbracoPluginsStaticFiles(this IApplicationBuilder app)
{
var hostingEnvironment = app.ApplicationServices.GetRequiredService<IHostingEnvironment>();
var umbracoPluginSettings = app.ApplicationServices.GetRequiredService<IOptions<UmbracoPluginSettings>>();

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.Common.Extensions
/// <summary>
/// Enables runtime minification for Umbraco
/// </summary>
public static IUmbracoEndpointBuilder UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilder app)
public static IUmbracoEndpointBuilderContext UseUmbracoRuntimeMinificationEndpoints(this IUmbracoEndpointBuilderContext app)
{
if (app == null)
{

View File

@@ -198,6 +198,9 @@ namespace Umbraco.Cms.Web.Common.Security
// code taken from aspnetcore: https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// we also override to set the current HttpContext principal since this isn't done by default
// we also need to call our handle login to ensure all date/events are set
await HandleSignIn(user, user.UserName, SignInResult.Success);
var userPrincipal = await CreateUserPrincipalAsync(user);
foreach (var claim in additionalClaims)
{
@@ -363,7 +366,7 @@ namespace Umbraco.Cms.Web.Common.Security
await Context.SignOutAsync(ExternalAuthenticationType);
}
if (loginProvider == null)
{
{
await SignInWithClaimsAsync(user, isPersistent, new Claim[] { new Claim("amr", "pwd") });
}
else