Files
Umbraco-CMS/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs
2021-02-10 14:30:14 +01:00

173 lines
7.1 KiB
C#

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
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 IUmbracoRenderingDefaults _renderingDefaults;
private readonly IShortStringHelper _shortStringHelper;
private readonly UmbracoFeatures _umbracoFeatures;
private readonly IControllerActionSearcher _controllerActionSearcher;
private readonly IPublishedRouter _publishedRouter;
private readonly Lazy<string> _defaultControllerName;
private readonly Lazy<ControllerActionDescriptor> _defaultControllerDescriptor;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRouteValuesFactory"/> class.
/// </summary>
public UmbracoRouteValuesFactory(
IUmbracoRenderingDefaults renderingDefaults,
IShortStringHelper shortStringHelper,
UmbracoFeatures umbracoFeatures,
IControllerActionSearcher controllerActionSearcher,
IPublishedRouter publishedRouter)
{
_renderingDefaults = renderingDefaults;
_shortStringHelper = shortStringHelper;
_umbracoFeatures = umbracoFeatures;
_controllerActionSearcher = controllerActionSearcher;
_publishedRouter = publishedRouter;
_defaultControllerName = new Lazy<string>(() => ControllerExtensions.GetControllerName(_renderingDefaults.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 UmbracoRouteValues Create(HttpContext httpContext, IPublishedRequest request)
{
if (httpContext is null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
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);
}
// The default values for the default controller/action
var def = new UmbracoRouteValues(
request,
_defaultControllerDescriptor.Value,
templateName: customActionName);
def = CheckHijackedRoute(httpContext, def);
def = CheckNoTemplate(httpContext, def);
return def;
}
/// <summary>
/// Check if the route is hijacked and return new route values
/// </summary>
private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def)
{
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)
{
return new UmbracoRouteValues(
request,
descriptor,
def.TemplateName,
true);
}
}
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 UmbracoRouteValues CheckNoTemplate(HttpContext httpContext, UmbracoRouteValues def)
{
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.
// 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()
&& !_umbracoFeatures.Disabled.DisableTemplates
&& !def.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.
IPublishedRequestBuilder builder = _publishedRouter.UpdateRequestToNotFound(request);
if (builder == null)
{
throw new InvalidOperationException($"The call to {nameof(IPublishedRouter.UpdateRequestToNotFound)} cannot return null");
}
request = builder.Build();
def = new UmbracoRouteValues(
request,
def.ControllerActionDescriptor,
def.TemplateName);
// if the content has changed, we must then again check for hijacked routes
if (content != request.PublishedContent)
{
def = CheckHijackedRoute(httpContext, def);
}
}
return def;
}
}
}