Fix for issue 13017 - BeginUmbracoForm doesn't work with custom umbraco routes (#13103)

* Fix issue with custom Umbraco routes not working after submitting to a Surface controller

* Added comments

* Fixed breaking changes

* Fixed test by using correct new ctor

* Fixed initializtion of UmbracoRouteValueTransformer due to ambiguous ctor

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Justin Neville
2022-12-06 11:39:43 +00:00
committed by Bjarke Berg
parent caed5bcb13
commit f28b4c1279
9 changed files with 432 additions and 50 deletions

View File

@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Umbraco.Cms.Web.Common.Extensions;
/// <summary>
/// Extensions methods for EndpointDataSource.
/// </summary>
public static class EndpointDataSourceExtensions
{
/// <summary>
/// Gets an endpoint that matches the specified path.
/// </summary>
/// <param name="endpointDatasource">The end point data source.</param>
/// <param name="linkParser">The link parsers using to parse the path.</param>
/// <param name="path">The path to be parsed.</param>
/// <param name="routeValues">The mathcing route values for the mathcing endpoint.</param>
/// <returns>The endpoint or null if not found.</returns>
public static Endpoint? GetEndpointByPath(this EndpointDataSource endpointDatasource, LinkParser linkParser, PathString path, out RouteValueDictionary? routeValues)
{
routeValues = null;
foreach (Endpoint endpoint in endpointDatasource.Endpoints)
{
routeValues = linkParser.ParsePathByEndpoint(endpoint, path);
if (routeValues != null)
{
return endpoint;
}
}
return null;
}
}

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
namespace Umbraco.Cms.Web.Common.Extensions;
/// <summary>
/// Extensions methods for Endpoint.
/// </summary>
public static class EndpointExtensions
{
/// <summary>
/// Gets the controller action descriptor from the endpoint.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <returns>The controller action descriptor or null if not found.</returns>
public static ControllerActionDescriptor? GetControllerActionDescriptor(this Endpoint endpoint)
{
return endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
}
/// <summary>
/// Gets the route name metadata from the endpoint.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <returns>The route name metadata or null if not found.</returns>
public static RouteNameMetadata? GetRouteNameMetadata(this Endpoint endpoint)
{
return endpoint.Metadata.GetMetadata<RouteNameMetadata>();
}
/// <summary>
/// Gets the route name from the endpoint metadata.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <returns>The route name or null if not found.</returns>
public static string? GetRouteName(this Endpoint endpoint)
{
return endpoint.GetRouteNameMetadata()?.RouteName;
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Umbraco.Cms.Web.Common.Extensions;
/// <summary>
/// Extension methods for LinkParser.
/// </summary>
public static class LinkParserExtensions
{
/// <summary>
/// Parses the path using the specified endpoint and returns the route values if a the path matches the route pattern.
/// </summary>
/// <param name="linkParser">The link parser.</param>
/// <param name="endpoint">The endpoint.</param>
/// <param name="path">The path to parse.</param>
/// <returns>The route values if the path matches or null.</returns>
public static RouteValueDictionary? ParsePathByEndpoint(this LinkParser linkParser, Endpoint endpoint, PathString path)
{
var name = endpoint.GetRouteName();
if (name != null)
{
return linkParser.ParsePathByEndpointName(name, path);
}
else
{
return null;
}
}
}

View File

@@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Routing;
@@ -21,23 +19,42 @@ public class UmbracoVirtualPageFilterAttribute : Attribute, IAsyncActionFilter
/// <inheritdoc />
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Endpoint? endpoint = context.HttpContext.GetEndpoint();
// Check if there's proxied ViewData (i.e. returned from a SurfaceController)
// We don't want to find the content and set route values again if this is from a surface controller
ProxyViewDataFeature? proxyViewDataFeature = context.HttpContext.Features.Get<ProxyViewDataFeature>();
// Check if there is any delegate in the metadata of the route, this
// will occur when using the ForUmbraco method during routing.
CustomRouteContentFinderDelegate? contentFinder =
endpoint?.Metadata.OfType<CustomRouteContentFinderDelegate>().FirstOrDefault();
if (contentFinder != null)
if (proxyViewDataFeature != null)
{
await SetUmbracoRouteValues(context, contentFinder.FindContent(context));
if (context.Controller is Controller controller)
{
foreach (KeyValuePair<string, object?> kv in proxyViewDataFeature.ViewData)
{
controller.ViewData[kv.Key] = kv.Value;
}
}
}
else
{
// Check if the controller is IVirtualPageController and then use that to FindContent
if (context.Controller is IVirtualPageController ctrl)
Endpoint? endpoint = context.HttpContext.GetEndpoint();
if (endpoint != null)
{
await SetUmbracoRouteValues(context, ctrl.FindContent(context));
IUmbracoVirtualPageRoute umbracoVirtualPageRoute = context.HttpContext.RequestServices.GetRequiredService<IUmbracoVirtualPageRoute>();
IPublishedContent? publishedContent = umbracoVirtualPageRoute.FindContent(endpoint, context);
if (publishedContent != null)
{
await umbracoVirtualPageRoute.SetRouteValues(
context.HttpContext,
publishedContent,
(ControllerActionDescriptor)context.ActionDescriptor);
}
else
{
// if there is no content then it should be a not found
context.Result = new NotFoundResult();
}
}
}
@@ -47,32 +64,4 @@ public class UmbracoVirtualPageFilterAttribute : Attribute, IAsyncActionFilter
await next();
}
}
private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent? content)
{
if (content != null)
{
UriUtility uriUtility = context.HttpContext.RequestServices.GetRequiredService<UriUtility>();
var originalRequestUrl = new Uri(context.HttpContext.Request.GetEncodedUrl());
Uri cleanedUrl = uriUtility.UriToUmbraco(originalRequestUrl);
IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService<IPublishedRouter>();
IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(cleanedUrl);
requestBuilder.SetPublishedContent(content);
IPublishedRequest publishedRequest = requestBuilder.Build();
var routeValues = new UmbracoRouteValues(
publishedRequest,
(ControllerActionDescriptor)context.ActionDescriptor);
context.HttpContext.Features.Set(routeValues);
}
else
{
// if there is no content then it should be a not found
context.Result = new NotFoundResult();
}
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
namespace Umbraco.Cms.Web.Common.Routing;
/// <summary>
/// This is used to setup the virtual page route so the route values and content are set for virtual pages.
/// </summary>
public interface IUmbracoVirtualPageRoute
{
/// <summary>
/// This sets up the virtual page route for the current request if a mtahcing endpoint is found.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>Nothing</returns>
Task SetupVirtualPageRoute(HttpContext httpContext);
/// <summary>
/// Finds the content from the custom route finder delegate or the virtual page controller.
/// Note - This creates a dummay action executing context so the FindContent method of the
/// IVirtualPageController can be called (without changing the interface contract).
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="routeValues">The route values.</param>
/// <param name="controllerActionDescriptor">The action descriptor.</param>
/// <param name="controller">The controller.</param>
/// <returns></returns>
IPublishedContent? FindContent(
Endpoint endpoint,
HttpContext httpContext,
RouteValueDictionary routeValues,
ControllerActionDescriptor controllerActionDescriptor,
object controller);
/// <summary>
/// Finds the content from the custom route finder delegate or the virtual page controller.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="actionExecutingContext">The action executing context.</param>
/// <returns>The published content if found or null.</returns>
IPublishedContent? FindContent(Endpoint endpoint, ActionExecutingContext actionExecutingContext);
/// <summary>
/// Creates the published request for the published content.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="publishedContent">The published content.</param>
/// <returns>The published request.</returns>
Task<IPublishedRequest> CreatePublishedRequest(HttpContext httpContext, IPublishedContent publishedContent);
/// <summary>
/// Sets the route values for the published content and the controller action descriptor.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="publishedContent">The published content.</param>
/// <param name="controllerActionDescriptor">The controller action descriptor.</param>
/// <returns>Nothing.</returns>
Task SetRouteValues(HttpContext httpContext, IPublishedContent publishedContent, ControllerActionDescriptor controllerActionDescriptor);
}

View File

@@ -0,0 +1,178 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Extensions;
namespace Umbraco.Cms.Web.Common.Routing;
/// <summary>
/// This is used to setup the virtual page route so the route values and content are set for virtual pages.
/// </summary>
public class UmbracoVirtualPageRoute : IUmbracoVirtualPageRoute
{
private readonly EndpointDataSource _endpointDataSource;
private readonly LinkParser _linkParser;
private readonly UriUtility _uriUtility;
private readonly IPublishedRouter _publishedRouter;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="endpointDataSource">The endpoint data source.</param>
/// <param name="linkParser">The link parser.</param>
/// <param name="uriUtility">The Uri utility.</param>
/// <param name="publishedRouter">The published router.</param>
public UmbracoVirtualPageRoute(
EndpointDataSource endpointDataSource,
LinkParser linkParser,
UriUtility uriUtility,
IPublishedRouter publishedRouter)
{
_endpointDataSource = endpointDataSource;
_linkParser = linkParser;
_uriUtility = uriUtility;
_publishedRouter = publishedRouter;
}
/// <summary>
/// This sets up the virtual page route for the current request if a mtahcing endpoint is found.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>Nothing</returns>
public async Task SetupVirtualPageRoute(HttpContext httpContext)
{
// Try and find an endpoint for the current path...
Endpoint? endpoint = _endpointDataSource.GetEndpointByPath(_linkParser, httpContext.Request.Path, out RouteValueDictionary? routeValues);
if (endpoint != null && routeValues != null)
{
ControllerActionDescriptor? controllerActionDescriptor = endpoint.GetControllerActionDescriptor();
if (controllerActionDescriptor != null)
{
Type controllerType = controllerActionDescriptor.ControllerTypeInfo.AsType();
if (controllerType != null)
{
// Get the controller for the endpoint
var controller = httpContext.RequestServices.GetRequiredService(controllerType);
// Try and find the content if this is a virtual page
IPublishedContent? publishedContent = FindContent(
endpoint,
httpContext,
routeValues,
controllerActionDescriptor,
controller);
if (publishedContent != null)
{
// If we have content then set the route values
await SetRouteValues(httpContext, publishedContent, controllerActionDescriptor);
}
}
}
}
}
/// <summary>
/// Finds the content from the custom route finder delegate or the virtual page controller.
/// Note - This creates a dummay action executing context so the FindContent method of the
/// IVirtualPageController can be called (without changing the interface contract).
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="routeValues">The route values.</param>
/// <param name="controllerActionDescriptor">The action descriptor.</param>
/// <param name="controller">The controller.</param>
/// <returns></returns>
public IPublishedContent? FindContent(
Endpoint endpoint,
HttpContext httpContext,
RouteValueDictionary routeValues,
ControllerActionDescriptor controllerActionDescriptor,
object controller)
{
var actionExecutingContext = new ActionExecutingContext(
new ActionContext(
httpContext,
new RouteData(routeValues),
controllerActionDescriptor),
filters: new List<IFilterMetadata>(),
actionArguments: new Dictionary<string, object?>(),
controller: controller);
return FindContent(endpoint, actionExecutingContext);
}
/// <summary>
/// Finds the content from the custom route finder delegate or the virtual page controller.
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <param name="actionExecutingContext">The action executing context.</param>
/// <returns>The published content if found or null.</returns>
public IPublishedContent? FindContent(Endpoint endpoint, ActionExecutingContext actionExecutingContext)
{
// Check if there is any delegate in the metadata of the route, this
// will occur when using the ForUmbraco method during routing.
CustomRouteContentFinderDelegate? contentFinder =
endpoint?.Metadata.OfType<CustomRouteContentFinderDelegate>().FirstOrDefault();
if (contentFinder != null)
{
return contentFinder.FindContent(actionExecutingContext);
}
else
{
// Check if the controller is IVirtualPageController and then use that to FindContent
if (actionExecutingContext.Controller is IVirtualPageController virtualPageController)
{
return virtualPageController.FindContent(actionExecutingContext);
}
}
return null;
}
/// <summary>
/// Creates the published request for the published content.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="publishedContent">The published content.</param>
/// <returns>The published request.</returns>
public async Task<IPublishedRequest> CreatePublishedRequest(HttpContext httpContext, IPublishedContent publishedContent)
{
var originalRequestUrl = new Uri(httpContext.Request.GetEncodedUrl());
Uri cleanedUrl = _uriUtility.UriToUmbraco(originalRequestUrl);
IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(cleanedUrl);
requestBuilder.SetPublishedContent(publishedContent);
return requestBuilder.Build();
}
/// <summary>
/// Sets the route values for the published content and the controller action descriptor.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <param name="publishedContent">The published content.</param>
/// <param name="controllerActionDescriptor">The controller action descriptor.</param>
/// <returns>Nothing.</returns>
public async Task SetRouteValues(HttpContext httpContext, IPublishedContent publishedContent, ControllerActionDescriptor controllerActionDescriptor)
{
IPublishedRequest publishedRequest = await CreatePublishedRequest(httpContext, publishedContent);
var umbracoRouteValues = new UmbracoRouteValues(
publishedRequest,
controllerActionDescriptor);
httpContext.Features.Set(umbracoRouteValues);
}
}

View File

@@ -1,9 +1,14 @@
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.DependencyInjection;
using Umbraco.Cms.Web.Common.Middleware;
using Umbraco.Cms.Web.Common.Routing;
@@ -40,9 +45,21 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddDataProtection();
builder.Services.AddAntiforgery();
builder.Services.AddSingleton<UmbracoRouteValueTransformer>();
builder.Services.AddSingleton<UmbracoRouteValueTransformer>(x => new UmbracoRouteValueTransformer(
x.GetRequiredService<ILogger<UmbracoRouteValueTransformer>>(),
x.GetRequiredService<IUmbracoContextAccessor>(),
x.GetRequiredService<IPublishedRouter>(),
x.GetRequiredService<IRuntimeState>(),
x.GetRequiredService<IUmbracoRouteValuesFactory>(),
x.GetRequiredService<IRoutableDocumentFilter>(),
x.GetRequiredService<IDataProtectionProvider>(),
x.GetRequiredService<IControllerActionSearcher>(),
x.GetRequiredService<IPublicAccessRequestHandler>(),
x.GetRequiredService<IUmbracoVirtualPageRoute>()
));
builder.Services.AddSingleton<IControllerActionSearcher, ControllerActionSearcher>();
builder.Services.TryAddEnumerable(Singleton<MatcherPolicy, NotFoundSelectorPolicy>());
builder.Services.AddSingleton<IUmbracoVirtualPageRoute, UmbracoVirtualPageRoute>();
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
builder.Services.AddSingleton<IRoutableDocumentFilter, RoutableDocumentFilter>();

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
@@ -13,6 +14,7 @@ using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.Routing;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Cms.Web.Website.Controllers;
@@ -46,8 +48,9 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer
private readonly IUmbracoRouteValuesFactory _routeValuesFactory;
private readonly IRuntimeState _runtime;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IUmbracoVirtualPageRoute _umbracoVirtualPageRoute;
[Obsolete("Please use constructor that does not take IOptions<GlobalSettings>, IHostingEnvironment & IEventAggregator instead")]
[Obsolete("Please use constructor that is not obsolete, instead of this. This will be removed in Umbraco 13.")]
public UmbracoRouteValueTransformer(
ILogger<UmbracoRouteValueTransformer> logger,
IUmbracoContextAccessor umbracoContextAccessor,
@@ -61,10 +64,26 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer
IControllerActionSearcher controllerActionSearcher,
IEventAggregator eventAggregator,
IPublicAccessRequestHandler publicAccessRequestHandler)
: this(logger, umbracoContextAccessor, publishedRouter, runtime, routeValuesFactory, routableDocumentFilter, dataProtectionProvider, controllerActionSearcher, publicAccessRequestHandler)
: this(logger, umbracoContextAccessor, publishedRouter, runtime, routeValuesFactory, routableDocumentFilter, dataProtectionProvider, controllerActionSearcher, publicAccessRequestHandler, StaticServiceProvider.Instance.GetRequiredService<IUmbracoVirtualPageRoute>())
{
}
[Obsolete("Please use constructor that is not obsolete, instead of this. This will be removed in Umbraco 13.")]
public UmbracoRouteValueTransformer(
ILogger<UmbracoRouteValueTransformer> logger,
IUmbracoContextAccessor umbracoContextAccessor,
IPublishedRouter publishedRouter,
IRuntimeState runtime,
IUmbracoRouteValuesFactory routeValuesFactory,
IRoutableDocumentFilter routableDocumentFilter,
IDataProtectionProvider dataProtectionProvider,
IControllerActionSearcher controllerActionSearcher,
IPublicAccessRequestHandler publicAccessRequestHandler)
: this(logger, umbracoContextAccessor, publishedRouter, runtime, routeValuesFactory, routableDocumentFilter, dataProtectionProvider, controllerActionSearcher, publicAccessRequestHandler, StaticServiceProvider.Instance.GetRequiredService<IUmbracoVirtualPageRoute>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer" /> class.
/// </summary>
@@ -77,7 +96,8 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer
IRoutableDocumentFilter routableDocumentFilter,
IDataProtectionProvider dataProtectionProvider,
IControllerActionSearcher controllerActionSearcher,
IPublicAccessRequestHandler publicAccessRequestHandler)
IPublicAccessRequestHandler publicAccessRequestHandler,
IUmbracoVirtualPageRoute umbracoVirtualPageRoute)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_umbracoContextAccessor =
@@ -90,6 +110,7 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer
_dataProtectionProvider = dataProtectionProvider;
_controllerActionSearcher = controllerActionSearcher;
_publicAccessRequestHandler = publicAccessRequestHandler;
_umbracoVirtualPageRoute = umbracoVirtualPageRoute;
}
/// <inheritdoc />
@@ -147,6 +168,10 @@ public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer
PostedDataProxyInfo? postedInfo = GetFormInfo(httpContext, values);
if (postedInfo != null)
{
// Ensure the virtual page content and route values are setup when submitting to a surface controller
// If we don't do this, the virtual page controller never gets called after the surface controller completes
await _umbracoVirtualPageRoute.SetupVirtualPageRoute(httpContext);
return HandlePostedValues(postedInfo, httpContext);
}

