Changes how controllers are discovered, re-uses aspnetcore to do this, rely directly on the resolved ControllerActionDescriptor since this is how routing works anyways and also saves future lookups (perf), gets the UmbracoPageResult 'working' - at least to proxy a controller execution but now we need to do the model state merging, etc...

This commit is contained in:
Shannon
2021-02-04 13:09:28 +11:00
parent 57f3de7652
commit eda98aa41f
14 changed files with 224 additions and 226 deletions

View File

@@ -205,7 +205,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders
var httpContext = new DefaultHttpContext();
var routeData = new RouteData();
httpContext.Features.Set(new UmbracoRouteValues(publishedRequest));
httpContext.Features.Set(new UmbracoRouteValues(publishedRequest, null));
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var metadataProvider = new EmptyModelMetadataProvider();

View File

@@ -125,7 +125,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
builder.SetPublishedContent(content);
IPublishedRequest publishedRequest = builder.Build();
var routeDefinition = new UmbracoRouteValues(publishedRequest);
var routeDefinition = new UmbracoRouteValues(publishedRequest, null);
var httpContext = new DefaultHttpContext();
httpContext.Features.Set(routeDefinition);

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
@@ -10,6 +11,7 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Extensions;
@@ -88,12 +90,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing
[TestCase("Custom", "Render1", nameof(Render1Controller.Custom), true)]
public void Matches_Controller(string action, string controller, string resultAction, bool matches)
{
IActionDescriptorCollectionProvider descriptors = GetActionDescriptors();
// TODO: Mock this more so that these tests work
IActionSelector actionSelector = Mock.Of<IActionSelector>();
var query = new ControllerActionSearcher(
new NullLogger<ControllerActionSearcher>(),
GetActionDescriptors());
actionSelector);
ControllerActionSearchResult result = query.Find<IRenderController>(controller, action);
Assert.AreEqual(matches, result.Success);
var httpContext = new DefaultHttpContext();
ControllerActionDescriptor result = query.Find<IRenderController>(httpContext, controller, action);
Assert.IsTrue(matches == (result != null));
if (matches)
{
Assert.IsTrue(result.ActionName.InvariantEquals(resultAction), "expected {0} does not match resulting action {1}", resultAction, result.ActionName);

View File

@@ -1,7 +1,9 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
@@ -73,8 +75,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing
private UmbracoRouteValues GetRouteValues(IPublishedRequest request)
=> new UmbracoRouteValues(
request,
ControllerExtensions.GetControllerName<TestController>(),
typeof(TestController));
new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(TestController).GetTypeInfo(),
ControllerName = ControllerExtensions.GetControllerName<TestController>()
});
private IUmbracoRouteValuesFactory GetRouteValuesFactory(IPublishedRequest request)
=> Mock.Of<IUmbracoRouteValuesFactory>(x => x.Create(It.IsAny<HttpContext>(), It.IsAny<IPublishedRequest>()) == GetRouteValues(request));

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing
new UmbracoFeatures(),
new ControllerActionSearcher(
new NullLogger<ControllerActionSearcher>(),
Mock.Of<IActionDescriptorCollectionProvider>()),
Mock.Of<IActionSelector>()),
publishedRouter.Object);
return factory;

View File

@@ -1,4 +1,5 @@
using System;
using Microsoft.AspNetCore.Mvc.Controllers;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Extensions;
using Umbraco.Web.Common.Controllers;
@@ -21,29 +22,25 @@ namespace Umbraco.Web.Common.Routing
/// </summary>
public UmbracoRouteValues(
IPublishedRequest publishedRequest,
string controllerName = null,
Type controllerType = null,
string actionName = DefaultActionName,
ControllerActionDescriptor controllerActionDescriptor,
string templateName = null,
bool hasHijackedRoute = false)
{
ControllerName = controllerName ?? ControllerExtensions.GetControllerName<RenderController>();
ControllerType = controllerType ?? typeof(RenderController);
PublishedRequest = publishedRequest;
ControllerActionDescriptor = controllerActionDescriptor;
HasHijackedRoute = hasHijackedRoute;
ActionName = actionName;
TemplateName = templateName;
}
/// <summary>
/// Gets the controller name
/// </summary>
public string ControllerName { get; }
public string ControllerName => ControllerActionDescriptor.ControllerName;
/// <summary>
/// Gets the action name
/// </summary>
public string ActionName { get; }
public string ActionName => ControllerActionDescriptor.ActionName;
/// <summary>
/// Gets the template name
@@ -51,9 +48,14 @@ namespace Umbraco.Web.Common.Routing
public string TemplateName { get; }
/// <summary>
/// Gets the Controller type found for routing to
/// Gets the controller type
/// </summary>
public Type ControllerType { get; }
public Type ControllerType => ControllerActionDescriptor.ControllerTypeInfo;
/// <summary>
/// Gets the Controller descriptor found for routing to
/// </summary>
public ControllerActionDescriptor ControllerActionDescriptor { get; }
/// <summary>
/// Gets the <see cref="IPublishedRequest"/>

