173 lines
7.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|