Splits up UmbracoRouteValueTransformer and trying to figure out this hijacked route conundrum with no template :(
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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).
|
||||
/// </summary>
|
||||
/// <param name="request">The request builder.</param>
|
||||
/// <param name="contentFoundByFinders">If the content was found by the finders, before anything such as 404, redirect... took place.</param>
|
||||
/// <remarks>
|
||||
/// Handles "not found", internal redirects, access validation...
|
||||
/// things that must be handled in one place because they can create loops
|
||||
/// </remarks>
|
||||
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 <c>umbracoInternalRedirectId</c> document property.
|
||||
/// </summary>
|
||||
/// <param name="request">The request builder.</param>
|
||||
/// <param name="contentFoundByFinders">If the content was found by the finders, before anything such as 404, redirect... took place.</param>
|
||||
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
|
||||
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
|
||||
/// </remarks>
|
||||
private bool FollowInternalRedirects(IPublishedRequestBuilder request, bool contentFoundByFinders)
|
||||
private bool FollowInternalRedirects(IPublishedRequestBuilder request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -118,6 +118,15 @@ namespace Umbraco.Web.Common.Controllers
|
||||
/// </summary>
|
||||
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));
|
||||
|
||||
/// <summary>
|
||||
/// The action that renders when there is no template assigned, templates are not disabled and there is no hijacked route
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public IActionResult NoTemplate() => GetNoTemplateResult(UmbracoRouteValues.PublishedRequest);
|
||||
|
||||
/// <summary>
|
||||
/// Before the controller executes we will handle redirects and not founds
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace Umbraco.Web.Website.DependencyInjection
|
||||
builder.Services.AddDataProtection();
|
||||
|
||||
builder.Services.AddScoped<UmbracoRouteValueTransformer>();
|
||||
builder.Services.AddSingleton<HijackedRouteEvaluator>();
|
||||
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
|
||||
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
|
||||
|
||||
builder.AddDistributedCache();
|
||||
|
||||
90
src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs
Normal file
90
src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a custom controller can hijack the current route
|
||||
/// </summary>
|
||||
public class HijackedRouteEvaluator
|
||||
{
|
||||
private readonly ILogger<HijackedRouteEvaluator> _logger;
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
||||
private const string DefaultActionName = nameof(RenderController.Index);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HijackedRouteEvaluator"/> class.
|
||||
/// </summary>
|
||||
public HijackedRouteEvaluator(
|
||||
ILogger<HijackedRouteEvaluator> logger,
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
||||
}
|
||||
|
||||
public HijackedRouteResult Evaluate(string controller, string action)
|
||||
{
|
||||
IReadOnlyList<ControllerActionDescriptor> 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<IRenderController>(controllerDescriptor.ControllerTypeInfo)
|
||||
&& TypeHelper.IsTypeAssignableFrom<ControllerBase>(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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of controller candidates that match the custom controller and action names
|
||||
/// </summary>
|
||||
private IReadOnlyList<ControllerActionDescriptor> FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName)
|
||||
{
|
||||
var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items
|
||||
.Cast<ControllerActionDescriptor>()
|
||||
.Where(x => x.ControllerName.InvariantEquals(customControllerName)
|
||||
&& (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName))))
|
||||
.ToList();
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs
Normal file
50
src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Website.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// The result from evaluating if a route can be hijacked
|
||||
/// </summary>
|
||||
public class HijackedRouteResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a failed result
|
||||
/// </summary>
|
||||
public static HijackedRouteResult Failed() => new HijackedRouteResult(false, null, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HijackedRouteResult"/> class.
|
||||
/// </summary>
|
||||
public HijackedRouteResult(
|
||||
bool success,
|
||||
string controllerName,
|
||||
Type controllerType,
|
||||
string actionName)
|
||||
{
|
||||
Success = success;
|
||||
ControllerName = controllerName;
|
||||
ControllerType = controllerType;
|
||||
ActionName = actionName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating if the route could be hijacked
|
||||
/// </summary>
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Controller name
|
||||
/// </summary>
|
||||
public string ControllerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Controller type
|
||||
/// </summary>
|
||||
public Type ControllerType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Acton name
|
||||
/// </summary>
|
||||
public string ActionName { get; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to create <see cref="UmbracoRouteValues"/>
|
||||
/// </summary>
|
||||
public interface IUmbracoRouteValuesFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates <see cref="UmbracoRouteValues"/>
|
||||
/// </summary>
|
||||
UmbracoRouteValues Create(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbracoRouteValueTransformer> _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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer"/> class.
|
||||
@@ -51,23 +40,19 @@ namespace Umbraco.Web.Website.Routing
|
||||
public UmbracoRouteValueTransformer(
|
||||
ILogger<UmbracoRouteValueTransformer> logger,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IUmbracoRenderingDefaults renderingDefaults,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
|
||||
IPublishedRouter publishedRouter,
|
||||
IOptions<GlobalSettings> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="UmbracoRouteValues"/> object based on the current content request
|
||||
/// </summary>
|
||||
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<ControllerActionDescriptor> 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<IRenderController>(controllerDescriptor.ControllerTypeInfo)
|
||||
&& TypeHelper.IsTypeAssignableFrom<ControllerBase>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of controller candidates that match the custom controller and action names
|
||||
/// </summary>
|
||||
private IReadOnlyList<ControllerActionDescriptor> FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName)
|
||||
{
|
||||
var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items
|
||||
.Cast<ControllerActionDescriptor>()
|
||||
.Where(x => x.ControllerName.InvariantEquals(customControllerName)
|
||||
&& (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName))))
|
||||
.ToList();
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private async Task<IPublishedRequest> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
135
src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs
Normal file
135
src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Used to create <see cref="UmbracoRouteValues"/>
|
||||
/// </summary>
|
||||
public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory
|
||||
{
|
||||
private readonly ILogger<UmbracoRouteValuesFactory> _logger;
|
||||
private readonly IUmbracoRenderingDefaults _renderingDefaults;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly UmbracoFeatures _umbracoFeatures;
|
||||
private readonly HijackedRouteEvaluator _hijackedRouteEvaluator;
|
||||
private readonly Lazy<string> _defaultControllerName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoRouteValuesFactory"/> class.
|
||||
/// </summary>
|
||||
public UmbracoRouteValuesFactory(
|
||||
ILogger<UmbracoRouteValuesFactory> logger,
|
||||
IUmbracoRenderingDefaults renderingDefaults,
|
||||
IShortStringHelper shortStringHelper,
|
||||
UmbracoFeatures umbracoFeatures,
|
||||
HijackedRouteEvaluator hijackedRouteEvaluator)
|
||||
{
|
||||
_logger = logger;
|
||||
_renderingDefaults = renderingDefaults;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_umbracoFeatures = umbracoFeatures;
|
||||
_hijackedRouteEvaluator = hijackedRouteEvaluator;
|
||||
_defaultControllerName = new Lazy<string>(() => ControllerExtensions.GetControllerName(_renderingDefaults.DefaultControllerType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default controller name
|
||||
/// </summary>
|
||||
protected string DefaultControllerName => _defaultControllerName.Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user