View File

@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -14,7 +15,10 @@ using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Extensions;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Website.Controllers;
using Umbraco.Web.Website.Routing;
namespace Umbraco.Web.Website.ActionResults
{
@@ -30,49 +34,65 @@ namespace Umbraco.Web.Website.ActionResults
_profilingLogger = profilingLogger;
}
public Task ExecuteResultAsync(ActionContext context)
/// <inheritdoc/>
public async Task ExecuteResultAsync(ActionContext context)
{
var routeData = context.RouteData;
ResetRouteData(routeData);
ValidateRouteData(context);
IControllerFactory factory = context.HttpContext.RequestServices.GetRequiredService<IControllerFactory>();
Controller controller = null;
if (!(context is ControllerContext controllerContext))
UmbracoRouteValues umbracoRouteValues = context.HttpContext.Features.Get<UmbracoRouteValues>();
if (umbracoRouteValues == null)
{
return Task.FromCanceled(CancellationToken.None);
throw new InvalidOperationException($"Can only use {nameof(UmbracoPageResult)} in the context of an Http POST when using a {nameof(SurfaceController)} form");
}
try
{
controller = CreateController(controllerContext, factory);
// Change the route values back to the original request vals
context.RouteData.Values[UmbracoRouteValueTransformer.ControllerToken] = umbracoRouteValues.ControllerName;
context.RouteData.Values[UmbracoRouteValueTransformer.ActionToken] = umbracoRouteValues.ActionName;
CopyControllerData(controllerContext, controller);
// Create a new context and excute the original controller
ExecuteControllerAction(controllerContext, controller);
}
finally
{
CleanupController(controllerContext, controller, factory);
}
// TODO: We need to take into account temp data, view data, etc... all like what we used to do below
// so that validation stuff gets carried accross
return Task.CompletedTask;
var renderActionContext = new ActionContext(context.HttpContext, context.RouteData, umbracoRouteValues.ControllerActionDescriptor);
IActionInvokerFactory actionInvokerFactory = context.HttpContext.RequestServices.GetRequiredService<IActionInvokerFactory>();
IActionInvoker actionInvoker = actionInvokerFactory.CreateInvoker(renderActionContext);
await ExecuteControllerAction(actionInvoker);
//ResetRouteData(context.RouteData);
//ValidateRouteData(context);
//IControllerFactory factory = context.HttpContext.RequestServices.GetRequiredService<IControllerFactory>();
//Controller controller = null;
//if (!(context is ControllerContext controllerContext))
//{
// // TODO: Better to throw since this is not expected?
// return Task.FromCanceled(CancellationToken.None);
//}
//try
//{
// controller = CreateController(controllerContext, factory);
// CopyControllerData(controllerContext, controller);
// ExecuteControllerAction(controllerContext, controller);
//}
//finally
//{
// CleanupController(controllerContext, controller, factory);
//}
//return Task.CompletedTask;
}
/// <summary>
/// Executes the controller action
/// </summary>
private void ExecuteControllerAction(ControllerContext context, Controller controller)
private async Task ExecuteControllerAction(IActionInvoker actionInvoker)
{
using (_profilingLogger.TraceDuration<UmbracoPageResult>("Executing Umbraco RouteDefinition controller", "Finished"))
{
//TODO I do not think this will work, We need to test this, when we can, in the .NET Core executable.
var aec = new ActionExecutingContext(context, new List<IFilterMetadata>(), new Dictionary<string, object>(), controller);
var actionExecutedDelegate = CreateActionExecutedDelegate(aec);
controller.OnActionExecutionAsync(aec, actionExecutedDelegate);
await actionInvoker.InvokeAsync();
}
}
@@ -88,29 +108,6 @@ namespace Umbraco.Web.Website.ActionResults
return () => Task.FromResult(actionExecutedContext);
}
/// <summary>
/// Since we could be returning the current page from a surface controller posted values in which the routing values are changed, we
/// need to revert these values back to nothing in order for the normal page to render again.
/// </summary>
private static void ResetRouteData(RouteData routeData)
{
routeData.DataTokens["area"] = null;
routeData.DataTokens["Namespaces"] = null;
}
/// <summary>
/// Validate that the current page execution is not being handled by the normal umbraco routing system
/// </summary>
private static void ValidateRouteData(ActionContext actionContext)
{
UmbracoRouteValues umbracoRouteValues = actionContext.HttpContext.Features.Get<UmbracoRouteValues>();
if (umbracoRouteValues == null)
{
throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name +
" in the context of an Http POST when using a SurfaceController form");
}
}
/// <summary>
/// Ensure ModelState, ViewData and TempData is copied across
/// </summary>
@@ -118,7 +115,7 @@ namespace Umbraco.Web.Website.ActionResults
{
controller.ViewData.ModelState.Merge(context.ModelState);
foreach (var d in controller.ViewData)
foreach (KeyValuePair<string, object> d in controller.ViewData)
{
controller.ViewData[d.Key] = d.Value;
}

View File

@@ -21,6 +21,7 @@ namespace Umbraco.Web.Website.Controllers
// TODO: Migrate MergeModelStateToChildAction and MergeParentContextViewData action filters
// [MergeModelStateToChildAction]
// [MergeParentContextViewData]
[AutoValidateAntiforgeryToken]
public abstract class SurfaceController : PluginController
{
/// <summary>

View File

@@ -78,7 +78,8 @@ namespace Umbraco.Extensions
umbrcoContext.PublishedRequest.PublishedContent.Id);
return new HtmlString(htmlBadge);
}
return new HtmlString(string.Empty);
return HtmlString.Empty;
}
@@ -243,14 +244,55 @@ namespace Umbraco.Extensions
return htmlHelper.ActionLink(actionName, metaData.ControllerName, routeVals);
}
/// <summary>
/// Outputs the hidden html input field for Surface Controller route information
/// </summary>
/// <typeparam name="TSurface">The <see cref="SurfaceController"/> type</typeparam>
/// <remarks>
/// Typically not used directly because BeginUmbracoForm automatically outputs this value when routing
/// for surface controllers. But this could be used in case a form tag is manually created.
/// </remarks>
public static IHtmlContent SurfaceControllerHiddenInput<TSurface>(
this IHtmlHelper htmlHelper,
string controllerAction,
string area,
object additionalRouteVals = null)
where TSurface : SurfaceController
{
var inputField = GetSurfaceControllerHiddenInput(
GetRequiredService<IDataProtectionProvider>(htmlHelper),
ControllerExtensions.GetControllerName<TSurface>(),
controllerAction,
area,
additionalRouteVals);
return new HtmlString(inputField);
}
private static string GetSurfaceControllerHiddenInput(
IDataProtectionProvider dataProtectionProvider,
string controllerName,
string controllerAction,
string area,
object additionalRouteVals = null)
{
var encryptedString = EncryptionHelper.CreateEncryptedRouteString(
dataProtectionProvider,
controllerName,
controllerAction,
area,
additionalRouteVals);
return "<input name=\"ufprt\" type=\"hidden\" value=\"" + encryptedString + "\" />";
}
/// <summary>
/// Used for rendering out the Form for BeginUmbracoForm
/// </summary>
internal class UmbracoForm : MvcForm
{
private readonly ViewContext _viewContext;
private readonly string _encryptedString;
private readonly string _controllerName;
private readonly string _surfaceControllerInput;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoForm"/> class.
@@ -265,25 +307,23 @@ namespace Umbraco.Extensions
: base(viewContext, htmlEncoder)
{
_viewContext = viewContext;
_controllerName = controllerName;
_encryptedString = EncryptionHelper.CreateEncryptedRouteString(GetRequiredService<IDataProtectionProvider>(viewContext), controllerName, controllerAction, area, additionalRouteVals);
_surfaceControllerInput = GetSurfaceControllerHiddenInput(
GetRequiredService<IDataProtectionProvider>(viewContext),
controllerName,
controllerAction,
area,
additionalRouteVals);
}
protected override void GenerateEndForm()
{
// Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken()
// We have a controllerName and area so we can match
if (_controllerName == "UmbRegister"
|| _controllerName == "UmbProfile"
|| _controllerName == "UmbLoginStatus"
|| _controllerName == "UmbLogin")
{
IAntiforgery antiforgery = _viewContext.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
_viewContext.Writer.Write(antiforgery.GetHtml(_viewContext.HttpContext).ToString());
}
// Always output an anti-forgery token
IAntiforgery antiforgery = _viewContext.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
IHtmlContent antiforgeryHtml = antiforgery.GetHtml(_viewContext.HttpContext);
_viewContext.Writer.Write(antiforgeryHtml.ToHtmlString());
// write out the hidden surface form routes
_viewContext.Writer.Write("<input name=\"ufprt\" type=\"hidden\" value=\"" + _encryptedString + "\" />");
_viewContext.Writer.Write(_surfaceControllerInput);
base.GenerateEndForm();
}
@@ -403,7 +443,7 @@ namespace Umbraco.Extensions
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
}
return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes);
return html.BeginUmbracoForm(action, controllerName, string.Empty, additionalRouteVals, htmlAttributes);
}
/// <summary>

