From fea86bbf7a8c625ebbd9add68d001d430cdce68f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 8 Feb 2021 11:00:15 +0100 Subject: [PATCH] Moved the application url to HostingEnvironment and set it in the request middleware --- .../Checks/Security/BaseHttpHeaderCheck.cs | 9 ++-- .../Checks/Security/ClickJackingCheck.cs | 6 +-- .../Checks/Security/ExcessiveHeadersCheck.cs | 10 ++--- .../HealthChecks/Checks/Security/HstsCheck.cs | 6 +-- .../Checks/Security/HttpsCheck.cs | 14 +++--- .../Checks/Security/NoSniffCheck.cs | 6 +-- .../Checks/Security/XssProtectionCheck.cs | 6 +-- .../EmailNotificationMethod.cs | 12 ++--- .../Hosting/IHostingEnvironment.cs | 10 +++++ src/Umbraco.Core/Web/IRequestAccessor.cs | 4 -- .../Compose/NotificationsComponent.cs | 20 +++------ .../HostedServices/KeepAlive.cs | 9 ++-- .../ServerRegistration/TouchServerTask.cs | 15 ++++--- .../Implementations/TestHelper.cs | 7 +++ .../Implementations/TestHostingEnvironment.cs | 4 +- .../TestHelpers/TestHelper.cs | 1 + .../Extensions/UriExtensionsTests.cs | 4 +- .../Routing/UmbracoRequestPathsTests.cs | 9 ++-- .../HostedServices/KeepAliveTests.cs | 5 ++- .../TouchServerTaskTests.cs | 8 ++-- .../Controllers/AuthenticationController.cs | 5 +-- .../Controllers/UsersController.cs | 5 +-- .../AspNetCoreHostingEnvironment.cs | 45 +++++++++++++++++-- .../UmbracoBuilderExtensions.cs | 4 +- .../Middleware/UmbracoRequestMiddleware.cs | 22 ++++++++- .../AspNet/AspNetHostingEnvironment.cs | 8 ++++ 26 files changed, 168 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index 0f188bd390..0e7cbfe839 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Umbraco.Core.Hosting; using Umbraco.Core.Services; using Umbraco.Web; @@ -18,18 +19,18 @@ namespace Umbraco.Core.HealthChecks.Checks.Security /// public abstract class BaseHttpHeaderCheck : HealthCheck { + private readonly IHostingEnvironment _hostingEnvironment; private readonly string _header; private readonly string _value; private readonly string _localizedTextPrefix; private readonly bool _metaTagOptionAvailable; - private readonly IRequestAccessor _requestAccessor; private static HttpClient s_httpClient; /// /// Initializes a new instance of the class. /// protected BaseHttpHeaderCheck( - IRequestAccessor requestAccessor, + IHostingEnvironment hostingEnvironment, ILocalizedTextService textService, string header, string value, @@ -37,7 +38,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security bool metaTagOptionAvailable) { LocalizedTextService = textService ?? throw new ArgumentNullException(nameof(textService)); - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; _header = header; _value = value; _localizedTextPrefix = localizedTextPrefix; @@ -78,7 +79,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security var success = false; // Access the site home page and check for the click-jack protection header or meta tag - Uri url = _requestAccessor.GetApplicationUrl(); + Uri url = _hostingEnvironment.ApplicationMainUrl; try { diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs index 9b654b5f8b..6bb92e4176 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs @@ -1,8 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -19,8 +19,8 @@ namespace Umbraco.Core.HealthChecks.Checks.Security /// /// Initializes a new instance of the class. /// - public ClickJackingCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) - : base(requestAccessor, textService, "X-Frame-Options", "sameorigin", "clickJacking", true) + public ClickJackingCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-Frame-Options", "sameorigin", "clickJacking", true) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index 010683c6fe..000c14f93d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -22,16 +22,16 @@ namespace Umbraco.Core.HealthChecks.Checks.Security public class ExcessiveHeadersCheck : HealthCheck { private readonly ILocalizedTextService _textService; - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private static HttpClient s_httpClient; /// /// Initializes a new instance of the class. /// - public ExcessiveHeadersCheck(ILocalizedTextService textService, IRequestAccessor requestAccessor) + public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment) { _textService = textService; - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; } private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); @@ -52,7 +52,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security { string message; var success = false; - Uri url = _requestAccessor.GetApplicationUrl(); + Uri url = _hostingEnvironment.ApplicationMainUrl; // Access the site home page and check for the headers var request = new HttpRequestMessage(HttpMethod.Head, url); diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs index 188e8f3080..828d2d2470 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs @@ -1,8 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -26,8 +26,8 @@ namespace Umbraco.Core.HealthChecks.Checks.Security /// If you want do to it perfectly, you have to submit it https://hstspreload.org/, /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. /// - public HstsCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) - : base(requestAccessor, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true) + public HstsCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index 187fb2d300..5916c48b82 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -10,8 +10,8 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -27,7 +27,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security { private readonly ILocalizedTextService _textService; private readonly IOptionsMonitor _globalSettings; - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private static HttpClient s_httpClient; private static HttpClientHandler s_httpClientHandler; @@ -39,11 +39,11 @@ namespace Umbraco.Core.HealthChecks.Checks.Security public HttpsCheck( ILocalizedTextService textService, IOptionsMonitor globalSettings, - IRequestAccessor requestAccessor) + IHostingEnvironment hostingEnvironment) { _textService = textService; _globalSettings = globalSettings; - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; } private static HttpClient HttpClient => s_httpClient ??= new HttpClient(HttpClientHandler); @@ -85,7 +85,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security // Attempt to access the site over HTTPS to see if it HTTPS is supported // and a valid certificate has been configured - var url = _requestAccessor.GetApplicationUrl().ToString().Replace("http:", "https:"); + var url = _hostingEnvironment.ApplicationMainUrl.ToString().Replace("http:", "https:"); var request = new HttpRequestMessage(HttpMethod.Head, url); @@ -148,7 +148,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security private Task CheckIfCurrentSchemeIsHttps() { - Uri uri = _requestAccessor.GetApplicationUrl(); + Uri uri = _hostingEnvironment.ApplicationMainUrl; var success = uri.Scheme == "https"; return Task.FromResult(new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) @@ -161,7 +161,7 @@ namespace Umbraco.Core.HealthChecks.Checks.Security private Task CheckHttpsConfigurationSetting() { bool httpsSettingEnabled = _globalSettings.CurrentValue.UseHttps; - Uri uri = _requestAccessor.GetApplicationUrl(); + Uri uri = _hostingEnvironment.ApplicationMainUrl; string resultMessage; StatusResultType resultType; diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs index b74be4ca6b..0722f4cf64 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs @@ -1,8 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -19,8 +19,8 @@ namespace Umbraco.Core.HealthChecks.Checks.Security /// /// Initializes a new instance of the class. /// - public NoSniffCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) - : base(requestAccessor, textService, "X-Content-Type-Options", "nosniff", "noSniff", false) + public NoSniffCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-Content-Type-Options", "nosniff", "noSniff", false) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs index 29d14ee238..5a1973d05b 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs @@ -1,8 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.Checks.Security { @@ -26,8 +26,8 @@ namespace Umbraco.Core.HealthChecks.Checks.Security /// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/, /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. /// - public XssProtectionCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService) - : base(requestAccessor, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true) + public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true) { } diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index 7492b8b9ad..97ef86d205 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -2,10 +2,10 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Mail; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Core.HealthChecks.NotificationMethods { @@ -13,7 +13,7 @@ namespace Umbraco.Core.HealthChecks.NotificationMethods public class EmailNotificationMethod : NotificationMethodBase { private readonly ILocalizedTextService _textService; - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IEmailSender _emailSender; private readonly IMarkdownToHtmlConverter _markdownToHtmlConverter; @@ -21,7 +21,7 @@ namespace Umbraco.Core.HealthChecks.NotificationMethods public EmailNotificationMethod( ILocalizedTextService textService, - IRequestAccessor requestAccessor, + IHostingEnvironment hostingEnvironment, IEmailSender emailSender, IOptions healthChecksSettings, IOptions contentSettings, @@ -38,7 +38,7 @@ namespace Umbraco.Core.HealthChecks.NotificationMethods RecipientEmail = recipientEmail; _textService = textService ?? throw new ArgumentNullException(nameof(textService)); - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; _emailSender = emailSender; _markdownToHtmlConverter = markdownToHtmlConverter; _contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings)); @@ -67,9 +67,9 @@ namespace Umbraco.Core.HealthChecks.NotificationMethods // Include the umbraco Application URL host in the message subject so that // you can identify the site that these results are for. - var host = _requestAccessor.GetApplicationUrl(); + var host = _hostingEnvironment.ApplicationMainUrl?.ToString(); - var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host.ToString() }); + var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host }); var mailMessage = CreateMailMessage(subject, message); diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs index ff2b3adfa5..e01435422d 100644 --- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs +++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs @@ -31,6 +31,11 @@ namespace Umbraco.Core.Hosting /// bool IsHosted { get; } + /// + /// Gets the main application url. + /// + Uri ApplicationMainUrl { get; } + /// /// Maps a virtual path to a physical path to the application's web root /// @@ -61,5 +66,10 @@ namespace Umbraco.Core.Hosting /// If virtualPath does not start with ~/ or / /// string ToAbsolute(string virtualPath); + + /// + /// Ensures that the application know its main Url. + /// + void EnsureApplicationMainUrl(Uri currentApplicationUrl); } } diff --git a/src/Umbraco.Core/Web/IRequestAccessor.cs b/src/Umbraco.Core/Web/IRequestAccessor.cs index 275c96bf23..56c8091f94 100644 --- a/src/Umbraco.Core/Web/IRequestAccessor.cs +++ b/src/Umbraco.Core/Web/IRequestAccessor.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Web.Routing; namespace Umbraco.Web { @@ -19,8 +18,5 @@ namespace Umbraco.Web /// Returns the current request uri /// Uri GetRequestUrl(); - - // TODO: This doesn't belongs here - Uri GetApplicationUrl(); } } diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 767f36aaf5..ad3efc88df 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -2,11 +2,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -185,7 +186,7 @@ namespace Umbraco.Web.Compose public sealed class Notifier { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private readonly INotificationService _notificationService; private readonly IUserService _userService; private readonly ILocalizedTextService _textService; @@ -193,18 +194,11 @@ namespace Umbraco.Web.Compose private readonly ILogger _logger; /// - /// Constructor + /// Initializes a new instance of the class. /// - /// - /// - /// - /// - /// - /// - /// public Notifier( IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IRequestAccessor requestAccessor, + IHostingEnvironment hostingEnvironment, INotificationService notificationService, IUserService userService, ILocalizedTextService textService, @@ -212,7 +206,7 @@ namespace Umbraco.Web.Compose ILogger logger) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; _notificationService = notificationService; _userService = userService; _textService = textService; @@ -236,7 +230,7 @@ namespace Umbraco.Web.Compose } } - SendNotification(user, entities, action, _requestAccessor.GetApplicationUrl()); + SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); } private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 0ec237c6d6..a020f04b16 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Web; @@ -19,7 +20,7 @@ namespace Umbraco.Infrastructure.HostedServices /// public class KeepAlive : RecurringHostedServiceBase { - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IMainDom _mainDom; private readonly KeepAliveSettings _keepAliveSettings; private readonly ILogger _logger; @@ -38,7 +39,7 @@ namespace Umbraco.Infrastructure.HostedServices /// Provider of server registrations to the distributed cache. /// Factory for instances. public KeepAlive( - IRequestAccessor requestAccessor, + IHostingEnvironment hostingEnvironment, IMainDom mainDom, IOptions keepAliveSettings, ILogger logger, @@ -47,7 +48,7 @@ namespace Umbraco.Infrastructure.HostedServices IHttpClientFactory httpClientFactory) : base(TimeSpan.FromMinutes(5), DefaultDelay) { - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; _mainDom = mainDom; _keepAliveSettings = keepAliveSettings.Value; _logger = logger; @@ -88,7 +89,7 @@ namespace Umbraco.Infrastructure.HostedServices { if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}")) { - var umbracoAppUrl = _requestAccessor.GetApplicationUrl().ToString(); + var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString(); if (umbracoAppUrl.IsNullOrWhiteSpace()) { _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 69f9280fc0..6771705c8e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -7,8 +7,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Services; -using Umbraco.Web; namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { @@ -19,7 +19,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { private readonly IRuntimeState _runtimeState; private readonly IServerRegistrationService _serverRegistrationService; - private readonly IRequestAccessor _requestAccessor; + private readonly IHostingEnvironment _hostingEnvironment; private readonly ILogger _logger; private readonly GlobalSettings _globalSettings; @@ -31,12 +31,17 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration /// Accessor for the current request. /// The typed logger. /// The configuration for global settings. - public TouchServerTask(IRuntimeState runtimeState, IServerRegistrationService serverRegistrationService, IRequestAccessor requestAccessor, ILogger logger, IOptions globalSettings) + public TouchServerTask( + IRuntimeState runtimeState, + IServerRegistrationService serverRegistrationService, + IHostingEnvironment hostingEnvironment, + ILogger logger, + IOptions globalSettings) : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { _runtimeState = runtimeState; _serverRegistrationService = serverRegistrationService ?? throw new ArgumentNullException(nameof(serverRegistrationService)); - _requestAccessor = requestAccessor; + _hostingEnvironment = hostingEnvironment; _logger = logger; _globalSettings = globalSettings.Value; } @@ -48,7 +53,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration return Task.CompletedTask; } - var serverAddress = _requestAccessor.GetApplicationUrl()?.ToString(); + var serverAddress = _hostingEnvironment.ApplicationMainUrl?.ToString(); if (serverAddress.IsNullOrWhiteSpace()) { _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index 4c698b221d..8bdca33561 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -141,6 +141,7 @@ namespace Umbraco.Tests.Integration.Implementations public override IHostingEnvironment GetHostingEnvironment() => _hostingEnvironment ??= new TestHostingEnvironment( GetIOptionsMonitorOfHostingSettings(), + GetIOptionsMonitorOfWebRoutingSettings(), _hostEnvironment); private IOptionsMonitor GetIOptionsMonitorOfHostingSettings() @@ -149,6 +150,12 @@ namespace Umbraco.Tests.Integration.Implementations return Mock.Of>(x => x.CurrentValue == hostingSettings); } + private IOptionsMonitor GetIOptionsMonitorOfWebRoutingSettings() + { + var webRoutingSettings = new WebRoutingSettings(); + return Mock.Of>(x => x.CurrentValue == webRoutingSettings); + } + public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _hostingLifetime; public override IIpResolver GetIpResolver() => _ipResolver; diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs index 2a91b6db83..8690a5f6f8 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs @@ -11,8 +11,8 @@ namespace Umbraco.Tests.Integration.Implementations { public class TestHostingEnvironment : AspNetCoreHostingEnvironment, IHostingEnvironment { - public TestHostingEnvironment(IOptionsMonitor hostingSettings, IWebHostEnvironment webHostEnvironment) - : base(hostingSettings, webHostEnvironment) + public TestHostingEnvironment(IOptionsMonitor hostingSettings,IOptionsMonitor webRoutingSettings, IWebHostEnvironment webHostEnvironment) + : base(hostingSettings,webRoutingSettings, webHostEnvironment) { } diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index b23ccc9080..9da0f84202 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -71,6 +71,7 @@ namespace Umbraco.Tests.TestHelpers var testPath = TestContext.CurrentContext.TestDirectory.Split("bin")[0]; return new AspNetCoreHostingEnvironment( Mock.Of>(x => x.CurrentValue == new HostingSettings()), + Mock.Of>(x => x.CurrentValue == new WebRoutingSettings()), Mock.Of( x => x.WebRootPath == "/" && diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs index fc8ecd0474..fa5ff0df0b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs @@ -28,8 +28,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") { var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; + var webRoutingSettings = new WebRoutingSettings(); var mockedOptionsMonitorOfHostingSettings = Mock.Of>(x => x.CurrentValue == hostingSettings); - return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, _hostEnvironment); + var mockedOptionsMonitorOfWebRoutingSettings = Mock.Of>(x => x.CurrentValue == webRoutingSettings); + return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, mockedOptionsMonitorOfWebRoutingSettings, _hostEnvironment); } [TestCase("http://www.domain.com/foo/bar", "/", "http://www.domain.com/")] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs index 24f0b04080..57015f30ea 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -25,9 +25,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") { + var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; + var webRoutingSettings = new WebRoutingSettings(); var mockedOptionsMonitorOfHostingSettings = Mock.Of>(x => x.CurrentValue == hostingSettings); - return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, _hostEnvironment); + var mockedOptionsMonitorOfWebRoutingSettings = Mock.Of>(x => x.CurrentValue == webRoutingSettings); + return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, mockedOptionsMonitorOfWebRoutingSettings, _hostEnvironment); } [TestCase("/favicon.ico", true)] @@ -65,7 +68,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://www.domain.com/Umbraco/", "", true)] [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] [TestCase("http://www.domain.com/umbraco/test/test", "", false)] - [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] + [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] [TestCase("http://www.domain.com/umbrac", "", false)] [TestCase("http://www.domain.com/test", "", false)] [TestCase("http://www.domain.com/test/umbraco", "", false)] @@ -84,7 +87,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); } - + [TestCase("http://www.domain.com/install", true)] [TestCase("http://www.domain.com/Install/", true)] [TestCase("http://www.domain.com/install/default.aspx", true)] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs index 752da01f0f..67c83e0642 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs @@ -13,6 +13,7 @@ using Moq.Protected; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; @@ -78,8 +79,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices DisableKeepAliveTask = !enabled, }; - var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(ApplicationUrl)); + var mockRequestAccessor = new Mock(); + mockRequestAccessor.SetupGet(x => x.ApplicationMainUrl).Returns(new Uri(ApplicationUrl)); var mockServerRegistrar = new Mock(); mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index d293a5b7e8..5b97c36d16 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -9,9 +9,9 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.Services; using Umbraco.Infrastructure.HostedServices.ServerRegistration; -using Umbraco.Web; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRegistration { @@ -53,8 +53,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = ApplicationUrl) { - var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); + var mockRequestAccessor = new Mock(); + mockRequestAccessor.SetupGet(x => x.ApplicationMainUrl).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); var mockRunTimeState = new Mock(); mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); @@ -62,7 +62,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe var mockLogger = new Mock>(); _mockServerRegistrationService = new Mock(); - + var settings = new GlobalSettings { DatabaseServerRegistrar = new DatabaseServerRegistrarSettings diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 11931b5f47..5e512b2342 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -64,7 +64,6 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IEmailSender _emailSender; private readonly ISmsSender _smsSender; private readonly Core.Hosting.IHostingEnvironment _hostingEnvironment; - private readonly IRequestAccessor _requestAccessor; private readonly LinkGenerator _linkGenerator; private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; @@ -86,7 +85,6 @@ namespace Umbraco.Web.BackOffice.Controllers IEmailSender emailSender, ISmsSender smsSender, Core.Hosting.IHostingEnvironment hostingEnvironment, - IRequestAccessor requestAccessor, LinkGenerator linkGenerator, IBackOfficeExternalLoginProviders externalAuthenticationOptions, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) @@ -105,7 +103,6 @@ namespace Umbraco.Web.BackOffice.Controllers _emailSender = emailSender; _smsSender = smsSender; _hostingEnvironment = hostingEnvironment; - _requestAccessor = requestAccessor; _linkGenerator = linkGenerator; _externalAuthenticationOptions = externalAuthenticationOptions; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; @@ -624,7 +621,7 @@ namespace Umbraco.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _requestAccessor.GetApplicationUrl(); + var applicationUri = _hostingEnvironment.ApplicationMainUrl; var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index b543146f19..5e3d6b6791 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -54,7 +54,6 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly ISqlContext _sqlContext; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly SecuritySettings _securitySettings; - private readonly IRequestAccessor _requestAccessor; private readonly IEmailSender _emailSender; private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly AppCaches _appCaches; @@ -77,7 +76,6 @@ namespace Umbraco.Web.BackOffice.Controllers ISqlContext sqlContext, IImageUrlGenerator imageUrlGenerator, IOptions securitySettings, - IRequestAccessor requestAccessor, IEmailSender emailSender, IBackOfficeSecurityAccessor backofficeSecurityAccessor, AppCaches appCaches, @@ -98,7 +96,6 @@ namespace Umbraco.Web.BackOffice.Controllers _sqlContext = sqlContext; _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings.Value; - _requestAccessor = requestAccessor; _emailSender = emailSender; _backofficeSecurityAccessor = backofficeSecurityAccessor; _appCaches = appCaches; @@ -566,7 +563,7 @@ namespace Umbraco.Web.BackOffice.Controllers }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = _requestAccessor.GetApplicationUrl(); + var applicationUri = _hostingEnvironment.ApplicationMainUrl; var inviteUri = new Uri(applicationUri, action); var emailSubject = _localizedTextService.Localize("user/inviteEmailCopySubject", diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 64e5e92e38..cdac7c562e 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; @@ -10,14 +11,20 @@ namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreHostingEnvironment : Core.Hosting.IHostingEnvironment { - private IOptionsMonitor _hostingSettings; + private readonly ISet _applicationUrls = new HashSet(); + private readonly IOptionsMonitor _hostingSettings; + private readonly IOptionsMonitor _webRoutingSettings; private readonly IWebHostEnvironment _webHostEnvironment; private string _localTempPath; - public AspNetCoreHostingEnvironment(IOptionsMonitor hostingSettings, IWebHostEnvironment webHostEnvironment) + public AspNetCoreHostingEnvironment( + IOptionsMonitor hostingSettings, + IOptionsMonitor webRoutingSettings, + IWebHostEnvironment webHostEnvironment) { _hostingSettings = hostingSettings ?? throw new ArgumentNullException(nameof(hostingSettings)); + _webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); SiteName = webHostEnvironment.ApplicationName; @@ -28,6 +35,9 @@ namespace Umbraco.Web.Common.AspNetCore /// public bool IsHosted { get; } = true; + /// + public Uri ApplicationMainUrl { get; private set; } + /// public string SiteName { get; } @@ -37,8 +47,6 @@ namespace Umbraco.Web.Common.AspNetCore /// public string ApplicationPhysicalPath { get; } - public string ApplicationServerAddress { get; } - // TODO how to find this, This is a server thing, not application thing. public string ApplicationVirtualPath => _hostingSettings.CurrentValue.ApplicationVirtualPath?.EnsureStartsWith('/') ?? "/"; @@ -123,6 +131,35 @@ namespace Umbraco.Web.Common.AspNetCore return fullPath; } + + public void EnsureApplicationMainUrl(Uri currentApplicationUrl) + { + // Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that + // it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part + // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626 + + // 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 (currentApplicationUrl == null) + { + return; + } + + if (!(_webRoutingSettings.CurrentValue.UmbracoApplicationUrl is null)) + { + return; + } + + var change = currentApplicationUrl != null && !_applicationUrls.Contains(currentApplicationUrl); + if (change) + { + _applicationUrls.Add(currentApplicationUrl); + + ApplicationMainUrl = currentApplicationUrl; + } + } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 7476a83167..ba6cbe03a9 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -382,9 +382,11 @@ namespace Umbraco.Web.Common.DependencyInjection private static IHostingEnvironment GetTemporaryHostingEnvironment(IWebHostEnvironment webHostEnvironment, IConfiguration config) { var hostingSettings = config.GetSection(Core.Constants.Configuration.ConfigHosting).Get() ?? new HostingSettings(); + var webRoutingSettings = config.GetSection(Core.Constants.Configuration.ConfigWebRouting).Get() ?? new WebRoutingSettings(); var wrappedHostingSettings = new OptionsMonitorAdapter(hostingSettings); + var wrappedWebRoutingSettings = new OptionsMonitorAdapter(webRoutingSettings); - return new AspNetCoreHostingEnvironment(wrappedHostingSettings, webHostEnvironment); + return new AspNetCoreHostingEnvironment(wrappedHostingSettings,wrappedWebRoutingSettings, webHostEnvironment); } } } diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index cee2d0e6e7..5dc604fea9 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Events; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Extensions; using Umbraco.Web.Common.Profiler; @@ -36,6 +37,7 @@ namespace Umbraco.Web.Common.Middleware private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler; private readonly IEventAggregator _eventAggregator; + private readonly IHostingEnvironment _hostingEnvironment; private readonly WebProfiler _profiler; private static bool s_cacheInitialized = false; private static bool s_cacheInitializedFlag = false; @@ -51,7 +53,8 @@ namespace Umbraco.Web.Common.Middleware IBackOfficeSecurityFactory backofficeSecurityFactory, PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler, IEventAggregator eventAggregator, - IProfiler profiler) + IProfiler profiler, + IHostingEnvironment hostingEnvironment) { _logger = logger; _umbracoContextFactory = umbracoContextFactory; @@ -59,6 +62,7 @@ namespace Umbraco.Web.Common.Middleware _backofficeSecurityFactory = backofficeSecurityFactory; _publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler; _eventAggregator = eventAggregator; + _hostingEnvironment = hostingEnvironment; _profiler = profiler as WebProfiler; // Ignore if not a WebProfiler } @@ -81,6 +85,10 @@ namespace Umbraco.Web.Common.Middleware _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext, TODO: Why? UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + Uri currentApplicationUrl = GetApplicationUrlFromCurrentRequest(context.Request); + _hostingEnvironment.EnsureApplicationMainUrl(currentApplicationUrl); + + bool isFrontEndRequest = umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest(); var pathAndQuery = context.Request.GetEncodedPathAndQuery(); @@ -138,6 +146,18 @@ namespace Umbraco.Web.Common.Middleware _profiler?.UmbracoApplicationEndRequest(context); } + 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; + } + /// /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request /// diff --git a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs index 6e0bca8af5..a58a5f3a14 100644 --- a/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs +++ b/src/Umbraco.Web/AspNet/AspNetHostingEnvironment.cs @@ -17,6 +17,7 @@ namespace Umbraco.Web.Hosting private readonly HostingSettings _hostingSettings; private string _localTempPath; + private Uri _applicationMainUrl; public AspNetHostingEnvironment(IOptions hostingSettings) @@ -41,6 +42,12 @@ namespace Umbraco.Web.Hosting /// public bool IsHosted => (HttpContext.Current != null || HostingEnvironment.IsHosted); + public Uri ApplicationMainUrl + { + get => _applicationMainUrl; + set => _applicationMainUrl = value; + } + public string MapPathWebRoot(string path) { if (HostingEnvironment.IsHosted) @@ -54,6 +61,7 @@ namespace Umbraco.Web.Hosting public string MapPathContentRoot(string path) => MapPathWebRoot(path); public string ToAbsolute(string virtualPath) => VirtualPathUtility.ToAbsolute(virtualPath, ApplicationVirtualPath); + public void EnsureApplicationMainUrl(Uri currentApplicationUrl) => throw new NotImplementedException(); public string LocalTempPath