186 lines
7.0 KiB
C#
186 lines
7.0 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
using Microsoft.Extensions.Options;
|
|
using Umbraco.Cms.Core.Features;
|
|
using Umbraco.Cms.Core.Models.PublishedContent;
|
|
using Umbraco.Cms.Core.Routing;
|
|
using Umbraco.Cms.Core.Strings;
|
|
using Umbraco.Cms.Web.Common.Controllers;
|
|
using Umbraco.Cms.Web.Common.Routing;
|
|
using Umbraco.Cms.Web.Website.Controllers;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.Web.Website.Routing;
|
|
|
|
/// <summary>
|
|
/// Used to create <see cref="UmbracoRouteValues" />
|
|
/// </summary>
|
|
public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory
|
|
{
|
|
private readonly IControllerActionSearcher _controllerActionSearcher;
|
|
private readonly Lazy<ControllerActionDescriptor> _defaultControllerDescriptor;
|
|
private readonly Lazy<string> _defaultControllerName;
|
|
private readonly IPublishedRouter _publishedRouter;
|
|
private readonly IShortStringHelper _shortStringHelper;
|
|
private readonly UmbracoFeatures _umbracoFeatures;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="UmbracoRouteValuesFactory" /> class.
|
|
/// </summary>
|
|
public UmbracoRouteValuesFactory(
|
|
IOptions<UmbracoRenderingDefaultsOptions> renderingDefaults,
|
|
IShortStringHelper shortStringHelper,
|
|
UmbracoFeatures umbracoFeatures,
|
|
IControllerActionSearcher controllerActionSearcher,
|
|
IPublishedRouter publishedRouter)
|
|
{
|
|
_shortStringHelper = shortStringHelper;
|
|
_umbracoFeatures = umbracoFeatures;
|
|
_controllerActionSearcher = controllerActionSearcher;
|
|
_publishedRouter = publishedRouter;
|
|
_defaultControllerName = new Lazy<string>(() =>
|
|
ControllerExtensions.GetControllerName(renderingDefaults.Value.DefaultControllerType));
|
|
_defaultControllerDescriptor = new Lazy<ControllerActionDescriptor>(() =>
|
|
{
|
|
ControllerActionDescriptor? descriptor = _controllerActionSearcher.Find<IRenderController>(
|
|
new DefaultHttpContext(), // this actually makes no difference for this method
|
|
DefaultControllerName,
|
|
UmbracoRouteValues.DefaultActionName);
|
|
|
|
if (descriptor == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"No controller/action found by name {DefaultControllerName}.{UmbracoRouteValues.DefaultActionName}");
|
|
}
|
|
|
|
return descriptor;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the default controller name
|
|
/// </summary>
|
|
protected string DefaultControllerName => _defaultControllerName.Value;
|
|
|
|
/// <inheritdoc />
|
|
public async Task<UmbracoRouteValues> CreateAsync(HttpContext httpContext, IPublishedRequest request)
|
|
{
|
|
if (httpContext is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(httpContext));
|
|
}
|
|
|
|
if (request is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(request));
|
|
}
|
|
|
|
string? customActionName = GetTemplateName(request);
|
|
|
|
// The default values for the default controller/action
|
|
var def = new UmbracoRouteValues(
|
|
request,
|
|
_defaultControllerDescriptor.Value,
|
|
customActionName);
|
|
|
|
def = CheckHijackedRoute(httpContext, def, out var hasHijackedRoute);
|
|
|
|
def = await CheckNoTemplateAsync(httpContext, def, hasHijackedRoute);
|
|
|
|
return def;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the route is hijacked and return new route values
|
|
/// </summary>
|
|
private UmbracoRouteValues CheckHijackedRoute(
|
|
HttpContext httpContext, UmbracoRouteValues def, out bool hasHijackedRoute)
|
|
{
|
|
IPublishedRequest request = def.PublishedRequest;
|
|
|
|
var customControllerName = request.PublishedContent?.ContentType?.Alias;
|
|
if (customControllerName != null)
|
|
{
|
|
ControllerActionDescriptor? descriptor =
|
|
_controllerActionSearcher.Find<IRenderController>(httpContext, customControllerName, def.TemplateName);
|
|
if (descriptor != null)
|
|
{
|
|
hasHijackedRoute = true;
|
|
|
|
return new UmbracoRouteValues(
|
|
request,
|
|
descriptor,
|
|
def.TemplateName);
|
|
}
|
|
}
|
|
|
|
hasHijackedRoute = false;
|
|
return def;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special check for when no template or hijacked route is done which needs to re-run through the routing pipeline
|
|
/// again for last chance finders
|
|
/// </summary>
|
|
private async Task<UmbracoRouteValues> CheckNoTemplateAsync(
|
|
HttpContext httpContext, UmbracoRouteValues def, bool hasHijackedRoute)
|
|
{
|
|
IPublishedRequest request = def.PublishedRequest;
|
|
|
|
// Here we need to check if there is no hijacked route and no template assigned but there is a content item.
|
|
// If this is the case we want to return a blank page, the only exception being if the content item has a redirect field present.
|
|
// 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.HasPublishedContent()
|
|
&& !request.HasTemplate()
|
|
&& !request.IsRedirect()
|
|
&& !_umbracoFeatures.Disabled.DisableTemplates
|
|
&& !hasHijackedRoute)
|
|
{
|
|
IPublishedContent? content = request.PublishedContent;
|
|
|
|
// This is basically a 404 even if there is content found.
|
|
// We then need to re-run this through the pipeline for the last
|
|
// chance finders to work.
|
|
// Set to null since we are telling it there is no content.
|
|
request = await _publishedRouter.UpdateRequestAsync(request, null);
|
|
|
|
if (request == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"The call to {nameof(IPublishedRouter.UpdateRequestAsync)} cannot return null");
|
|
}
|
|
|
|
string? customActionName = GetTemplateName(request);
|
|
|
|
def = new UmbracoRouteValues(
|
|
request,
|
|
def.ControllerActionDescriptor,
|
|
customActionName);
|
|
|
|
// if the content has changed, we must then again check for hijacked routes
|
|
if (content != request.PublishedContent)
|
|
{
|
|
def = CheckHijackedRoute(httpContext, def, out _);
|
|
}
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
private string? GetTemplateName(IPublishedRequest request)
|
|
{
|
|
// 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.
|
|
return request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|