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
|
// to find out the appropriate template
|
||||||
|
|
||||||
// complete the PCR and assign the remaining values
|
// complete the PCR and assign the remaining values
|
||||||
return ConfigureRequest(request);
|
return BuildRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
|
/// This method finalizes/builds the PCR with the values assigned.
|
||||||
/// finalizes the PCR with the values assigned.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Returns false if the request was not successfully configured
|
/// 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
|
/// 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.
|
/// but need to finalize it themselves.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest)
|
internal IPublishedRequest BuildRequest(IPublishedRequestBuilder frequest)
|
||||||
{
|
{
|
||||||
|
IPublishedRequest result = frequest.Build();
|
||||||
|
|
||||||
if (!frequest.HasPublishedContent())
|
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
|
// set the culture -- again, 'cos it might have changed in the event handler
|
||||||
SetVariationContext(result.Culture);
|
SetVariationContext(result.Culture);
|
||||||
|
|
||||||
@@ -388,7 +387,7 @@ namespace Umbraco.Web.Routing
|
|||||||
// so internal redirect, 404, etc has precedence over redirect
|
// so internal redirect, 404, etc has precedence over redirect
|
||||||
|
|
||||||
// handle not-found, redirects, access...
|
// handle not-found, redirects, access...
|
||||||
HandlePublishedContent(request, foundContentByFinders);
|
HandlePublishedContent(request);
|
||||||
|
|
||||||
// find a template
|
// find a template
|
||||||
FindTemplate(request, foundContentByFinders);
|
FindTemplate(request, foundContentByFinders);
|
||||||
@@ -425,12 +424,11 @@ namespace Umbraco.Web.Routing
|
|||||||
/// Handles the published content (if any).
|
/// Handles the published content (if any).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request builder.</param>
|
/// <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>
|
/// <remarks>
|
||||||
/// Handles "not found", internal redirects, access validation...
|
/// Handles "not found", internal redirects, access validation...
|
||||||
/// things that must be handled in one place because they can create loops
|
/// things that must be handled in one place because they can create loops
|
||||||
/// </remarks>
|
/// </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
|
// because these might loop, we have to have some sort of infinite loop detection
|
||||||
int i = 0, j = 0;
|
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
|
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
|
||||||
j = 0;
|
j = 0;
|
||||||
while (FollowInternalRedirects(request, contentFoundByFinders) && j++ < maxLoop)
|
while (FollowInternalRedirects(request) && j++ < maxLoop)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
// we're running out of control
|
// we're running out of control
|
||||||
@@ -490,13 +488,12 @@ namespace Umbraco.Web.Routing
|
|||||||
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
|
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request builder.</param>
|
/// <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>
|
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
|
/// <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>
|
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private bool FollowInternalRedirects(IPublishedRequestBuilder request, bool contentFoundByFinders)
|
private bool FollowInternalRedirects(IPublishedRequestBuilder request)
|
||||||
{
|
{
|
||||||
if (request.PublishedContent == null)
|
if (request.PublishedContent == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Umbraco.Tests.PublishedContent
|
|||||||
var umbracoContext = GetUmbracoContext("/test");
|
var umbracoContext = GetUmbracoContext("/test");
|
||||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext));
|
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext));
|
||||||
var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||||
var result = publishedRouter.ConfigureRequest(request);
|
var result = publishedRouter.BuildRequest(request);
|
||||||
|
|
||||||
Assert.IsFalse(result.Success());
|
Assert.IsFalse(result.Success());
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedContent
|
|||||||
request.SetPublishedContent(content.Object);
|
request.SetPublishedContent(content.Object);
|
||||||
request.SetCulture(new CultureInfo("en-AU"));
|
request.SetCulture(new CultureInfo("en-AU"));
|
||||||
request.SetRedirect("/hello");
|
request.SetRedirect("/hello");
|
||||||
var result = publishedRouter.ConfigureRequest(request);
|
var result = publishedRouter.BuildRequest(request);
|
||||||
|
|
||||||
Assert.IsFalse(result.Success());
|
Assert.IsFalse(result.Success());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,15 @@ namespace Umbraco.Web.Common.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));
|
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>
|
/// <summary>
|
||||||
/// Before the controller executes we will handle redirects and not founds
|
/// Before the controller executes we will handle redirects and not founds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -126,10 +135,10 @@ namespace Umbraco.Web.Common.Controllers
|
|||||||
IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest;
|
IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest;
|
||||||
|
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
|
"Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
|
||||||
pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
|
pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
|
||||||
pcr.Is404() ? "true" : "false",
|
pcr.Is404() ? "true" : "false",
|
||||||
pcr.ResponseStatusCode);
|
pcr.ResponseStatusCode);
|
||||||
|
|
||||||
UmbracoRouteResult routeStatus = pcr.GetRouteResult();
|
UmbracoRouteResult routeStatus = pcr.GetRouteResult();
|
||||||
switch (routeStatus)
|
switch (routeStatus)
|
||||||
@@ -142,9 +151,8 @@ namespace Umbraco.Web.Common.Controllers
|
|||||||
: Redirect(pcr.RedirectUrl);
|
: Redirect(pcr.RedirectUrl);
|
||||||
break;
|
break;
|
||||||
case UmbracoRouteResult.NotFound:
|
case UmbracoRouteResult.NotFound:
|
||||||
|
|
||||||
// set the redirect result and do not call next to short circuit
|
// set the redirect result and do not call next to short circuit
|
||||||
context.Result = new PublishedContentNotFoundResult(UmbracoContext);
|
context.Result = GetNoTemplateResult(pcr);
|
||||||
break;
|
break;
|
||||||
case UmbracoRouteResult.Success:
|
case UmbracoRouteResult.Success:
|
||||||
default:
|
default:
|
||||||
@@ -153,5 +161,28 @@ namespace Umbraco.Web.Common.Controllers
|
|||||||
break;
|
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.AddDataProtection();
|
||||||
|
|
||||||
builder.Services.AddScoped<UmbracoRouteValueTransformer>();
|
builder.Services.AddScoped<UmbracoRouteValueTransformer>();
|
||||||
|
builder.Services.AddSingleton<HijackedRouteEvaluator>();
|
||||||
|
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
|
||||||
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
|
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
|
||||||
|
|
||||||
builder.AddDistributedCache();
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
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.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
using Umbraco.Core.Composing;
|
|
||||||
using Umbraco.Core.Configuration.Models;
|
using Umbraco.Core.Configuration.Models;
|
||||||
using Umbraco.Core.Hosting;
|
using Umbraco.Core.Hosting;
|
||||||
using Umbraco.Core.Strings;
|
|
||||||
using Umbraco.Extensions;
|
using Umbraco.Extensions;
|
||||||
using Umbraco.Web.Common.Controllers;
|
|
||||||
using Umbraco.Web.Common.Routing;
|
using Umbraco.Web.Common.Routing;
|
||||||
using Umbraco.Web.Routing;
|
using Umbraco.Web.Routing;
|
||||||
using Umbraco.Web.Website.Controllers;
|
using Umbraco.Web.Website.Controllers;
|
||||||
@@ -37,13 +28,11 @@ namespace Umbraco.Web.Website.Routing
|
|||||||
{
|
{
|
||||||
private readonly ILogger<UmbracoRouteValueTransformer> _logger;
|
private readonly ILogger<UmbracoRouteValueTransformer> _logger;
|
||||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||||
private readonly IUmbracoRenderingDefaults _renderingDefaults;
|
|
||||||
private readonly IShortStringHelper _shortStringHelper;
|
|
||||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
|
||||||
private readonly IPublishedRouter _publishedRouter;
|
private readonly IPublishedRouter _publishedRouter;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IHostingEnvironment _hostingEnvironment;
|
private readonly IHostingEnvironment _hostingEnvironment;
|
||||||
private readonly IRuntimeState _runtime;
|
private readonly IRuntimeState _runtime;
|
||||||
|
private readonly IUmbracoRouteValuesFactory _routeValuesFactory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer"/> class.
|
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer"/> class.
|
||||||
@@ -51,23 +40,19 @@ namespace Umbraco.Web.Website.Routing
|
|||||||
public UmbracoRouteValueTransformer(
|
public UmbracoRouteValueTransformer(
|
||||||
ILogger<UmbracoRouteValueTransformer> logger,
|
ILogger<UmbracoRouteValueTransformer> logger,
|
||||||
IUmbracoContextAccessor umbracoContextAccessor,
|
IUmbracoContextAccessor umbracoContextAccessor,
|
||||||
IUmbracoRenderingDefaults renderingDefaults,
|
|
||||||
IShortStringHelper shortStringHelper,
|
|
||||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
|
|
||||||
IPublishedRouter publishedRouter,
|
IPublishedRouter publishedRouter,
|
||||||
IOptions<GlobalSettings> globalSettings,
|
IOptions<GlobalSettings> globalSettings,
|
||||||
IHostingEnvironment hostingEnvironment,
|
IHostingEnvironment hostingEnvironment,
|
||||||
IRuntimeState runtime)
|
IRuntimeState runtime,
|
||||||
|
IUmbracoRouteValuesFactory routeValuesFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_umbracoContextAccessor = umbracoContextAccessor;
|
_umbracoContextAccessor = umbracoContextAccessor;
|
||||||
_renderingDefaults = renderingDefaults;
|
|
||||||
_shortStringHelper = shortStringHelper;
|
|
||||||
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
|
||||||
_publishedRouter = publishedRouter;
|
_publishedRouter = publishedRouter;
|
||||||
_globalSettings = globalSettings.Value;
|
_globalSettings = globalSettings.Value;
|
||||||
_hostingEnvironment = hostingEnvironment;
|
_hostingEnvironment = hostingEnvironment;
|
||||||
_runtime = runtime;
|
_runtime = runtime;
|
||||||
|
_routeValuesFactory = routeValuesFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -104,7 +89,7 @@ namespace Umbraco.Web.Website.Routing
|
|||||||
|
|
||||||
IPublishedRequest publishedRequest = await RouteRequestAsync(_umbracoContextAccessor.UmbracoContext);
|
IPublishedRequest publishedRequest = await RouteRequestAsync(_umbracoContextAccessor.UmbracoContext);
|
||||||
|
|
||||||
UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest);
|
UmbracoRouteValues routeDef = _routeValuesFactory.Create(httpContext, values, publishedRequest);
|
||||||
|
|
||||||
values["controller"] = routeDef.ControllerName;
|
values["controller"] = routeDef.ControllerName;
|
||||||
if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false)
|
if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false)
|
||||||
@@ -115,119 +100,6 @@ namespace Umbraco.Web.Website.Routing
|
|||||||
return await Task.FromResult(values);
|
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)
|
private async Task<IPublishedRequest> RouteRequestAsync(IUmbracoContext umbracoContext)
|
||||||
{
|
{
|
||||||
// ok, process
|
// 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
|
// 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.
|
// 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?
|
// 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
|
return publishedRequest;
|
||||||
// // 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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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