diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs
index ae77abe4a7..a0f7fe6344 100644
--- a/src/Umbraco.Core/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/PublishedRouter.cs
@@ -177,12 +177,11 @@ namespace Umbraco.Web.Routing
// to find out the appropriate template
// complete the PCR and assign the remaining values
- return ConfigureRequest(request);
+ return BuildRequest(request);
}
///
- /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
- /// finalizes the PCR with the values assigned.
+ /// This method finalizes/builds the PCR with the values assigned.
///
///
/// Returns false if the request was not successfully configured
@@ -191,15 +190,15 @@ namespace Umbraco.Web.Routing
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
/// but need to finalize it themselves.
///
- internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest)
+ internal IPublishedRequest BuildRequest(IPublishedRequestBuilder frequest)
{
+ IPublishedRequest result = frequest.Build();
+
if (!frequest.HasPublishedContent())
{
- return frequest.Build();
+ return result;
}
- IPublishedRequest result = frequest.Build();
-
// set the culture -- again, 'cos it might have changed in the event handler
SetVariationContext(result.Culture);
@@ -388,7 +387,7 @@ namespace Umbraco.Web.Routing
// so internal redirect, 404, etc has precedence over redirect
// handle not-found, redirects, access...
- HandlePublishedContent(request, foundContentByFinders);
+ HandlePublishedContent(request);
// find a template
FindTemplate(request, foundContentByFinders);
@@ -425,12 +424,11 @@ namespace Umbraco.Web.Routing
/// Handles the published content (if any).
///
/// The request builder.
- /// If the content was found by the finders, before anything such as 404, redirect... took place.
///
/// Handles "not found", internal redirects, access validation...
/// things that must be handled in one place because they can create loops
///
- private void HandlePublishedContent(IPublishedRequestBuilder request, bool contentFoundByFinders)
+ private void HandlePublishedContent(IPublishedRequestBuilder request)
{
// because these might loop, we have to have some sort of infinite loop detection
int i = 0, j = 0;
@@ -457,7 +455,7 @@ namespace Umbraco.Web.Routing
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
j = 0;
- while (FollowInternalRedirects(request, contentFoundByFinders) && j++ < maxLoop)
+ while (FollowInternalRedirects(request) && j++ < maxLoop)
{ }
// we're running out of control
@@ -490,13 +488,12 @@ namespace Umbraco.Web.Routing
/// Follows internal redirections through the umbracoInternalRedirectId document property.
///
/// The request builder.
- /// If the content was found by the finders, before anything such as 404, redirect... took place.
/// A value indicating whether redirection took place and led to a new published document.
///
/// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.
/// As per legacy, if the redirect does not work, we just ignore it.
///
- private bool FollowInternalRedirects(IPublishedRequestBuilder request, bool contentFoundByFinders)
+ private bool FollowInternalRedirects(IPublishedRequestBuilder request)
{
if (request.PublishedContent == null)
{
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs
index 02ccc69f80..52b76a0021 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs
@@ -22,7 +22,7 @@ namespace Umbraco.Tests.PublishedContent
var umbracoContext = GetUmbracoContext("/test");
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext));
var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
- var result = publishedRouter.ConfigureRequest(request);
+ var result = publishedRouter.BuildRequest(request);
Assert.IsFalse(result.Success());
}
@@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedContent
request.SetPublishedContent(content.Object);
request.SetCulture(new CultureInfo("en-AU"));
request.SetRedirect("/hello");
- var result = publishedRouter.ConfigureRequest(request);
+ var result = publishedRouter.BuildRequest(request);
Assert.IsFalse(result.Success());
}
diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs
index 7f6d61de98..e0df571988 100644
--- a/src/Umbraco.Web.Common/Controllers/RenderController.cs
+++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs
@@ -118,6 +118,15 @@ namespace Umbraco.Web.Common.Controllers
///
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));
+ ///
+ /// The action that renders when there is no template assigned, templates are not disabled and there is no hijacked route
+ ///
+ ///
+ /// This action renders even if there might be content found, but if there is no template assigned, templates are not disabled and there is no hijacked route
+ /// then we render the blank screen.
+ ///
+ public IActionResult NoTemplate() => GetNoTemplateResult(UmbracoRouteValues.PublishedRequest);
+
///
/// Before the controller executes we will handle redirects and not founds
///
@@ -126,10 +135,10 @@ namespace Umbraco.Web.Common.Controllers
IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest;
_logger.LogDebug(
- "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
- pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
- pcr.Is404() ? "true" : "false",
- pcr.ResponseStatusCode);
+ "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
+ pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
+ pcr.Is404() ? "true" : "false",
+ pcr.ResponseStatusCode);
UmbracoRouteResult routeStatus = pcr.GetRouteResult();
switch (routeStatus)
@@ -142,9 +151,8 @@ namespace Umbraco.Web.Common.Controllers
: Redirect(pcr.RedirectUrl);
break;
case UmbracoRouteResult.NotFound:
-
// set the redirect result and do not call next to short circuit
- context.Result = new PublishedContentNotFoundResult(UmbracoContext);
+ context.Result = GetNoTemplateResult(pcr);
break;
case UmbracoRouteResult.Success:
default:
@@ -153,5 +161,28 @@ namespace Umbraco.Web.Common.Controllers
break;
}
}
+
+ private PublishedContentNotFoundResult GetNoTemplateResult(IPublishedRequest pcr)
+ {
+ // missing template, so we're in a 404 here
+ // so the content, if any, is a custom 404 page of some sort
+ if (!pcr.HasPublishedContent())
+ {
+ // means the builder could not find a proper document to handle 404
+ return new PublishedContentNotFoundResult(UmbracoContext);
+ }
+ else if (!pcr.HasTemplate())
+ {
+ // means the engine could find a proper document, but the document has no template
+ // at that point there isn't much we can do
+ return new PublishedContentNotFoundResult(
+ UmbracoContext,
+ "In addition, no template exists to render the custom 404.");
+ }
+ else
+ {
+ return new PublishedContentNotFoundResult(UmbracoContext);
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
index a94ee5e678..c4aae8839f 100644
--- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -37,6 +37,8 @@ namespace Umbraco.Web.Website.DependencyInjection
builder.Services.AddDataProtection();
builder.Services.AddScoped();
+ 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
new file mode 100644
index 0000000000..d617dbbab4
--- /dev/null
+++ b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.Logging;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.Web.Common.Controllers;
+
+namespace Umbraco.Web.Website.Routing
+{
+ ///
+ /// Determines if a custom controller can hijack the current route
+ ///
+ public class HijackedRouteEvaluator
+ {
+ private readonly ILogger _logger;
+ private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
+ private const string DefaultActionName = nameof(RenderController.Index);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HijackedRouteEvaluator(
+ ILogger logger,
+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
+ {
+ _logger = logger;
+ _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
+ }
+
+ public HijackedRouteResult Evaluate(string controller, string action)
+ {
+ IReadOnlyList candidates = FindControllerCandidates(controller, action, DefaultActionName);
+
+ // check if there's a custom controller assigned, base on the document type alias.
+ var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(controller)).ToList();
+
+ // check if that custom controller exists
+ if (customControllerCandidates.Count > 0)
+ {
+ ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0];
+
+ // ensure the controller is of type IRenderController and ControllerBase
+ if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)
+ && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo))
+ {
+ // now check if the custom action matches
+ var customActionExists = action != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(action));
+
+ // 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);
+ }
+ else
+ {
+ _logger.LogWarning(
+ "The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.",
+ controller,
+ controllerDescriptor.ControllerTypeInfo.FullName,
+ typeof(IRenderController).FullName,
+ typeof(ControllerBase).FullName);
+
+ // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults
+ // that have already been set above.
+ }
+ }
+
+ return HijackedRouteResult.Failed();
+ }
+
+ ///
+ /// Return a list of controller candidates that match the custom controller and action names
+ ///
+ private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName)
+ {
+ var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items
+ .Cast()
+ .Where(x => x.ControllerName.InvariantEquals(customControllerName)
+ && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName))))
+ .ToList();
+
+ return descriptors;
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs
new file mode 100644
index 0000000000..f88bdfa2fd
--- /dev/null
+++ b/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Umbraco.Web.Website.Routing
+{
+ ///
+ /// The result from evaluating if a route can be hijacked
+ ///
+ public class HijackedRouteResult
+ {
+ ///
+ /// Returns a failed result
+ ///
+ public static HijackedRouteResult Failed() => new HijackedRouteResult(false, null, null, null);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HijackedRouteResult(
+ bool success,
+ string controllerName,
+ Type controllerType,
+ string actionName)
+ {
+ Success = success;
+ ControllerName = controllerName;
+ ControllerType = controllerType;
+ ActionName = actionName;
+ }
+
+ ///
+ /// Gets a value indicating if the route could be hijacked
+ ///
+ public bool Success { get; }
+
+ ///
+ /// Gets the Controller name
+ ///
+ public string ControllerName { get; }
+
+ ///
+ /// Gets the Controller type
+ ///
+ public Type ControllerType { get; }
+
+ ///
+ /// Gets the Acton name
+ ///
+ public string ActionName { get; }
+ }
+}
diff --git a/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs
new file mode 100644
index 0000000000..7af41d865b
--- /dev/null
+++ b/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Umbraco.Web.Common.Routing;
+using Umbraco.Web.Routing;
+
+namespace Umbraco.Web.Website.Routing
+{
+ ///
+ /// Used to create
+ ///
+ public interface IUmbracoRouteValuesFactory
+ {
+ ///
+ /// Creates
+ ///
+ UmbracoRouteValues Create(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request);
+ }
+}
diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
index 4916faeece..9cb920f434 100644
--- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
+++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
@@ -1,22 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
-using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
-using Umbraco.Core.Strings;
using Umbraco.Extensions;
-using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Routing;
using Umbraco.Web.Website.Controllers;
@@ -37,13 +28,11 @@ namespace Umbraco.Web.Website.Routing
{
private readonly ILogger _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
- private readonly IUmbracoRenderingDefaults _renderingDefaults;
- private readonly IShortStringHelper _shortStringHelper;
- private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
private readonly IPublishedRouter _publishedRouter;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IRuntimeState _runtime;
+ private readonly IUmbracoRouteValuesFactory _routeValuesFactory;
///
/// Initializes a new instance of the class.
@@ -51,23 +40,19 @@ namespace Umbraco.Web.Website.Routing
public UmbracoRouteValueTransformer(
ILogger logger,
IUmbracoContextAccessor umbracoContextAccessor,
- IUmbracoRenderingDefaults renderingDefaults,
- IShortStringHelper shortStringHelper,
- IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
IPublishedRouter publishedRouter,
IOptions globalSettings,
IHostingEnvironment hostingEnvironment,
- IRuntimeState runtime)
+ IRuntimeState runtime,
+ IUmbracoRouteValuesFactory routeValuesFactory)
{
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor;
- _renderingDefaults = renderingDefaults;
- _shortStringHelper = shortStringHelper;
- _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_publishedRouter = publishedRouter;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
_runtime = runtime;
+ _routeValuesFactory = routeValuesFactory;
}
///
@@ -104,7 +89,7 @@ namespace Umbraco.Web.Website.Routing
IPublishedRequest publishedRequest = await RouteRequestAsync(_umbracoContextAccessor.UmbracoContext);
- UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest);
+ UmbracoRouteValues routeDef = _routeValuesFactory.Create(httpContext, values, publishedRequest);
values["controller"] = routeDef.ControllerName;
if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false)
@@ -115,119 +100,6 @@ namespace Umbraco.Web.Website.Routing
return await Task.FromResult(values);
}
- ///
- /// Returns a object based on the current content request
- ///
- private UmbracoRouteValues GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request)
- {
- if (httpContext is null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- if (values is null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (request is null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- Type defaultControllerType = _renderingDefaults.DefaultControllerType;
- var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType);
-
- string customActionName = null;
-
- // check that a template is defined), if it doesn't and there is a hijacked route it will just route
- // to the index Action
- if (request.HasTemplate())
- {
- // the template Alias should always be already saved with a safe name.
- // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
- // with the action name attribute.
- customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper);
- }
-
- // creates the default route definition which maps to the 'UmbracoController' controller
- var def = new UmbracoRouteValues(
- request,
- defaultControllerName,
- defaultControllerType,
- templateName: customActionName);
-
- var customControllerName = request.PublishedContent?.ContentType.Alias;
- if (customControllerName != null)
- {
- def = DetermineHijackedRoute(def, customControllerName, customActionName, request);
- }
-
- // store the route definition
- values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def);
-
- return def;
- }
-
- private UmbracoRouteValues DetermineHijackedRoute(UmbracoRouteValues routeValues, string customControllerName, string customActionName, IPublishedRequest request)
- {
- IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, routeValues.ActionName);
-
- // check if there's a custom controller assigned, base on the document type alias.
- var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(customControllerName)).ToList();
-
- // check if that custom controller exists
- if (customControllerCandidates.Count > 0)
- {
- ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0];
-
- // ensure the controller is of type IRenderController and ControllerBase
- if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)
- && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo))
- {
- // now check if the custom action matches
- var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName));
-
- // it's a hijacked route with a custom controller, so return the the values
- return new UmbracoRouteValues(
- request,
- controllerDescriptor.ControllerName,
- controllerDescriptor.ControllerTypeInfo,
- customActionExists ? customActionName : routeValues.ActionName,
- customActionName,
- true); // Hijacked = true
- }
- else
- {
- _logger.LogWarning(
- "The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.",
- request.PublishedContent.ContentType.Alias,
- controllerDescriptor.ControllerTypeInfo.FullName,
- typeof(IRenderController).FullName,
- typeof(ControllerBase).FullName);
-
- // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults
- // that have already been set above.
- }
- }
-
- return routeValues;
- }
-
- ///
- /// Return a list of controller candidates that match the custom controller and action names
- ///
- private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName)
- {
- var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items
- .Cast()
- .Where(x => x.ControllerName.InvariantEquals(customControllerName)
- && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName))))
- .ToList();
-
- return descriptors;
- }
-
private async Task RouteRequestAsync(IUmbracoContext umbracoContext)
{
// ok, process
@@ -240,20 +112,9 @@ 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?
- return umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder);
+ IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder);
- // // HandleHttpResponseStatus returns a value indicating that the request should
- // // not be processed any further, eg because it has been redirect. then, exit.
- // if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger))
- // return;
- // if (!request.HasPublishedContent == false)
- // {
- // // httpContext.RemapHandler(new PublishedContentNotFoundHandler());
- // }
- // else
- // {
- // // RewriteToUmbracoHandler(httpContext, request);
- // }
+ return publishedRequest;
}
}
}
diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs
new file mode 100644
index 0000000000..b101cf155a
--- /dev/null
+++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs
@@ -0,0 +1,135 @@
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.Logging;
+using Umbraco.Core;
+using Umbraco.Core.Strings;
+using Umbraco.Extensions;
+using Umbraco.Web.Common.Controllers;
+using Umbraco.Web.Common.Routing;
+using Umbraco.Web.Features;
+using Umbraco.Web.Routing;
+using Umbraco.Web.Website.Controllers;
+
+namespace Umbraco.Web.Website.Routing
+{
+
+ ///
+ /// Used to create
+ ///
+ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory
+ {
+ private readonly ILogger _logger;
+ private readonly IUmbracoRenderingDefaults _renderingDefaults;
+ private readonly IShortStringHelper _shortStringHelper;
+ private readonly UmbracoFeatures _umbracoFeatures;
+ private readonly HijackedRouteEvaluator _hijackedRouteEvaluator;
+ private readonly Lazy _defaultControllerName;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UmbracoRouteValuesFactory(
+ ILogger logger,
+ IUmbracoRenderingDefaults renderingDefaults,
+ IShortStringHelper shortStringHelper,
+ UmbracoFeatures umbracoFeatures,
+ HijackedRouteEvaluator hijackedRouteEvaluator)
+ {
+ _logger = logger;
+ _renderingDefaults = renderingDefaults;
+ _shortStringHelper = shortStringHelper;
+ _umbracoFeatures = umbracoFeatures;
+ _hijackedRouteEvaluator = hijackedRouteEvaluator;
+ _defaultControllerName = new Lazy(() => ControllerExtensions.GetControllerName(_renderingDefaults.DefaultControllerType));
+ }
+
+ ///
+ /// Gets the default controller name
+ ///
+ protected string DefaultControllerName => _defaultControllerName.Value;
+
+ ///
+ public UmbracoRouteValues Create(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request)
+ {
+ if (httpContext is null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (values is null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ if (request is null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ Type defaultControllerType = _renderingDefaults.DefaultControllerType;
+
+ string customActionName = null;
+
+ // check that a template is defined), if it doesn't and there is a hijacked route it will just route
+ // to the index Action
+ if (request.HasTemplate())
+ {
+ // the template Alias should always be already saved with a safe name.
+ // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
+ // with the action name attribute.
+ customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper);
+ }
+
+ // creates the default route definition which maps to the 'UmbracoController' controller
+ var def = new UmbracoRouteValues(
+ request,
+ DefaultControllerName,
+ defaultControllerType,
+ templateName: customActionName);
+
+ var customControllerName = request.PublishedContent?.ContentType.Alias;
+ if (customControllerName != null)
+ {
+ HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, customActionName);
+ if (hijackedResult.Success)
+ {
+ def = new UmbracoRouteValues(
+ request,
+ hijackedResult.ControllerName,
+ hijackedResult.ControllerType,
+ hijackedResult.ActionName,
+ customActionName,
+ true);
+ }
+ }
+
+ // Here we need to check if there is no hijacked route and no template assigned,
+ // if this is the case we want to return a blank page.
+ // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template,
+ // for example for json rendering in headless.
+ if (!request.HasTemplate()
+ && !_umbracoFeatures.Disabled.DisableTemplates
+ && !def.HasHijackedRoute)
+ {
+ // TODO: this is basically a 404, in v8 this will re-run the pipeline
+ // in order to see if a last chance finder finds some content
+ // At this point we're already done building the request and we have an immutable
+ // request. in v8 it was re-mutated :/ I really don't want to do that
+
+ // In this case we'll render the NoTemplate action
+ def = new UmbracoRouteValues(
+ request,
+ DefaultControllerName,
+ defaultControllerType,
+ nameof(RenderController.NoTemplate));
+ }
+
+ // store the route definition
+ values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def);
+
+ return def;
+ }
+ }
+}