diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 46571f5d65..500bd65f82 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Routing } IPublishedContent node = null; - var path = frequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.AbsolutePathDecoded; var nodeId = -1; diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 38f04d1ddb..e3c5b28a2a 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -51,8 +51,8 @@ namespace Umbraco.Web.Routing } var route = frequest.Domain != null - ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) - : frequest.Uri.GetAbsolutePathDecoded(); + ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded) + : frequest.AbsolutePathDecoded; IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index 27893cd3de..c20cf9fd85 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -44,11 +44,11 @@ namespace Umbraco.Web.Routing string route; if (frequest.Domain != null) { - route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded); } else { - route = frequest.Uri.GetAbsolutePathDecoded(); + route = frequest.AbsolutePathDecoded; } IPublishedContent node = FindContent(frequest, route); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 770fdf4003..4745ea8cd3 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Routing umbCtx.Content, frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture, - frequest.Uri.GetAbsolutePathDecoded()); + frequest.AbsolutePathDecoded); if (node != null) { diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index c6bd4f383d..2e69446d68 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Routing /// If successful, also assigns the template. public override bool TryFindContent(IPublishedRequestBuilder frequest) { - var path = frequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.AbsolutePathDecoded; if (frequest.Domain != null) { diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index fa5d84836d..0d14b26396 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -364,9 +364,7 @@ namespace Umbraco.Web.Routing /// The path part relative to the uri of the domain. /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. public static string PathRelativeToDomain(Uri domainUri, string path) - { - return path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); - } + => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); #endregion } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index ced443a89c..180b6825f2 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -18,6 +18,11 @@ namespace Umbraco.Web.Routing /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. Uri Uri { get; } + /// + /// Gets the decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + /// /// Gets the assigned (if any) /// diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index faa793c7ff..9ed8a1ee10 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -26,12 +27,16 @@ namespace Umbraco.Web.Routing public PublishedRequestBuilder(Uri uri, IFileService fileService) { Uri = uri; + AbsolutePathDecoded = uri.GetAbsolutePathDecoded(); _fileService = fileService; } /// public Uri Uri { get; } + /// + public string AbsolutePathDecoded { get; } + /// public DomainAndUri Domain { get; private set; } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 8de78dfbf5..96325d1289 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -88,6 +88,10 @@ namespace Umbraco.Web // ie no virtual directory, no .aspx, lowercase... public Uri UriToUmbraco(Uri uri) { + // TODO: Ideally we do this witout so many string allocations, we can use + // techniques like StringSegment and Span. This is critical code that executes on every request. + // not really sure we need ToLower. + // note: no need to decode uri here because we're returning a uri // so it will be re-encoded anyway var path = uri.GetSafeAbsolutePath(); @@ -95,23 +99,11 @@ namespace Umbraco.Web path = path.ToLower(); path = ToAppRelative(path); // strip vdir if any - //we need to check if the path is /default.aspx because this will occur when using a - //web server pre IIS 7 when requesting the root document - //if this is the case we need to change it to '/' - if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); - path = rempath.StartsWith("/") ? rempath : "/" + rempath; - } if (path != "/") { path = path.TrimEnd('/'); } - //if any part of the path contains .aspx, replace it with nothing. - //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate - path = path.Replace(".aspx", ""); - return uri.Rewrite(path); } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 497159309a..53bf2d6d92 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -61,7 +61,9 @@ namespace Umbraco.Core public static string GetSafeAbsolutePath(this Uri uri) { if (uri.IsAbsoluteUri) + { return uri.AbsolutePath; + } // cannot get .AbsolutePath on relative uri (InvalidOperation) var s = uri.OriginalString; diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 5634fa4a93..bc9f9f3857 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -61,7 +61,7 @@ namespace Umbraco.Web.Routing } else { - var route = frequest.Uri.GetAbsolutePathDecoded(); + var route = frequest.AbsolutePathDecoded; var pos = route.LastIndexOf('/'); IPublishedContent node = null; while (pos > 1) diff --git a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index aa39070cc7..bdb703753d 100644 --- a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index c1d0fe3758..dbb3fa1137 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -59,7 +59,7 @@ - + all diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs index 5543a8920a..2d96476b30 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs @@ -79,13 +79,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing } [TestCase("Index", "RenderNotFound", null, false)] - [TestCase("index", "Render", "Index", true)] - [TestCase("Index", "Render1", "Index", true)] - [TestCase("Index", "render2", "Index", true)] - [TestCase("NotFound", "Render", "Index", true)] - [TestCase("NotFound", "Render1", "Index", true)] - [TestCase("NotFound", "Render2", "Index", true)] - [TestCase("Custom", "Render1", "Custom", true)] + [TestCase("index", "Render", nameof(RenderController.Index), true)] + [TestCase("Index", "Render1", nameof(RenderController.Index), true)] + [TestCase("Index", "render2", nameof(Render2Controller.Index), true)] + [TestCase("NotFound", "Render", nameof(RenderController.Index), true)] + [TestCase("NotFound", "Render1", nameof(Render1Controller.Index), true)] + [TestCase("NotFound", "Render2", nameof(Render2Controller.Index), true)] + [TestCase("Custom", "Render1", nameof(Render1Controller.Custom), true)] public void Matches_Controller(string action, string controller, string resultAction, bool matches) { var evaluator = new HijackedRouteEvaluator( diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs new file mode 100644 index 0000000000..0b9e1d6420 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -0,0 +1,170 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Extensions; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing +{ + [TestFixture] + public class UmbracoRouteValueTransformerTests + { + private IOptions GetGlobalSettings() => Options.Create(new GlobalSettings()); + + private UmbracoRouteValueTransformer GetTransformerWithRunState( + IUmbracoContextAccessor ctx, + IRoutableDocumentFilter filter = null, + IPublishedRouter router = null, + IUmbracoRouteValuesFactory routeValuesFactory = null) + => GetTransformer(ctx, Mock.Of(x => x.Level == RuntimeLevel.Run), filter, router, routeValuesFactory); + + private UmbracoRouteValueTransformer GetTransformer( + IUmbracoContextAccessor ctx, + IRuntimeState state, + IRoutableDocumentFilter filter = null, + IPublishedRouter router = null, + IUmbracoRouteValuesFactory routeValuesFactory = null) + { + var transformer = new UmbracoRouteValueTransformer( + new NullLogger(), + ctx, + router ?? Mock.Of(), + GetGlobalSettings(), + TestHelper.GetHostingEnvironment(), + state, + routeValuesFactory ?? Mock.Of(), + filter ?? Mock.Of(x => x.IsDocumentRequest(It.IsAny()) == true)); + + return transformer; + } + + private IUmbracoContext GetUmbracoContext(bool hasContent) + { + IPublishedContentCache publishedContent = Mock.Of(x => x.HasContent() == hasContent); + var uri = new Uri("http://example.com"); + + IUmbracoContext umbracoContext = Mock.Of(x => + x.Content == publishedContent + && x.OriginalRequestUrl == uri + && x.CleanedUmbracoUrl == uri); + + return umbracoContext; + } + + private UmbracoRouteValues GetRouteValues(IPublishedRequest request) + => new UmbracoRouteValues( + request, + ControllerExtensions.GetControllerName(), + typeof(TestController)); + + private IUmbracoRouteValuesFactory GetRouteValuesFactory(IPublishedRequest request) + => Mock.Of(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()) == GetRouteValues(request)); + + private IPublishedRouter GetRouter(IPublishedRequest request) + => Mock.Of(x => x.RouteRequestAsync(It.IsAny(), It.IsAny()) == Task.FromResult(request)); + + [Test] + public async Task Noop_When_Runtime_Level_Not_Run() + { + UmbracoRouteValueTransformer transformer = GetTransformer( + Mock.Of(x => x.UmbracoContext == null), + Mock.Of()); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task Noop_When_No_Umbraco_Context() + { + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == null)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task Noop_When_Not_Document_Request() + { + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == Mock.Of()), + Mock.Of(x => x.IsDocumentRequest(It.IsAny()) == false)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task NoContentController_Values_When_No_Content() + { + IUmbracoContext umbracoContext = GetUmbracoContext(false); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(ControllerExtensions.GetControllerName(), result["controller"]); + Assert.AreEqual(nameof(RenderNoContentController.Index), result["action"]); + } + + [Test] + public async Task Assigns_PublishedRequest_To_UmbracoContext() + { + IUmbracoContext umbracoContext = GetUmbracoContext(true); + IPublishedRequest request = Mock.Of(); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext), + router: GetRouter(request), + routeValuesFactory: GetRouteValuesFactory(request)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(request, umbracoContext.PublishedRequest); + } + + [Test] + public async Task Assigns_Values_To_RouteValueDictionary() + { + IUmbracoContext umbracoContext = GetUmbracoContext(true); + IPublishedRequest request = Mock.Of(); + UmbracoRouteValues routeValues = GetRouteValues(request); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext), + router: GetRouter(request), + routeValuesFactory: GetRouteValuesFactory(request)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + + Assert.AreEqual(routeValues.ControllerName, result["controller"]); + Assert.AreEqual(routeValues.ActionName, result["action"]); + } + + private class TestController : RenderController + { + public TestController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs index ca5329a3f7..17ce59862f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; @@ -18,16 +18,17 @@ using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing { + [TestFixture] public class UmbracoRouteValuesFactoryTests { - private UmbracoRouteValuesFactory GetFactory(IPublishedRouter router, out UmbracoRenderingDefaults renderingDefaults) + private UmbracoRouteValuesFactory GetFactory(out Mock publishedRouter, out UmbracoRenderingDefaults renderingDefaults) { var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); builder.SetPublishedContent(Mock.Of()); IPublishedRequest request = builder.Build(); - var publishedRouter = new Mock(); + publishedRouter = new Mock(); publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) .Returns((IPublishedRequest r) => builder) .Verifiable(); @@ -53,12 +54,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing builder.SetPublishedContent(Mock.Of()); IPublishedRequest request = builder.Build(); - var publishedRouter = new Mock(); - publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) - .Returns((IPublishedRequest r) => builder) - .Verifiable(); - - UmbracoRouteValuesFactory factory = GetFactory(publishedRouter.Object, out _); + UmbracoRouteValuesFactory factory = GetFactory(out Mock publishedRouter, out _); UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), new RouteValueDictionary(), request); @@ -74,7 +70,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing builder.SetTemplate(Mock.Of()); IPublishedRequest request = builder.Build(); - UmbracoRouteValuesFactory factory = GetFactory(Mock.Of(), out UmbracoRenderingDefaults renderingDefaults); + UmbracoRouteValuesFactory factory = GetFactory(out _, out UmbracoRenderingDefaults renderingDefaults); var routeVals = new RouteValueDictionary(); UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), routeVals, request); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e1f57262ff..fe9d41f681 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -112,7 +112,7 @@ - + @@ -307,7 +307,7 @@ - + diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs index c5104c0fdc..cd38712aa0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -61,7 +61,7 @@ namespace Umbraco.Web.Common.AspNetCore 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 + // 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 diff --git a/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs new file mode 100644 index 0000000000..b921918bf6 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Common.Routing +{ + public interface IRoutableDocumentFilter + { + bool IsDocumentRequest(string absPath); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index dee90bbfba..18190f9ad9 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Routing /// /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes. /// - public sealed class RoutableDocumentFilter + public sealed class RoutableDocumentFilter : IRoutableDocumentFilter { private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly GlobalSettings _globalSettings; diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index f7d3e61664..e89a874d71 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { private readonly IHostingEnvironment _hostingEnvironment; + private readonly UriUtility _uriUtility; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; private readonly Lazy _publishedSnapshot; @@ -23,6 +24,8 @@ namespace Umbraco.Web private bool? _previewing; private readonly IBackOfficeSecurity _backofficeSecurity; private readonly UmbracoRequestPaths _umbracoRequestPaths; + private Uri _originalRequestUrl; + private Uri _cleanedUmbracoUrl; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -43,8 +46,8 @@ namespace Umbraco.Web throw new ArgumentNullException(nameof(publishedSnapshotService)); } - VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - + VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _uriUtility = uriUtility; _hostingEnvironment = hostingEnvironment; _cookieManager = cookieManager; _requestAccessor = requestAccessor; @@ -56,15 +59,6 @@ namespace Umbraco.Web // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); - - // set the urls... - // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this - // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people - // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get - // the current domain during application startup. - // see: http://issues.umbraco.org/issue/U4-1890 - OriginalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"); - CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); } /// @@ -79,10 +73,17 @@ namespace Umbraco.Web internal Guid UmbracoRequestId { get; } /// - public Uri OriginalRequestUrl { get; } + // set the urls lazily, no need to allocate until they are needed... + // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this + // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people + // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get + // the current domain during application startup. + // see: http://issues.umbraco.org/issue/U4-1890 + public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost")); /// - public Uri CleanedUmbracoUrl { get; } + // set the urls lazily, no need to allocate until they are needed... + public Uri CleanedUmbracoUrl => _cleanedUmbracoUrl ?? (_cleanedUmbracoUrl = _uriUtility.UriToUmbraco(OriginalRequestUrl)); /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index a641f32235..ca2f9e6161 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.AddDistributedCache(); diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 5d0c564df3..af23105099 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Website.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtime; private readonly IUmbracoRouteValuesFactory _routeValuesFactory; - private readonly RoutableDocumentFilter _routableDocumentFilter; + private readonly IRoutableDocumentFilter _routableDocumentFilter; /// /// Initializes a new instance of the class. @@ -47,16 +47,21 @@ namespace Umbraco.Web.Website.Routing IHostingEnvironment hostingEnvironment, IRuntimeState runtime, IUmbracoRouteValuesFactory routeValuesFactory, - RoutableDocumentFilter routableDocumentFilter) + IRoutableDocumentFilter routableDocumentFilter) { - _logger = logger; - _umbracoContextAccessor = umbracoContextAccessor; - _publishedRouter = publishedRouter; + if (globalSettings is null) + { + throw new System.ArgumentNullException(nameof(globalSettings)); + } + + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _publishedRouter = publishedRouter ?? throw new System.ArgumentNullException(nameof(publishedRouter)); _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - _runtime = runtime; - _routeValuesFactory = routeValuesFactory; - _routableDocumentFilter = routableDocumentFilter; + _hostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment)); + _runtime = runtime ?? throw new System.ArgumentNullException(nameof(runtime)); + _routeValuesFactory = routeValuesFactory ?? throw new System.ArgumentNullException(nameof(routeValuesFactory)); + _routableDocumentFilter = routableDocumentFilter ?? throw new System.ArgumentNullException(nameof(routableDocumentFilter)); } /// @@ -113,9 +118,10 @@ namespace Umbraco.Web.Website.Routing // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? - IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); + IPublishedRequest routedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); + umbracoContext.PublishedRequest = routedRequest; - return publishedRequest; + return routedRequest; } } } diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 7461364d3f..92a480bc45 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -62,7 +62,6 @@ namespace Umbraco.Web // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 - // OriginalRequestUrl = GetRequestFromContext()?.Url ?? new Uri("http://localhost"); CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); }