diff --git a/src/Umbraco.Web.Common/AspNetCore/ApplicationUrlRequestBeginNotificationHandler.cs b/src/Umbraco.Web.Common/AspNetCore/ApplicationUrlRequestBeginNotificationHandler.cs new file mode 100644 index 0000000000..0472d31592 --- /dev/null +++ b/src/Umbraco.Web.Common/AspNetCore/ApplicationUrlRequestBeginNotificationHandler.cs @@ -0,0 +1,26 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Web; + +namespace Umbraco.Cms.Web.Common.AspNetCore; + +/// +/// Notification handler which will listen to the , and ensure that +/// the applicationUrl is set on the first request. +/// +internal class ApplicationUrlRequestBeginNotificationHandler : INotificationHandler +{ + private readonly IRequestAccessor _requestAccessor; + + public ApplicationUrlRequestBeginNotificationHandler(IRequestAccessor requestAccessor) => + _requestAccessor = requestAccessor; + + public void Handle(UmbracoRequestBeginNotification notification) + { + // If someone has replaced the AspNetCoreRequestAccessor we'll do nothing and assume they handle it themselves. + if (_requestAccessor is AspNetCoreRequestAccessor accessor) + { + accessor.EnsureApplicationUrl(); + } + } +} diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs index 38d67ff2f0..de47999835 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs @@ -9,7 +9,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.AspNetCore; -public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler +public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler, IDisposable { private readonly ISet _applicationUrls = new HashSet(); private readonly IHttpContextAccessor _httpContextAccessor; @@ -18,6 +18,7 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler< private object _initLocker = new(); private bool _isInit; private WebRoutingSettings _webRoutingSettings; + private readonly IDisposable? _onChangeDisposable; /// /// Initializes a new instance of the class. @@ -28,20 +29,19 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler< { _httpContextAccessor = httpContextAccessor; _webRoutingSettings = webRoutingSettings.CurrentValue; - webRoutingSettings.OnChange(x => _webRoutingSettings = x); + _onChangeDisposable = webRoutingSettings.OnChange(x => _webRoutingSettings = x); } /// - /// This just initializes the application URL on first request attempt - /// TODO: This doesn't belong here, the GetApplicationUrl doesn't belong to IRequestAccessor - /// this should be part of middleware not a lazy init based on an INotification + /// + /// This is now a NoOp, and is no longer used, instead ApplicationUrlRequestBeginNotificationHandler is used + /// /// + [Obsolete("This is no longer used, AspNetCoreRequestAccessor will no longer implement INotificationHandler in V12, see ApplicationUrlRequestBeginNotificationHandler instead.")] public void Handle(UmbracoRequestBeginNotification notification) - => LazyInitializer.EnsureInitialized(ref _hasAppUrl, ref _isInit, ref _initLocker, () => - { - GetApplicationUrl(); - return true; - }); + { + // NoOP + } /// public string GetRequestValue(string name) => GetFormValue(name) ?? GetQueryStringValue(name); @@ -54,6 +54,17 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler< ? new Uri(_httpContextAccessor.HttpContext.Request.GetEncodedUrl()) : null; + /// + /// Ensure that the ApplicationUrl is set on the first request, this is using a LazyInitializer, so the code will only be run the first time + /// + /// TODO: This doesn't belong here, the GetApplicationUrl doesn't belong to IRequestAccessor + internal void EnsureApplicationUrl() => + LazyInitializer.EnsureInitialized(ref _hasAppUrl, ref _isInit, ref _initLocker, () => + { + GetApplicationUrl(); + return true; + }); + public Uri? GetApplicationUrl() { // Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that @@ -63,7 +74,7 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler< // see U4-10626 - in some cases we want to reset the application url // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? - if (!(_webRoutingSettings.UmbracoApplicationUrl is null)) + if (_webRoutingSettings.UmbracoApplicationUrl is not null) { return new Uri(_webRoutingSettings.UmbracoApplicationUrl); } @@ -96,4 +107,6 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler< return request.Form[name]; } + + public void Dispose() => _onChangeDisposable?.Dispose(); } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 40b84a0987..d454e5d6c5 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -298,6 +298,7 @@ public static partial class UmbracoBuilderExtensions // AspNetCore specific services builder.Services.AddUnique(); builder.AddNotificationHandler(); + builder.AddNotificationHandler(); // Password hasher builder.Services.AddUnique();