v10: Fix build warnings in Web.Common (#12349)
* Run code cleanup * Run dotnet format * Start manual cleanup in Web.Common * Finish up manual cleanup * Fix tests * Fix up InMemoryModelFactory.cs * Inject proper macroRenderer * Update src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Fix based on review Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk> Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -1,66 +1,78 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Exceptions;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
namespace Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Executes when Umbraco booting fails in order to show the problem
|
||||
/// </summary>
|
||||
public class BootFailedMiddleware : IMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes when Umbraco booting fails in order to show the problem
|
||||
/// </summary>
|
||||
public class BootFailedMiddleware : IMiddleware
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment)
|
||||
: this(runtimeState, hostingEnvironment, StaticServiceProvider.Instance.GetRequiredService<IWebHostEnvironment>())
|
||||
{
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment)
|
||||
public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment, IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// TODO: It would be possible to redirect to the installer here in debug mode while
|
||||
// still showing the error. This would be a lot more friendly than just the YSOD.
|
||||
// We could also then have a different installer view for when package migrations fails
|
||||
// and to retry each one individually. Perhaps this can happen in the future.
|
||||
if (_runtimeState.Level == RuntimeLevel.BootFailed)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// TODO: It would be possible to redirect to the installer here in debug mode while
|
||||
// still showing the error. This would be a lot more friendly than just the YSOD.
|
||||
// We could also then have a different installer view for when package migrations fails
|
||||
// and to retry each one individually. Perhaps this can happen in the future.
|
||||
|
||||
if (_runtimeState.Level == RuntimeLevel.BootFailed)
|
||||
// short circuit
|
||||
if (_hostingEnvironment.IsDebugMode)
|
||||
{
|
||||
// short circuit
|
||||
|
||||
if (_hostingEnvironment.IsDebugMode)
|
||||
{
|
||||
BootFailedException.Rethrow(_runtimeState.BootFailedException);
|
||||
}
|
||||
else // Print a nice error page
|
||||
{
|
||||
context.Response.Clear();
|
||||
context.Response.StatusCode = 500;
|
||||
|
||||
var file = GetBootErrorFileName();
|
||||
|
||||
var viewContent = await File.ReadAllTextAsync(file);
|
||||
await context.Response.WriteAsync(viewContent, Encoding.UTF8);
|
||||
}
|
||||
BootFailedException.Rethrow(_runtimeState.BootFailedException);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next(context);
|
||||
// Print a nice error page
|
||||
context.Response.Clear();
|
||||
context.Response.StatusCode = 500;
|
||||
|
||||
var file = GetBootErrorFileName();
|
||||
|
||||
var viewContent = await File.ReadAllTextAsync(file);
|
||||
await context.Response.WriteAsync(viewContent, Encoding.UTF8);
|
||||
}
|
||||
|
||||
}
|
||||
private string GetBootErrorFileName()
|
||||
else
|
||||
{
|
||||
var fileName = _hostingEnvironment.MapPathWebRoot("~/config/errors/BootFailed.html");
|
||||
if (File.Exists(fileName)) return fileName;
|
||||
|
||||
return _hostingEnvironment.MapPathWebRoot("~/umbraco/views/errors/BootFailed.html");
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetBootErrorFileName()
|
||||
{
|
||||
var fileName = _webHostEnvironment.MapPathWebRoot("~/config/errors/BootFailed.html");
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
return _webHostEnvironment.MapPathWebRoot("~/umbraco/views/errors/BootFailed.html");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -7,72 +7,73 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
namespace Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the
|
||||
/// principal alongside any default authentication that takes place
|
||||
/// </summary>
|
||||
public class PreviewAuthenticationMiddleware : IMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the principal alongside any default authentication that takes place
|
||||
/// </summary>
|
||||
public class PreviewAuthenticationMiddleware : IMiddleware
|
||||
private readonly ILogger<PreviewAuthenticationMiddleware> _logger;
|
||||
|
||||
public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
private readonly ILogger<PreviewAuthenticationMiddleware> _logger;
|
||||
HttpRequest request = context.Request;
|
||||
|
||||
public PreviewAuthenticationMiddleware(ILogger<PreviewAuthenticationMiddleware> logger) => _logger = logger;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
// do not process if client-side request
|
||||
if (request.IsClientSideRequest())
|
||||
{
|
||||
var request = context.Request;
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// do not process if client-side request
|
||||
if (request.IsClientSideRequest())
|
||||
try
|
||||
{
|
||||
var isPreview = request.HasPreviewCookie()
|
||||
&& !request.IsBackOfficeRequest();
|
||||
|
||||
if (isPreview)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
CookieAuthenticationOptions? cookieOptions = context.RequestServices
|
||||
.GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()
|
||||
.Get(Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
|
||||
try
|
||||
{
|
||||
var isPreview = request.HasPreviewCookie()
|
||||
&& context.User != null
|
||||
&& !request.IsBackOfficeRequest();
|
||||
|
||||
if (isPreview)
|
||||
if (cookieOptions == null)
|
||||
{
|
||||
var cookieOptions = context.RequestServices.GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>()
|
||||
.Get(Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
throw new InvalidOperationException("No cookie options found with name " +
|
||||
Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
}
|
||||
|
||||
if (cookieOptions == null)
|
||||
// If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.
|
||||
// In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication
|
||||
// for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.
|
||||
if (cookieOptions.Cookie.Name is not null &&
|
||||
request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie))
|
||||
{
|
||||
AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);
|
||||
ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity != null)
|
||||
{
|
||||
throw new InvalidOperationException("No cookie options found with name " + Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
// Ok, we've got a real ticket, now we can add this ticket's identity to the current
|
||||
// Principal, this means we'll have 2 identities assigned to the principal which we can
|
||||
// use to authorize the preview and allow for a back office User.
|
||||
context.User.AddIdentity(backOfficeIdentity);
|
||||
}
|
||||
|
||||
// If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing.
|
||||
// In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication
|
||||
// for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered.
|
||||
if (cookieOptions.Cookie.Name is not null && request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie))
|
||||
{
|
||||
var unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie);
|
||||
var backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity != null)
|
||||
{
|
||||
// Ok, we've got a real ticket, now we can add this ticket's identity to the current
|
||||
// Principal, this means we'll have 2 identities assigned to the principal which we can
|
||||
// use to authorize the preview and allow for a back office User.
|
||||
context.User?.AddIdentity(backOfficeIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// log any errors and continue the request without preview
|
||||
_logger.LogError($"Unable to perform preview authentication: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// log any errors and continue the request without preview
|
||||
_logger.LogError($"Unable to perform preview authentication: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog.Context;
|
||||
using Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds request based serilog enrichers to the LogContext for each request
|
||||
/// </summary>
|
||||
public class UmbracoRequestLoggingMiddleware : IMiddleware
|
||||
{
|
||||
private readonly HttpSessionIdEnricher _sessionIdEnricher;
|
||||
private readonly HttpRequestNumberEnricher _requestNumberEnricher;
|
||||
private readonly HttpRequestIdEnricher _requestIdEnricher;
|
||||
namespace Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
public UmbracoRequestLoggingMiddleware(
|
||||
HttpSessionIdEnricher sessionIdEnricher,
|
||||
HttpRequestNumberEnricher requestNumberEnricher,
|
||||
HttpRequestIdEnricher requestIdEnricher)
|
||||
/// <summary>
|
||||
/// Adds request based serilog enrichers to the LogContext for each request
|
||||
/// </summary>
|
||||
public class UmbracoRequestLoggingMiddleware : IMiddleware
|
||||
{
|
||||
private readonly HttpRequestIdEnricher _requestIdEnricher;
|
||||
private readonly HttpRequestNumberEnricher _requestNumberEnricher;
|
||||
private readonly HttpSessionIdEnricher _sessionIdEnricher;
|
||||
|
||||
public UmbracoRequestLoggingMiddleware(
|
||||
HttpSessionIdEnricher sessionIdEnricher,
|
||||
HttpRequestNumberEnricher requestNumberEnricher,
|
||||
HttpRequestIdEnricher requestIdEnricher)
|
||||
{
|
||||
_sessionIdEnricher = sessionIdEnricher;
|
||||
_requestNumberEnricher = requestNumberEnricher;
|
||||
_requestIdEnricher = requestIdEnricher;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (context.Request.IsClientSideRequest())
|
||||
{
|
||||
_sessionIdEnricher = sessionIdEnricher;
|
||||
_requestNumberEnricher = requestNumberEnricher;
|
||||
_requestIdEnricher = requestIdEnricher;
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
// TODO: Need to decide if we want this stuff still, there's new request logging in serilog:
|
||||
// https://github.com/serilog/serilog-aspnetcore#request-logging which i think would suffice and replace all of this?
|
||||
using (LogContext.Push(_sessionIdEnricher))
|
||||
using (LogContext.Push(_requestNumberEnricher))
|
||||
using (LogContext.Push(_requestIdEnricher))
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (context.Request.IsClientSideRequest())
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Need to decide if we want this stuff still, there's new request logging in serilog:
|
||||
// https://github.com/serilog/serilog-aspnetcore#request-logging which i think would suffice and replace all of this?
|
||||
|
||||
using (LogContext.Push(_sessionIdEnricher))
|
||||
using (LogContext.Push(_requestNumberEnricher))
|
||||
using (LogContext.Push(_requestIdEnricher))
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -19,246 +16,249 @@ using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.WebAssets;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Cms.Web.Common.Profiler;
|
||||
using Umbraco.Cms.Web.Common.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
namespace Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Manages Umbraco request objects and their lifetime
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is responsible for initializing the content cache
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is responsible for creating and assigning an <see cref="IUmbracoContext" />
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class UmbracoRequestMiddleware : IMiddleware
|
||||
{
|
||||
private readonly BackOfficeWebAssets _backOfficeWebAssets;
|
||||
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ILogger<UmbracoRequestMiddleware> _logger;
|
||||
private readonly WebProfiler? _profiler;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly IOptions<UmbracoRequestOptions> _umbracoRequestOptions;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private SmidgeOptions _smidgeOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Manages Umbraco request objects and their lifetime
|
||||
/// Initializes a new instance of the <see cref="UmbracoRequestMiddleware" /> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is responsible for initializing the content cache
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is responsible for creating and assigning an <see cref="IUmbracoContext"/>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class UmbracoRequestMiddleware : IMiddleware
|
||||
// Obsolete, scheduled for removal in V11
|
||||
[Obsolete("Use constructor that takes an IOptions<UmbracoRequestOptions>")]
|
||||
public UmbracoRequestMiddleware(
|
||||
ILogger<UmbracoRequestMiddleware> logger,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
IRequestCache requestCache,
|
||||
IEventAggregator eventAggregator,
|
||||
IProfiler profiler,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
BackOfficeWebAssets backOfficeWebAssets,
|
||||
IOptionsMonitor<SmidgeOptions> smidgeOptions,
|
||||
IRuntimeState runtimeState,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IDefaultCultureAccessor defaultCultureAccessor)
|
||||
: this(
|
||||
logger,
|
||||
umbracoContextFactory,
|
||||
requestCache,
|
||||
eventAggregator,
|
||||
profiler,
|
||||
hostingEnvironment,
|
||||
umbracoRequestPaths,
|
||||
backOfficeWebAssets,
|
||||
smidgeOptions,
|
||||
runtimeState,
|
||||
variationContextAccessor,
|
||||
defaultCultureAccessor,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IOptions<UmbracoRequestOptions>>())
|
||||
{
|
||||
private readonly ILogger<UmbracoRequestMiddleware> _logger;
|
||||
}
|
||||
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly BackOfficeWebAssets _backOfficeWebAssets;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
||||
private readonly IOptions<UmbracoRequestOptions> _umbracoRequestOptions;
|
||||
private SmidgeOptions _smidgeOptions;
|
||||
private readonly WebProfiler? _profiler;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoRequestMiddleware" /> class.
|
||||
/// </summary>
|
||||
public UmbracoRequestMiddleware(
|
||||
ILogger<UmbracoRequestMiddleware> logger,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
IRequestCache requestCache,
|
||||
IEventAggregator eventAggregator,
|
||||
IProfiler profiler,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
BackOfficeWebAssets backOfficeWebAssets,
|
||||
IOptionsMonitor<SmidgeOptions> smidgeOptions,
|
||||
IRuntimeState runtimeState,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IDefaultCultureAccessor defaultCultureAccessor,
|
||||
IOptions<UmbracoRequestOptions> umbracoRequestOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_requestCache = requestCache;
|
||||
_eventAggregator = eventAggregator;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_backOfficeWebAssets = backOfficeWebAssets;
|
||||
_runtimeState = runtimeState;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_defaultCultureAccessor = defaultCultureAccessor;
|
||||
_umbracoRequestOptions = umbracoRequestOptions;
|
||||
_smidgeOptions = smidgeOptions.CurrentValue;
|
||||
_profiler = profiler as WebProfiler; // Ignore if not a WebProfiler
|
||||
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
private static bool s_firstBackOfficeRequest;
|
||||
private static bool s_firstBackOfficeReqestFlag;
|
||||
private static object s_firstBackOfficeRequestLocker = new object();
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
smidgeOptions.OnChange(x => _smidgeOptions = x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoRequestMiddleware"/> class.
|
||||
/// </summary>
|
||||
// Obsolete, scheduled for removal in V11
|
||||
[Obsolete("Use constructor that takes an IOptions<UmbracoRequestOptions>")]
|
||||
public UmbracoRequestMiddleware(
|
||||
ILogger<UmbracoRequestMiddleware> logger,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
IRequestCache requestCache,
|
||||
IEventAggregator eventAggregator,
|
||||
IProfiler profiler,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
BackOfficeWebAssets backOfficeWebAssets,
|
||||
IOptionsMonitor<SmidgeOptions> smidgeOptions,
|
||||
IRuntimeState runtimeState,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IDefaultCultureAccessor defaultCultureAccessor)
|
||||
: this(
|
||||
logger,
|
||||
umbracoContextFactory,
|
||||
requestCache,
|
||||
eventAggregator,
|
||||
profiler,
|
||||
hostingEnvironment,
|
||||
umbracoRequestPaths,
|
||||
backOfficeWebAssets,
|
||||
smidgeOptions,
|
||||
runtimeState,
|
||||
variationContextAccessor,
|
||||
defaultCultureAccessor,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IOptions<UmbracoRequestOptions>>())
|
||||
/// <inheritdoc />
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (context.Request.IsClientSideRequest() &&
|
||||
!_umbracoRequestOptions.Value.HandleAsServerSideRequest(context.Request))
|
||||
{
|
||||
// we need this here because for bundle requests, these are 'client side' requests that we need to handle
|
||||
LazyInitializeBackOfficeServices(context.Request.Path);
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoRequestMiddleware"/> class.
|
||||
/// </summary>
|
||||
public UmbracoRequestMiddleware(
|
||||
ILogger<UmbracoRequestMiddleware> logger,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
IRequestCache requestCache,
|
||||
IEventAggregator eventAggregator,
|
||||
IProfiler profiler,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
BackOfficeWebAssets backOfficeWebAssets,
|
||||
IOptionsMonitor<SmidgeOptions> smidgeOptions,
|
||||
IRuntimeState runtimeState,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IDefaultCultureAccessor defaultCultureAccessor,
|
||||
IOptions<UmbracoRequestOptions> umbracoRequestOptions)
|
||||
// Profiling start needs to be one of the first things that happens.
|
||||
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
|
||||
_profiler?.UmbracoApplicationBeginRequest(context, _runtimeState.Level);
|
||||
|
||||
_variationContextAccessor.VariationContext ??= new VariationContext(_defaultCultureAccessor.DefaultCulture);
|
||||
UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();
|
||||
|
||||
Uri? currentApplicationUrl = GetApplicationUrlFromCurrentRequest(context.Request);
|
||||
_hostingEnvironment.EnsureApplicationMainUrl(currentApplicationUrl);
|
||||
|
||||
var pathAndQuery = context.Request.GetEncodedPathAndQuery();
|
||||
|
||||
try
|
||||
{
|
||||
_logger = logger;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_requestCache = requestCache;
|
||||
_eventAggregator = eventAggregator;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_backOfficeWebAssets = backOfficeWebAssets;
|
||||
_runtimeState = runtimeState;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_defaultCultureAccessor = defaultCultureAccessor;
|
||||
_umbracoRequestOptions = umbracoRequestOptions;
|
||||
_smidgeOptions = smidgeOptions.CurrentValue;
|
||||
_profiler = profiler as WebProfiler; // Ignore if not a WebProfiler
|
||||
|
||||
smidgeOptions.OnChange(x => _smidgeOptions = x);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (context.Request.IsClientSideRequest() && !_umbracoRequestOptions.Value.HandleAsServerSideRequest(context.Request))
|
||||
{
|
||||
// we need this here because for bundle requests, these are 'client side' requests that we need to handle
|
||||
LazyInitializeBackOfficeServices(context.Request.Path);
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Profiling start needs to be one of the first things that happens.
|
||||
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
|
||||
_profiler?.UmbracoApplicationBeginRequest(context, _runtimeState.Level);
|
||||
|
||||
_variationContextAccessor.VariationContext ??= new VariationContext(_defaultCultureAccessor.DefaultCulture);
|
||||
UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();
|
||||
|
||||
Uri? currentApplicationUrl = GetApplicationUrlFromCurrentRequest(context.Request);
|
||||
_hostingEnvironment.EnsureApplicationMainUrl(currentApplicationUrl);
|
||||
|
||||
var pathAndQuery = context.Request.GetEncodedPathAndQuery();
|
||||
// Verbose log start of every request
|
||||
LogHttpRequest.TryGetCurrentHttpRequestId(out Guid? httpRequestId, _requestCache);
|
||||
_logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, pathAndQuery);
|
||||
|
||||
try
|
||||
{
|
||||
// Verbose log start of every request
|
||||
LogHttpRequest.TryGetCurrentHttpRequestId(out Guid? httpRequestId, _requestCache);
|
||||
_logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, pathAndQuery);
|
||||
|
||||
try
|
||||
{
|
||||
LazyInitializeBackOfficeServices(context.Request.Path);
|
||||
await _eventAggregator.PublishAsync(new UmbracoRequestBeginNotification(umbracoContextReference.UmbracoContext));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// try catch so we don't kill everything in all requests
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _eventAggregator.PublishAsync(new UmbracoRequestEndNotification(umbracoContextReference.UmbracoContext));
|
||||
}
|
||||
}
|
||||
LazyInitializeBackOfficeServices(context.Request.Path);
|
||||
await _eventAggregator.PublishAsync(
|
||||
new UmbracoRequestBeginNotification(umbracoContextReference.UmbracoContext));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// try catch so we don't kill everything in all requests
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Verbose log end of every request (in v8 we didn't log the end request of ALL requests, only the front-end which was
|
||||
// strange since we always logged the beginning, so now we just log start/end of all requests)
|
||||
LogHttpRequest.TryGetCurrentHttpRequestId(out Guid? httpRequestId, _requestCache);
|
||||
_logger.LogTrace("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, pathAndQuery, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
DisposeHttpContextItems(context.Request);
|
||||
await next(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose the umbraco context reference which will in turn dispose the UmbracoContext itself.
|
||||
umbracoContextReference.Dispose();
|
||||
await _eventAggregator.PublishAsync(
|
||||
new UmbracoRequestEndNotification(umbracoContextReference.UmbracoContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Verbose log end of every request (in v8 we didn't log the end request of ALL requests, only the front-end which was
|
||||
// strange since we always logged the beginning, so now we just log start/end of all requests)
|
||||
LogHttpRequest.TryGetCurrentHttpRequestId(out Guid? httpRequestId, _requestCache);
|
||||
_logger.LogTrace(
|
||||
"End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)",
|
||||
httpRequestId,
|
||||
pathAndQuery,
|
||||
DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds);
|
||||
|
||||
// Profiling end needs to be last of the first things that happens.
|
||||
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
|
||||
_profiler?.UmbracoApplicationEndRequest(context, _runtimeState.Level);
|
||||
try
|
||||
{
|
||||
DisposeHttpContextItems(context.Request);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose the umbraco context reference which will in turn dispose the UmbracoContext itself.
|
||||
umbracoContextReference.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to lazily initialize any back office services when the first request to the back office is made
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <returns></returns>
|
||||
private void LazyInitializeBackOfficeServices(PathString absPath)
|
||||
{
|
||||
if (s_firstBackOfficeRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Profiling end needs to be last of the first things that happens.
|
||||
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
|
||||
_profiler?.UmbracoApplicationEndRequest(context, _runtimeState.Level);
|
||||
}
|
||||
|
||||
if (_umbracoRequestPaths.IsBackOfficeRequest(absPath)
|
||||
|| (absPath.Value?.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.CompositeFilePath}") ?? false)
|
||||
|| (absPath.Value?.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.BundleFilePath}") ?? false))
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref s_firstBackOfficeRequest, ref s_firstBackOfficeReqestFlag, ref s_firstBackOfficeRequestLocker, () =>
|
||||
/// <summary>
|
||||
/// Used to lazily initialize any back office services when the first request to the back office is made
|
||||
/// </summary>
|
||||
private void LazyInitializeBackOfficeServices(PathString absPath)
|
||||
{
|
||||
if (s_firstBackOfficeRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_umbracoRequestPaths.IsBackOfficeRequest(absPath)
|
||||
|| (absPath.Value?.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.CompositeFilePath}") ?? false)
|
||||
|| (absPath.Value?.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.BundleFilePath}") ?? false))
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref s_firstBackOfficeRequest, ref s_firstBackOfficeReqestFlag,
|
||||
ref s_firstBackOfficeRequestLocker, () =>
|
||||
{
|
||||
_backOfficeWebAssets.CreateBundles();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Uri? GetApplicationUrlFromCurrentRequest(HttpRequest request)
|
||||
{
|
||||
// We only consider GET and POST.
|
||||
// Especially the DEBUG sent when debugging the application is annoying because it uses http, even when the https is available.
|
||||
if (request.Method == "GET" || request.Method == "POST")
|
||||
{
|
||||
return new Uri($"{request.Scheme}://{request.Host}{request.PathBase}", UriKind.Absolute);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose some request scoped objects that we are maintaining the lifecycle for.
|
||||
/// </summary>
|
||||
private void DisposeHttpContextItems(HttpRequest request)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (request.IsClientSideRequest())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure this is disposed by DI at the end of the request
|
||||
IHttpScopeReference httpScopeReference = request.HttpContext.RequestServices.GetRequiredService<IHttpScopeReference>();
|
||||
httpScopeReference.Register();
|
||||
}
|
||||
}
|
||||
|
||||
private Uri? GetApplicationUrlFromCurrentRequest(HttpRequest request)
|
||||
{
|
||||
// We only consider GET and POST.
|
||||
// Especially the DEBUG sent when debugging the application is annoying because it uses http, even when the https is available.
|
||||
if (request.Method == "GET" || request.Method == "POST")
|
||||
{
|
||||
return new Uri($"{request.Scheme}://{request.Host}{request.PathBase}", UriKind.Absolute);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose some request scoped objects that we are maintaining the lifecycle for.
|
||||
/// </summary>
|
||||
private void DisposeHttpContextItems(HttpRequest request)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (request.IsClientSideRequest())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure this is disposed by DI at the end of the request
|
||||
IHttpScopeReference httpScopeReference =
|
||||
request.HttpContext.RequestServices.GetRequiredService<IHttpScopeReference>();
|
||||
httpScopeReference.Register();
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
private static bool s_firstBackOfficeRequest;
|
||||
private static bool s_firstBackOfficeReqestFlag;
|
||||
private static object s_firstBackOfficeRequestLocker = new();
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user