View File

@@ -1,61 +0,0 @@
using System;
namespace Umbraco.Web.Website.Routing
{
/// <summary>
/// The result from querying a controller/action in the existing routes
/// </summary>
public class ControllerActionSearchResult
{
/// <summary>
/// Initializes a new instance of the <see cref="ControllerActionSearchResult"/> class.
/// </summary>
private ControllerActionSearchResult(
bool success,
string controllerName,
Type controllerType,
string actionName)
{
Success = success;
ControllerName = controllerName;
ControllerType = controllerType;
ActionName = actionName;
}
/// <summary>
/// Initializes a new instance of the <see cref="ControllerActionSearchResult"/> class.
/// </summary>
public ControllerActionSearchResult(
string controllerName,
Type controllerType,
string actionName)
: this(true, controllerName, controllerType, actionName)
{
}
/// <summary>
/// Gets a value indicating whether 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; }
/// <summary>
/// Returns a failed result
/// </summary>
public static ControllerActionSearchResult Failed() => new ControllerActionSearchResult(false, null, null, null);
}
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Web.Common.Controllers;
@@ -16,7 +16,7 @@ namespace Umbraco.Web.Website.Routing
public class ControllerActionSearcher : IControllerActionSearcher
{
private readonly ILogger<ControllerActionSearcher> _logger;
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
private readonly IActionSelector _actionSelector;
private const string DefaultActionName = nameof(RenderController.Index);
/// <summary>
@@ -24,78 +24,69 @@ namespace Umbraco.Web.Website.Routing
/// </summary>
public ControllerActionSearcher(
ILogger<ControllerActionSearcher> logger,
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
IActionSelector actionSelector)
{
_logger = logger;
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_actionSelector = actionSelector;
}
/// <summary>
/// Determines if a custom controller can hijack the current route
/// </summary>
/// <typeparam name="T">The controller type to find</typeparam>
public ControllerActionSearchResult Find<T>(string controller, string action)
public ControllerActionDescriptor Find<T>(HttpContext httpContext, string controller, string action)
{
IReadOnlyList<ControllerActionDescriptor> candidates = FindControllerCandidates(controller, action, DefaultActionName);
IReadOnlyList<ControllerActionDescriptor> candidates = FindControllerCandidates<T>(httpContext, 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)
if (candidates.Count > 0)
{
ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0];
// ensure the controller is of type T and ControllerBase
if (TypeHelper.IsTypeAssignableFrom<T>(controllerDescriptor.ControllerTypeInfo)
&& TypeHelper.IsTypeAssignableFrom<ControllerBase>(controllerDescriptor.ControllerTypeInfo))
{
// now check if the custom action matches
var resultingAction = DefaultActionName;
if (action != null)
{
var found = customControllerCandidates.FirstOrDefault(x => x.ActionName.InvariantEquals(action))?.ActionName;
if (found != null)
{
resultingAction = found;
}
}
// it's a hijacked route with a custom controller, so return the the values
return new ControllerActionSearchResult(
controllerDescriptor.ControllerName,
controllerDescriptor.ControllerTypeInfo,
resultingAction);
}
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(T).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 candidates[0];
}
return ControllerActionSearchResult.Failed();
return null;
}
/// <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)
private IReadOnlyList<ControllerActionDescriptor> FindControllerCandidates<T>(
HttpContext httpContext,
string customControllerName,
string customActionName,
string defaultActionName)
{
var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items
// Use aspnetcore's IActionSelector to do the finding since it uses an optimized cache lookup
var routeValues = new RouteValueDictionary
{
[UmbracoRouteValueTransformer.ControllerToken] = customControllerName,
[UmbracoRouteValueTransformer.ActionToken] = customActionName, // first try to find the custom action
};
var routeData = new RouteData(routeValues);
var routeContext = new RouteContext(httpContext)
{
RouteData = routeData
};
// try finding candidates for the custom action
var candidates = _actionSelector.SelectCandidates(routeContext)
.Cast<ControllerActionDescriptor>()
.Where(x => x.ControllerName.InvariantEquals(customControllerName)
&& (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName))))
.Where(x => TypeHelper.IsTypeAssignableFrom<T>(x.ControllerTypeInfo))
.ToList();
return descriptors;
if (candidates.Count > 0)
{
// return them if found
return candidates;
}
// now find for the default action since we couldn't find the custom one
routeValues[UmbracoRouteValueTransformer.ActionToken] = defaultActionName;
candidates = _actionSelector.SelectCandidates(routeContext)
.Cast<ControllerActionDescriptor>()
.Where(x => TypeHelper.IsTypeAssignableFrom<T>(x.ControllerTypeInfo))
.ToList();
return candidates;
}
}
}

