diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs
index 3d6ab51276..57b38dbff8 100644
--- a/src/Umbraco.Core/Routing/IPublishedRequest.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs
@@ -6,7 +6,9 @@ using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Routing
{
-
+ ///
+ /// The result of Umbraco routing built with the
+ ///
public interface IPublishedRequest
{
///
@@ -15,11 +17,6 @@ namespace Umbraco.Web.Routing
/// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
Uri Uri { get; }
- ///
- /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
- ///
- bool IgnorePublishedContentCollisions { get; }
-
///
/// Gets a value indicating the requested content.
///
@@ -73,8 +70,22 @@ namespace Umbraco.Web.Routing
IReadOnlyDictionary Headers { get; }
///
- /// Gets a value indicating if the no-cache value should be added to the Cache-Control header
+ /// Gets a value indicating whether the no-cache value should be added to the Cache-Control header
///
bool SetNoCacheHeader { get; }
+
+ ///
+ /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
+ ///
+ ///
+ /// This is an uncommon API used for edge cases with complex routing and would be used
+ /// by developers to configure the request to disable collision checks in .
+ /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since
+ /// collission checking only occurs in the back office which is launched by
+ /// for which events do not execute.
+ /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345
+ /// but it's still unclear how this was used.
+ ///
+ bool IgnorePublishedContentCollisions { get; }
}
}
diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs
index 706315795e..f8e5837838 100644
--- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs
@@ -138,5 +138,19 @@ namespace Umbraco.Web.Routing
/// Sets a dictionary of Headers to append to the Response object.
///
IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers);
+
+ ///
+ /// Can be called to configure the result to ignore URL collisions
+ ///
+ ///
+ /// This is an uncommon API used for edge cases with complex routing and would be used
+ /// by developers to configure the request to disable collision checks in .
+ /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since
+ /// collission checking only occurs in the back office which is launched by
+ /// for which events do not execute.
+ /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345
+ /// but it's still unclear how this was used.
+ ///
+ void IgnorePublishedContentCollisions();
}
}
diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs
index 6d974fcc44..b4c35c0e4d 100644
--- a/src/Umbraco.Core/Routing/IPublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
-using Umbraco.Core.Models;
namespace Umbraco.Web.Routing
{
@@ -10,8 +8,6 @@ namespace Umbraco.Web.Routing
///
public interface IPublishedRouter
{
- // TODO: consider this and UmbracoRouteValueTransformer - move some code around?
-
///
/// Creates a published request.
///
@@ -20,18 +16,12 @@ namespace Umbraco.Web.Routing
Task CreateRequestAsync(Uri uri);
///
- /// Prepares a request for rendering.
+ /// Sends a through the routing pipeline and builds a result.
///
/// The request.
- /// A value indicating whether the request was successfully prepared and can be rendered.
- Task RouteRequestAsync(IPublishedRequestBuilder request);
-
- ///
- /// Tries to route a request.
- ///
- /// The request.
- /// A value indicating whether the request can be routed to a document.
- Task TryRouteRequestAsync(IPublishedRequestBuilder request);
+ /// The options.
+ /// The built instance.
+ Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options);
///
/// Updates the request to "not found".
diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs
index 7f1f8b63b5..bb1c28cab1 100644
--- a/src/Umbraco.Core/Routing/PublishedRequest.cs
+++ b/src/Umbraco.Core/Routing/PublishedRequest.cs
@@ -13,11 +13,9 @@ namespace Umbraco.Web.Routing
///
/// Initializes a new instance of the class.
///
- public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache)
+ public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache, bool ignorePublishedContentCollisions)
{
Uri = uri ?? throw new ArgumentNullException(nameof(uri));
- // TODO: What is this?
- //IgnorePublishedContentCollisions = ignorePublishedContentCollisions;
PublishedContent = publishedContent;
IsInternalRedirect = isInternalRedirect;
Template = template;
@@ -28,6 +26,7 @@ namespace Umbraco.Web.Routing
CacheExtensions = cacheExtensions;
Headers = headers;
SetNoCacheHeader = cacheabilityNoCache;
+ IgnorePublishedContentCollisions = ignorePublishedContentCollisions;
}
///
diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs
index 77c3420399..4230e73a78 100644
--- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs
+++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs
@@ -18,6 +18,7 @@ namespace Umbraco.Web.Routing
private string _redirectUrl;
private HttpStatusCode? _responseStatus;
private IPublishedContent _publishedContent;
+ private bool _ignorePublishedContentCollisions;
///
/// Initializes a new instance of the class.
@@ -70,7 +71,8 @@ namespace Umbraco.Web.Routing
_responseStatus.HasValue ? (int?)_responseStatus : null,
_cacheExtensions,
_headers,
- _cacheability);
+ _cacheability,
+ _ignorePublishedContentCollisions);
///
public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability)
@@ -190,5 +192,8 @@ namespace Umbraco.Web.Routing
Template = model;
return true;
}
+
+ ///
+ public void IgnorePublishedContentCollisions() => _ignorePublishedContentCollisions = true;
}
}
diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs
index 70049660ca..c17eb3e2b7 100644
--- a/src/Umbraco.Core/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/PublishedRouter.cs
@@ -100,26 +100,25 @@ namespace Umbraco.Web.Routing
return publishedRequestBuilder;
}
- ///
- public Task TryRouteRequestAsync(IPublishedRequestBuilder request)
+ private IPublishedRequest TryRouteRequest(IPublishedRequestBuilder request)
{
FindDomain(request);
// TODO: This was ported from v8 but how could it possibly have a redirect here?
if (request.IsRedirect())
{
- return Task.FromResult(false);
+ return request.Build();
}
// TODO: This was ported from v8 but how could it possibly have content here?
if (request.HasPublishedContent())
{
- return Task.FromResult(true);
+ return request.Build();
}
FindPublishedContent(request);
- return Task.FromResult(request.Build().Success());
+ return request.Build();
}
private void SetVariationContext(CultureInfo culture)
@@ -138,11 +137,18 @@ namespace Umbraco.Web.Routing
}
///
- public async Task RouteRequestAsync(IPublishedRequestBuilder request)
+ public async Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options)
{
+ // outbound routing performs different/simpler logic
+ if (options.RouteDirection == RouteDirection.Outbound)
+ {
+ return TryRouteRequest(request);
+ }
+
// find domain
FindDomain(request);
+ // TODO: This was ported from v8 but how could it possibly have a redirect here?
// if request has been flagged to redirect then return
// whoever called us is in charge of actually redirecting
if (request.IsRedirect())
@@ -156,7 +162,8 @@ namespace Umbraco.Web.Routing
// find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
- if (request.PublishedContent == null)
+ // TODO: This might very well change when we look into porting custom routes in netcore like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler.
+ if (!request.HasPublishedContent())
{
// find the document & template
FindPublishedContentAndTemplate(request);
diff --git a/src/Umbraco.Core/Routing/RouteDirection.cs b/src/Umbraco.Core/Routing/RouteDirection.cs
new file mode 100644
index 0000000000..1d811b06e3
--- /dev/null
+++ b/src/Umbraco.Core/Routing/RouteDirection.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// The direction of a route
+ ///
+ public enum RouteDirection
+ {
+ ///
+ /// An inbound route used to map a URL to a content item
+ ///
+ Inbound = 1,
+
+ ///
+ /// An outbound route used to generate a URL for a content item
+ ///
+ Outbound = 2
+ }
+}
diff --git a/src/Umbraco.Core/Routing/RouteRequestOptions.cs b/src/Umbraco.Core/Routing/RouteRequestOptions.cs
new file mode 100644
index 0000000000..91a343e2f0
--- /dev/null
+++ b/src/Umbraco.Core/Routing/RouteRequestOptions.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Options for routing an Umbraco request
+ ///
+ public struct RouteRequestOptions : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public RouteRequestOptions(RouteDirection direction) => RouteDirection = direction;
+
+ ///
+ /// Gets the
+ ///
+ public RouteDirection RouteDirection { get; }
+
+ ///
+ public override bool Equals(object obj) => obj is RouteRequestOptions options && Equals(options);
+
+ ///
+ public bool Equals(RouteRequestOptions other) => RouteDirection == other.RouteDirection;
+
+ ///
+ public override int GetHashCode() => 15391035 + RouteDirection.GetHashCode();
+ }
+}
diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
index e7095feb2b..698b9ab526 100644
--- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
@@ -204,20 +204,19 @@ namespace Umbraco.Web.Routing
}
uri = uriUtility.UriToUmbraco(uri);
- IPublishedRequestBuilder pcr = await publishedRouter.CreateRequestAsync(uri);
- var routeResult = await publishedRouter.TryRouteRequestAsync(pcr);
+ IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri);
+ IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
- if (pcr.PublishedContent == null)
+ if (!pcr.HasPublishedContent())
{
var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
return Attempt.Succeed(urlInfo);
}
- // TODO: What is this?
- //if (pcr.IgnorePublishedContentCollisions)
- //{
- // return false;
- //}
+ if (pcr.IgnorePublishedContentCollisions)
+ {
+ return Attempt.Fail();
+ }
if (pcr.PublishedContent.Id != content.Id)
{
diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
index 9cb920f434..de6cb72edb 100644
--- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
+++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
@@ -11,6 +11,7 @@ using Umbraco.Extensions;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Routing;
using Umbraco.Web.Website.Controllers;
+using RouteDirection = Umbraco.Web.Routing.RouteDirection;
namespace Umbraco.Web.Website.Routing
{
@@ -112,7 +113,7 @@ 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);
+ IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound));
return publishedRequest;
}
diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs
index aef8b4bc3e..f45371707a 100644
--- a/src/Umbraco.Web/UmbracoInjectedModule.cs
+++ b/src/Umbraco.Web/UmbracoInjectedModule.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.Security;
using Umbraco.Web.Composing;
using Umbraco.Web.Routing;
+using RouteDirection = Umbraco.Web.Routing.RouteDirection;
namespace Umbraco.Web
{
@@ -136,7 +137,7 @@ namespace Umbraco.Web
// instantiate, prepare and process the published content request
// important to use CleanedUmbracoUrl - lowercase path-only version of the current URL
var requestBuilder = _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl).Result;
- var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder).Result;
+ var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)).Result;
// NOTE: This has been ported to netcore
// HandleHttpResponseStatus returns a value indicating that the request should