Splits up UmbracoRouteValueTransformer and trying to figure out this hijacked route conundrum with no template :(

This commit is contained in:
Shannon
2021-01-08 02:10:13 +11:00
parent 53bc92608a
commit bc6768101e
9 changed files with 351 additions and 167 deletions

View File

@@ -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)
{ {

View File

@@ -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());
} }

View File

@@ -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);
}
}
} }
} }

View File

@@ -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();

View 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;
}
}
}

View 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; }
}
}

View File

@@ -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);
}
}

View File

@@ -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);
// }
} }
} }
} }

View 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;
}
}
}