View File

@@ -21,6 +21,7 @@ using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Cms.Web.Common.Routing;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Cms.Web.Website.Routing;
@@ -53,20 +54,21 @@ public class UmbracoRouteValueTransformerTests
x.RewriteForPublishedContentAccessAsync(It.IsAny<HttpContext>(), It.IsAny<UmbracoRouteValues>()))
.Returns((HttpContext ctx, UmbracoRouteValues routeVals) => Task.FromResult(routeVals));
var umbracoVirtualPageRoute = new Mock<IUmbracoVirtualPageRoute>();
umbracoVirtualPageRoute.Setup(x => x.SetupVirtualPageRoute(It.IsAny<HttpContext>()));
var transformer = new UmbracoRouteValueTransformer(
new NullLogger<UmbracoRouteValueTransformer>(),
ctx,
router ?? Mock.Of<IPublishedRouter>(),
GetGlobalSettings(),
TestHelper.GetHostingEnvironment(),
state,
routeValuesFactory ?? Mock.Of<IUmbracoRouteValuesFactory>(),
filter ?? Mock.Of<IRoutableDocumentFilter>(x => x.IsDocumentRequest(It.IsAny<string>()) == true),
Mock.Of<IDataProtectionProvider>(),
Mock.Of<IControllerActionSearcher>(),
Mock.Of<IEventAggregator>(),
publicAccessRequestHandler.Object);
publicAccessRequestHandler.Object,
Mock.Of<IUmbracoVirtualPageRoute>()
);
return transformer;
}