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/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index fedfd69dc3..58523d12e4 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -17,6 +17,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 URI decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + /// /// Gets a value indicating the requested content. /// diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index ced443a89c..bd5b5625a3 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 URI decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + /// /// Gets the assigned (if any) /// diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index e42211da49..7a3d44149d 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -13,9 +11,10 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) + public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded)); PublishedContent = publishedContent; IsInternalRedirect = isInternalRedirect; Template = template; @@ -32,6 +31,9 @@ namespace Umbraco.Web.Routing /// public Uri Uri { get; } + /// + public string AbsolutePathDecoded { get; } + /// public bool IgnorePublishedContentCollisions { get; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index faa793c7ff..606031564b 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; } @@ -62,6 +67,7 @@ namespace Umbraco.Web.Routing /// public IPublishedRequest Build() => new PublishedRequest( Uri, + AbsolutePathDecoded, PublishedContent, IsInternalRedirect, Template, diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 8de78dfbf5..43a36db101 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; @@ -55,9 +55,15 @@ namespace Umbraco.Web { if (virtualPath.InvariantStartsWith(_appPathPrefix) && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) + { virtualPath = virtualPath.Substring(_appPathPrefix.Length); + } + if (virtualPath.Length == 0) + { virtualPath = "/"; + } + return virtualPath; } @@ -88,6 +94,9 @@ namespace Umbraco.Web // ie no virtual directory, no .aspx, lowercase... public Uri UriToUmbraco(Uri uri) { + // TODO: This is critical code that executes on every request, we should + // look into if all of this is necessary? 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 +104,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..26580fab84 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -61,10 +61,14 @@ 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; + + // TODO: Shouldn't this just use Uri.GetLeftPart? var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); var pos = posq > 0 ? posq : (posf > 0 ? posf : 0); 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.Core/Routing/UriUtilityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs index dee487621f..4789698fcd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs @@ -23,28 +23,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://LocalHost/Home/Sub1", "http://localhost/home/sub1")] [TestCase("http://LocalHost/Home/Sub1?x=y", "http://localhost/home/sub1?x=y")] - // same with .aspx - [TestCase("http://LocalHost/Home.aspx", "http://localhost/home")] - [TestCase("http://LocalHost/Home.aspx?x=y", "http://localhost/home?x=y")] - [TestCase("http://LocalHost/Home/Sub1.aspx", "http://localhost/home/sub1")] - [TestCase("http://LocalHost/Home/Sub1.aspx?x=y", "http://localhost/home/sub1?x=y")] - // test that the trailing slash goes but not on hostname [TestCase("http://LocalHost/", "http://localhost/")] [TestCase("http://LocalHost/Home/", "http://localhost/home")] [TestCase("http://LocalHost/Home/?x=y", "http://localhost/home?x=y")] [TestCase("http://LocalHost/Home/Sub1/", "http://localhost/home/sub1")] [TestCase("http://LocalHost/Home/Sub1/?x=y", "http://localhost/home/sub1?x=y")] - - // test that default.aspx goes, even with parameters - [TestCase("http://LocalHost/deFault.aspx", "http://localhost/")] - [TestCase("http://LocalHost/deFault.aspx?x=y", "http://localhost/?x=y")] - - // test with inner .aspx - [TestCase("http://Localhost/Home/Sub1.aspx/Sub2", "http://localhost/home/sub1/sub2")] - [TestCase("http://Localhost/Home/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] - [TestCase("http://Localhost/Home.aspx/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] - [TestCase("http://Localhost/deFault.aspx/Home.aspx/deFault.aspx/Sub1.aspx", "http://localhost/home/default/sub1")] public void Uri_To_Umbraco(string sourceUrl, string expectedUrl) { // Arrange diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs new file mode 100644 index 0000000000..2d96476b30 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Primitives; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Extensions; +using Umbraco.Web; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Website.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing +{ + + [TestFixture] + public class HijackedRouteEvaluatorTests + { + private class TestActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider + { + private readonly IEnumerable _actions; + + public TestActionDescriptorCollectionProvider(IEnumerable actions) => _actions = actions; + + public override ActionDescriptorCollection ActionDescriptors => new ActionDescriptorCollection(_actions.ToList(), 1); + + public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; + } + + private IActionDescriptorCollectionProvider GetActionDescriptors() => new TestActionDescriptorCollectionProvider( + new ActionDescriptor[] + { + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(RenderController).GetTypeInfo() + }, + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render1Controller).GetTypeInfo() + }, + new ControllerActionDescriptor + { + ActionName = "Custom", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render1Controller).GetTypeInfo() + }, + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render2Controller).GetTypeInfo() + } + }); + + private class Render1Controller : ControllerBase, IRenderController + { + public IActionResult Index => Content("hello world"); + + public IActionResult Custom => Content("hello world"); + } + + private class Render2Controller : RenderController + { + public Render2Controller(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + } + + [TestCase("Index", "RenderNotFound", null, false)] + [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( + new NullLogger(), + GetActionDescriptors()); + + HijackedRouteResult result = evaluator.Evaluate(controller, action); + Assert.AreEqual(matches, result.Success); + if (matches) + { + Assert.IsTrue(result.ActionName.InvariantEquals(resultAction), "expected {0} does not match resulting action {1}", resultAction, result.ActionName); + Assert.IsTrue(result.ControllerName.InvariantEquals(controller), "expected {0} does not match resulting controller {1}", controller, result.ControllerName); + } + } + } +} 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..a531c77fe1 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -0,0 +1,169 @@ +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.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(), + 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()); + + 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 new file mode 100644 index 0000000000..17ce59862f --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Features; +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 UmbracoRouteValuesFactoryTests + { + 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(); + + publishedRouter = new Mock(); + publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) + .Returns((IPublishedRequest r) => builder) + .Verifiable(); + + renderingDefaults = new UmbracoRenderingDefaults(); + + var factory = new UmbracoRouteValuesFactory( + renderingDefaults, + Mock.Of(), + new UmbracoFeatures(), + new HijackedRouteEvaluator( + new NullLogger(), + Mock.Of()), + publishedRouter.Object); + + return factory; + } + + [Test] + public void Update_Request_To_Not_Found_When_No_Template() + { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(Mock.Of()); + IPublishedRequest request = builder.Build(); + + UmbracoRouteValuesFactory factory = GetFactory(out Mock publishedRouter, out _); + + UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), new RouteValueDictionary(), request); + + // The request has content, no template, no hijacked route and no disabled template features so UpdateRequestToNotFound will be called + publishedRouter.Verify(m => m.UpdateRequestToNotFound(It.IsAny()), Times.Once); + } + + [Test] + public void Adds_Result_To_Route_Value_Dictionary() + { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(Mock.Of()); + builder.SetTemplate(Mock.Of()); + IPublishedRequest request = builder.Build(); + + UmbracoRouteValuesFactory factory = GetFactory(out _, out UmbracoRenderingDefaults renderingDefaults); + + var routeVals = new RouteValueDictionary(); + UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), routeVals, request); + + Assert.IsNotNull(result); + Assert.IsTrue(routeVals.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)); + Assert.AreEqual(result, routeVals[Constants.Web.UmbracoRouteDefinitionDataToken]); + Assert.AreEqual(renderingDefaults.DefaultControllerType, result.ControllerType); + Assert.AreEqual(UmbracoRouteValues.DefaultActionName, result.ActionName); + Assert.IsNull(result.TemplateName); + } + } +} 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/Localization/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs index 741583413c..e2a7c35daa 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web.Common.Localization public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider { private readonly RequestLocalizationOptions _localizationOptions; + private readonly object _locker = new object(); /// /// Initializes a new instance of the class. @@ -31,18 +32,21 @@ namespace Umbraco.Web.Common.Localization return NullProviderCultureResult; } - // We need to dynamically change the supported cultures since we won't ever know what languages are used since - // they are dynamic within Umbraco. - var cultureExists = _localizationOptions.SupportedCultures.Contains(culture); - - if (!cultureExists) + lock(_locker) { - // add this as a supporting culture - _localizationOptions.SupportedCultures.Add(culture); - _localizationOptions.SupportedUICultures.Add(culture); - } + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + var cultureExists = _localizationOptions.SupportedCultures.Contains(culture); - return Task.FromResult(new ProviderCultureResult(culture.Name)); + if (!cultureExists) + { + // add this as a supporting culture + _localizationOptions.SupportedCultures.Add(culture); + _localizationOptions.SupportedUICultures.Add(culture); + } + + return Task.FromResult(new ProviderCultureResult(culture.Name)); + } } } } diff --git a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs index bedf5e73a7..7322ad2869 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.Common.Localization public class UmbracoPublishedContentCultureProvider : RequestCultureProvider { private readonly RequestLocalizationOptions _localizationOptions; + private readonly object _locker = new object(); /// /// Initializes a new instance of the class. @@ -33,19 +34,22 @@ namespace Umbraco.Web.Common.Localization string culture = routeValues.PublishedRequest?.Culture; if (culture != null) { - // We need to dynamically change the supported cultures since we won't ever know what languages are used since - // they are dynamic within Umbraco. - // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo - // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165 - CultureInfo existingCulture = _localizationOptions.SupportedCultures.FirstOrDefault(supportedCulture => - StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase)); - - if (existingCulture == null) + lock (_locker) { - // add this as a supporting culture - var ci = CultureInfo.GetCultureInfo(culture); - _localizationOptions.SupportedCultures.Add(ci); - _localizationOptions.SupportedUICultures.Add(ci); + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo + // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165 + CultureInfo existingCulture = _localizationOptions.SupportedCultures.FirstOrDefault(supportedCulture => + StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase)); + + if (existingCulture == null) + { + // add this as a supporting culture + var ci = CultureInfo.GetCultureInfo(culture); + _localizationOptions.SupportedCultures.Add(ci); + _localizationOptions.SupportedUICultures.Add(ci); + } } return Task.FromResult(new ProviderCultureResult(culture)); 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/HijackedRouteEvaluator.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs index a72268d298..79036a01e1 100644 --- a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs +++ b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs @@ -50,14 +50,22 @@ namespace Umbraco.Web.Website.Routing && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) { // now check if the custom action matches - var customActionExists = action != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(action)); + var resultingAction = DefaultActionName; + if (action != null) + { + var found = customControllerCandidates.FirstOrDefault(x => x.ActionName.InvariantEquals(action))?.ActionName; + if (found != null) + { + resultingAction = found; + } + } // it's a hijacked route with a custom controller, so return the the values return new HijackedRouteResult( true, controllerDescriptor.ControllerName, controllerDescriptor.ControllerTypeInfo, - customActionExists ? action : DefaultActionName); + resultingAction); } else { 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.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index fd92f7f11e..d26216204e 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.Website.Routing { IPublishedRequest request = def.PublishedRequest; - var customControllerName = request.PublishedContent?.ContentType.Alias; + var customControllerName = request.PublishedContent?.ContentType?.Alias; if (customControllerName != null) { HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, def.TemplateName); @@ -144,6 +144,12 @@ namespace Umbraco.Web.Website.Routing // We then need to re-run this through the pipeline for the last // chance finders to work. IPublishedRequestBuilder builder = _publishedRouter.UpdateRequestToNotFound(request); + + if (builder == null) + { + throw new InvalidOperationException($"The call to {nameof(IPublishedRouter.UpdateRequestToNotFound)} cannot return null"); + } + request = builder.Build(); def = new UmbracoRouteValues( 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); }