View File

@@ -1,7 +1,10 @@
namespace Umbraco.Web.Website.Routing
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Umbraco.Web.Website.Routing
{
public interface IControllerActionSearcher
{
ControllerActionSearchResult Find<T>(string controller, string action);
ControllerActionDescriptor Find<T>(HttpContext httpContext, string controller, string action);
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
@@ -45,8 +46,9 @@ namespace Umbraco.Web.Website.Routing
private readonly IRoutableDocumentFilter _routableDocumentFilter;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IControllerActionSearcher _controllerActionSearcher;
private const string ControllerToken = "controller";
private const string ActionToken = "action";
internal const string ControllerToken = "controller";
internal const string ActionToken = "action";
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer"/> class.
@@ -197,9 +199,9 @@ namespace Umbraco.Web.Website.Routing
values[ControllerToken] = postedInfo.ControllerName;
values[ActionToken] = postedInfo.ActionName;
ControllerActionSearchResult surfaceControllerQueryResult = _controllerActionSearcher.Find<SurfaceController>(postedInfo.ControllerName, postedInfo.ActionName);
ControllerActionDescriptor surfaceControllerDescriptor = _controllerActionSearcher.Find<SurfaceController>(httpContext, postedInfo.ControllerName, postedInfo.ActionName);
if (surfaceControllerQueryResult == null || !surfaceControllerQueryResult.Success)
if (surfaceControllerDescriptor == null)
{
throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName);
}

View File

@@ -1,5 +1,6 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Umbraco.Core;
using Umbraco.Core.Strings;
@@ -24,6 +25,7 @@ namespace Umbraco.Web.Website.Routing
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.
@@ -41,6 +43,20 @@ namespace Umbraco.Web.Website.Routing
_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>
@@ -61,8 +77,6 @@ namespace Umbraco.Web.Website.Routing
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
@@ -75,16 +89,15 @@ namespace Umbraco.Web.Website.Routing
customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper);
}
// creates the default route definition which maps to the 'UmbracoController' controller
// The default values for the default controller/action
var def = new UmbracoRouteValues(
request,
DefaultControllerName,
defaultControllerType,
_defaultControllerDescriptor.Value,
templateName: customActionName);
def = CheckHijackedRoute(def);
def = CheckHijackedRoute(httpContext, def);
def = CheckNoTemplate(def);
def = CheckNoTemplate(httpContext, def);
return def;
}
@@ -92,21 +105,19 @@ namespace Umbraco.Web.Website.Routing
/// <summary>
/// Check if the route is hijacked and return new route values
/// </summary>
private UmbracoRouteValues CheckHijackedRoute(UmbracoRouteValues def)
private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def)
{
IPublishedRequest request = def.PublishedRequest;
var customControllerName = request.PublishedContent?.ContentType?.Alias;
if (customControllerName != null)
{
ControllerActionSearchResult hijackedResult = _controllerActionSearcher.Find<IRenderController>(customControllerName, def.TemplateName);
if (hijackedResult.Success)
ControllerActionDescriptor descriptor = _controllerActionSearcher.Find<IRenderController>(httpContext, customControllerName, def.TemplateName);
if (descriptor != null)
{
return new UmbracoRouteValues(
request,
hijackedResult.ControllerName,
hijackedResult.ControllerType,
hijackedResult.ActionName,
descriptor,
def.TemplateName,
true);
}
@@ -118,7 +129,7 @@ namespace Umbraco.Web.Website.Routing
/// <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(UmbracoRouteValues def)
private UmbracoRouteValues CheckNoTemplate(HttpContext httpContext, UmbracoRouteValues def)
{
IPublishedRequest request = def.PublishedRequest;
@@ -147,15 +158,13 @@ namespace Umbraco.Web.Website.Routing
def = new UmbracoRouteValues(
request,
def.ControllerName,
def.ControllerType,
def.ActionName,
def.ControllerActionDescriptor,
def.TemplateName);
// if the content has changed, we must then again check for hijacked routes
if (content != request.PublishedContent)
{
def = CheckHijackedRoute(def);
def = CheckHijackedRoute(httpContext, def);
}
}