diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index 9f06046452..d94fdd8496 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -10,6 +10,17 @@ namespace Umbraco.Core.Configuration.Models /// public class WebRoutingSettings { + /// + /// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before + /// the Umbraco dynamic router tries to map the request to an Umbraco content item. + /// + /// + /// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In that case + /// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this is what v7/v8 used + /// to do. + /// + public bool TryMatchingEndpointsForAllPages { get; set; } = false; + /// /// Gets or sets a value indicating whether IIS custom errors should be skipped. /// diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index 6fa3328014..e670930691 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -112,7 +112,6 @@ namespace Umbraco.Core.Routing } // check for special front-end paths - // TODO: These should be constants - will need to update when we do front-end routing if (urlPath.InvariantStartsWith(_surfaceMvcPath) || urlPath.InvariantStartsWith(_apiMvcPath)) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs index b25da2351b..1c9cbc9c26 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -18,6 +18,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { private IOptions GetGlobalSettings() => Options.Create(new GlobalSettings()); + private IOptions GetWebRoutingSettings() => Options.Create(new WebRoutingSettings()); + private IHostingEnvironment GetHostingEnvironment() { var hostingEnv = new Mock(); @@ -35,6 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { var routableDocFilter = new RoutableDocumentFilter( GetGlobalSettings(), + GetWebRoutingSettings(), GetHostingEnvironment(), new DefaultEndpointDataSource()); @@ -44,14 +47,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase("/base/somebasehandler")] [TestCase("/")] - [TestCase("/home.aspx")] + [TestCase("/home")] [TestCase("/umbraco-test")] [TestCase("/install-test")] - [TestCase("/install.aspx")] public void Is_Not_Reserved_Path_Or_Url(string url) { var routableDocFilter = new RoutableDocumentFilter( GetGlobalSettings(), + GetWebRoutingSettings(), GetHostingEnvironment(), new DefaultEndpointDataSource()); @@ -73,6 +76,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing public void Is_Reserved_By_Route(string url, bool isReserved) { var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true }; RouteEndpoint endpoint1 = CreateEndpoint( "Umbraco/RenderMvc/{action?}/{id?}", @@ -88,6 +92,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var routableDocFilter = new RoutableDocumentFilter( Options.Create(globalSettings), + Options.Create(routingSettings), GetHostingEnvironment(), endpointDataSource); @@ -105,16 +110,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing public void Is_Reserved_By_Default_Back_Office_Route(string url, bool isReserved) { var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true }; RouteEndpoint endpoint1 = CreateEndpoint( - "umbraco/{action}/{id?}", - new { controller = "BackOffice", action = "Default" }, + "umbraco/{action?}/{id?}", + new { controller = "BackOffice" }, 0); var endpointDataSource = new DefaultEndpointDataSource(endpoint1); var routableDocFilter = new RoutableDocumentFilter( Options.Create(globalSettings), + Options.Create(routingSettings), GetHostingEnvironment(), endpointDataSource); diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 8f61918121..dee90bbfba 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Common.Routing { private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly GlobalSettings _globalSettings; + private readonly WebRoutingSettings _routingSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; private readonly object _routeLocker = new object(); @@ -34,9 +35,10 @@ namespace Umbraco.Web.Common.Routing /// /// Initializes a new instance of the class. /// - public RoutableDocumentFilter(IOptions globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) + public RoutableDocumentFilter(IOptions globalSettings, IOptions routingSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) { _globalSettings = globalSettings.Value; + _routingSettings = routingSettings.Value; _hostingEnvironment = hostingEnvironment; _endpointDataSource = endpointDataSource; _endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null); @@ -67,20 +69,17 @@ namespace Umbraco.Web.Common.Routing // a document request should be // /foo/bar/nil // /foo/bar/nil/ - // /foo/bar/nil.aspx // where /foo is not a reserved path - // TODO: Remove aspx checks - - // if the path contains an extension that is not .aspx + // if the path contains an extension // then it cannot be a document request var extension = Path.GetExtension(absPath); - if (maybeDoc && extension.IsNullOrWhiteSpace() == false && !extension.InvariantEquals(".aspx")) + if (maybeDoc && !extension.IsNullOrWhiteSpace()) { maybeDoc = false; } - // at that point, either we have no extension, or it is .aspx + // at that point we have no extension // if the path is reserved then it cannot be a document request if (maybeDoc && IsReservedPathOrUrl(absPath)) @@ -143,16 +142,9 @@ namespace Umbraco.Web.Common.Routing return true; } - // TODO: We have a problem here: - // For every page that is rendered we are storing the URL and if it's routable in _routeChecks which - // is a small memory leak. Not sure how we work around this since routes are all dynamic and we don't want - // to double route everything on each request. Maybe instead of a growing list it's a list with a max capacity? - // BUT... then do we need to do this at all? So long as the catch all route is registered LAST shouldn't all other routes - // just match before it anyways and then we don't need to check? The strange part is that the "/umbraco" route doesn't automatically - // match so we need to investigate that first. - - // check if the current request matches a route, if so then it is reserved. - var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); + // If configured, check if the current request matches a route, if so then it is reserved, + // else if not configured (default) proceed as normal since we assume the request is for an Umbraco content item. + var hasRoute = _routingSettings.TryMatchingEndpointsForAllPages && _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); if (hasRoute) { return true;