* Add IDynamicEndpointMetadata for dynamic endpoints * Use a dynamic endpoint policy for pages
This commit is contained in:
parent
27e54a1b7a
commit
f2a1a4542e
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A metadata interface that can be used to specify that the associated <see cref="Endpoint" />
|
||||
/// will be dynamically replaced during matching.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IDynamicEndpointMetadata"/> and related derived interfaces signal to
|
||||
/// <see cref="MatcherPolicy"/> implementations that an <see cref="Endpoint"/> has dynamic behavior
|
||||
/// and thus cannot have its characteristics cached.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Using dynamic endpoints can be useful because the default matcher implementation does not
|
||||
/// supply extensibility for how URLs are processed. Routing implementations that have dynamic
|
||||
/// behavior can apply their dynamic logic after URL processing, by replacing a endpoints as
|
||||
/// part of a <see cref="CandidateSet"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IDynamicEndpointMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a value that indicates whether the associated endpoint has dynamic matching
|
||||
/// behavior.
|
||||
/// </summary>
|
||||
bool IsDynamic { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -281,6 +281,57 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the <see cref="Endpoint"/> at the provided <paramref name="index"/> with the
|
||||
/// provided <paramref name="endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The candidate index.</param>
|
||||
/// <param name="endpoint">
|
||||
/// The <see cref="Endpoint"/> to replace the original <see cref="Endpoint"/> at
|
||||
/// the <paramref name="index"/>. If <paramref name="endpoint"/> the candidate will be marked
|
||||
/// as invalid.
|
||||
/// </param>
|
||||
/// <param name="values">
|
||||
/// The <see cref="RouteValueDictionary"/> to replace the original <see cref="RouteValueDictionary"/> at
|
||||
/// the <paramref name="index"/>.
|
||||
/// </param>
|
||||
public void ReplaceEndpoint(int index, Endpoint endpoint, RouteValueDictionary values)
|
||||
{
|
||||
// Friendliness for inlining
|
||||
if ((uint)index >= Count)
|
||||
{
|
||||
ThrowIndexArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
_state0 = new CandidateState(endpoint, values, _state0.Score);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
_state1 = new CandidateState(endpoint, values, _state1.Score);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_state2 = new CandidateState(endpoint, values, _state2.Score);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_state3 = new CandidateState(endpoint, values, _state3.Score);
|
||||
break;
|
||||
|
||||
default:
|
||||
_additionalCandidates[index - 4] = new CandidateState(endpoint, values, _additionalCandidates[index - 4].Score);
|
||||
break;
|
||||
}
|
||||
|
||||
if (endpoint == null)
|
||||
{
|
||||
SetValidity(index, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowIndexArgumentOutOfRangeException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
|
|
@ -24,5 +27,30 @@ namespace Microsoft.AspNetCore.Routing
|
|||
/// property.
|
||||
/// </summary>
|
||||
public abstract int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that indicates whether the provided <paramref name="endpoints"/> contains
|
||||
/// one or more dynamic endpoints.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The set of endpoints.</param>
|
||||
/// <returns><c>true</c> if a dynamic endpoint is found; otherwise returns <c>false</c>.</returns>
|
||||
protected static bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var metadata = endpoints[i].Metadata.GetMetadata<IDynamicEndpointMetadata>();
|
||||
if (metadata?.IsDynamic == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class MatcherPolicyTest
|
||||
{
|
||||
[Fact]
|
||||
public void ContainsDynamicEndpoint_FindsDynamicEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var endpoints = new Endpoint[]
|
||||
{
|
||||
CreateEndpoint("1"),
|
||||
CreateEndpoint("2"),
|
||||
CreateEndpoint("3", new DynamicEndpointMetadata(isDynamic: true)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var endpoints = new Endpoint[]
|
||||
{
|
||||
CreateEndpoint("1"),
|
||||
CreateEndpoint("2"),
|
||||
CreateEndpoint("3", new DynamicEndpointMetadata(isDynamic: false)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint_Empty()
|
||||
{
|
||||
// Arrange
|
||||
var endpoints = new Endpoint[]{ };
|
||||
|
||||
// Act
|
||||
var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateEndpoint(string template, params object[] metadata)
|
||||
{
|
||||
return new RouteEndpoint(
|
||||
TestConstants.EmptyRequestDelegate,
|
||||
RoutePatternFactory.Parse(template),
|
||||
0,
|
||||
new EndpointMetadataCollection(metadata),
|
||||
"test");
|
||||
}
|
||||
|
||||
private class DynamicEndpointMetadata : IDynamicEndpointMetadata
|
||||
{
|
||||
public DynamicEndpointMetadata(bool isDynamic)
|
||||
{
|
||||
IsDynamic = isDynamic;
|
||||
}
|
||||
|
||||
public bool IsDynamic { get; }
|
||||
}
|
||||
|
||||
private class TestMatcherPolicy : MatcherPolicy
|
||||
{
|
||||
public override int Order => throw new System.NotImplementedException();
|
||||
|
||||
public new static bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return MatcherPolicy.ContainsDynamicEndpoints(endpoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -55,6 +55,91 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
}
|
||||
|
||||
// We special case low numbers of candidates, so we want to verify that it works correctly for a variety
|
||||
// of input sizes.
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
[InlineData(5)] // this is the break-point where we start to use a list.
|
||||
[InlineData(6)]
|
||||
[InlineData(31)]
|
||||
[InlineData(32)] // this is the break point where we use a BitArray
|
||||
[InlineData(33)]
|
||||
public void ReplaceEndpoint_WithEndpoint(int count)
|
||||
{
|
||||
// Arrange
|
||||
var endpoints = new RouteEndpoint[count];
|
||||
for (var i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
endpoints[i] = CreateEndpoint($"/{i}");
|
||||
}
|
||||
|
||||
var builder = CreateDfaMatcherBuilder();
|
||||
var candidates = builder.CreateCandidates(endpoints);
|
||||
|
||||
var candidateSet = new CandidateSet(candidates);
|
||||
|
||||
for (var i = 0; i < candidateSet.Count; i++)
|
||||
{
|
||||
ref var state = ref candidateSet[i];
|
||||
|
||||
var endpoint = CreateEndpoint($"/test{i}");
|
||||
var values = new RouteValueDictionary();
|
||||
|
||||
// Act
|
||||
candidateSet.ReplaceEndpoint(i, endpoint, values);
|
||||
|
||||
// Assert
|
||||
Assert.Same(endpoint, state.Endpoint);
|
||||
Assert.Same(values, state.Values);
|
||||
Assert.True(candidateSet.IsValidCandidate(i));
|
||||
}
|
||||
}
|
||||
|
||||
// We special case low numbers of candidates, so we want to verify that it works correctly for a variety
|
||||
// of input sizes.
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
[InlineData(5)] // this is the break-point where we start to use a list.
|
||||
[InlineData(6)]
|
||||
[InlineData(31)]
|
||||
[InlineData(32)] // this is the break point where we use a BitArray
|
||||
[InlineData(33)]
|
||||
public void ReplaceEndpoint_WithEndpoint_Null(int count)
|
||||
{
|
||||
// Arrange
|
||||
var endpoints = new RouteEndpoint[count];
|
||||
for (var i = 0; i < endpoints.Length; i++)
|
||||
{
|
||||
endpoints[i] = CreateEndpoint($"/{i}");
|
||||
}
|
||||
|
||||
var builder = CreateDfaMatcherBuilder();
|
||||
var candidates = builder.CreateCandidates(endpoints);
|
||||
|
||||
var candidateSet = new CandidateSet(candidates);
|
||||
|
||||
for (var i = 0; i < candidateSet.Count; i++)
|
||||
{
|
||||
ref var state = ref candidateSet[i];
|
||||
|
||||
// Act
|
||||
candidateSet.ReplaceEndpoint(i, null, null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(state.Endpoint);
|
||||
Assert.Null(state.Values);
|
||||
Assert.False(candidateSet.IsValidCandidate(i));
|
||||
}
|
||||
}
|
||||
|
||||
// We special case low numbers of candidates, so we want to verify that it works correctly for a variety
|
||||
// of input sizes.
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -114,10 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
{
|
||||
var dataSource = new ActionEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
new ActionEndpointFactory(
|
||||
new MockRoutePatternTransformer(),
|
||||
new MvcEndpointInvokerFactory(
|
||||
new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>()))));
|
||||
new ActionEndpointFactory(new MockRoutePatternTransformer()));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,7 +272,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<ActionEndpointDataSource>();
|
||||
services.TryAddSingleton<ControllerActionEndpointDataSource>();
|
||||
services.TryAddSingleton<ActionEndpointFactory>();
|
||||
services.TryAddSingleton<MvcEndpointInvokerFactory>();
|
||||
|
||||
//
|
||||
// Middleware pipeline filter related
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
{
|
||||
public class ActionContextAccessor : IActionContextAccessor
|
||||
{
|
||||
internal static readonly IActionContextAccessor Null = new NullActionContextAccessor();
|
||||
|
||||
private static readonly AsyncLocal<ActionContext> _storage = new AsyncLocal<ActionContext>();
|
||||
|
||||
public ActionContext ActionContext
|
||||
|
|
@ -14,5 +16,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
get { return _storage.Value; }
|
||||
set { _storage.Value = value; }
|
||||
}
|
||||
|
||||
private class NullActionContextAccessor : IActionContextAccessor
|
||||
{
|
||||
public ActionContext ActionContext
|
||||
{
|
||||
get => null;
|
||||
set { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,11 +27,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
internal ControllerActionInvoker(
|
||||
ILogger logger,
|
||||
DiagnosticListener diagnosticListener,
|
||||
IActionContextAccessor actionContextAccessor,
|
||||
IActionResultTypeMapper mapper,
|
||||
ControllerContext controllerContext,
|
||||
ControllerActionInvokerCacheEntry cacheEntry,
|
||||
IFilterMetadata[] filters)
|
||||
: base(diagnosticListener, logger, mapper, controllerContext, filters, controllerContext.ValueProviderFactories)
|
||||
: base(diagnosticListener, logger, actionContextAccessor, mapper, controllerContext, filters, controllerContext.ValueProviderFactories)
|
||||
{
|
||||
if (cacheEntry == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
private readonly ILogger _logger;
|
||||
private readonly DiagnosticListener _diagnosticListener;
|
||||
private readonly IActionResultTypeMapper _mapper;
|
||||
private readonly IActionContextAccessor _actionContextAccessor;
|
||||
|
||||
public ControllerActionInvokerProvider(
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
|
|
@ -28,6 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
ILoggerFactory loggerFactory,
|
||||
DiagnosticListener diagnosticListener,
|
||||
IActionResultTypeMapper mapper)
|
||||
: this(controllerActionInvokerCache, optionsAccessor, loggerFactory, diagnosticListener, mapper, null)
|
||||
{
|
||||
}
|
||||
|
||||
public ControllerActionInvokerProvider(
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
IOptions<MvcOptions> optionsAccessor,
|
||||
ILoggerFactory loggerFactory,
|
||||
DiagnosticListener diagnosticListener,
|
||||
IActionResultTypeMapper mapper,
|
||||
IActionContextAccessor actionContextAccessor)
|
||||
{
|
||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
||||
|
|
@ -35,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
|
||||
_diagnosticListener = diagnosticListener;
|
||||
_mapper = mapper;
|
||||
_actionContextAccessor = actionContextAccessor ?? ActionContextAccessor.Null;
|
||||
}
|
||||
|
||||
public int Order => -1000;
|
||||
|
|
@ -61,6 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
var invoker = new ControllerActionInvoker(
|
||||
_logger,
|
||||
_diagnosticListener,
|
||||
_actionContextAccessor,
|
||||
_mapper,
|
||||
controllerContext,
|
||||
cacheEntry,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
{
|
||||
protected readonly DiagnosticListener _diagnosticListener;
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly IActionContextAccessor _actionContextAccessor;
|
||||
protected readonly IActionResultTypeMapper _mapper;
|
||||
protected readonly ActionContext _actionContext;
|
||||
protected readonly IFilterMetadata[] _filters;
|
||||
|
|
@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
public ResourceInvoker(
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILogger logger,
|
||||
IActionContextAccessor actionContextAccessor,
|
||||
IActionResultTypeMapper mapper,
|
||||
ActionContext actionContext,
|
||||
IFilterMetadata[] filters,
|
||||
|
|
@ -46,6 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
{
|
||||
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||
_actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
|
||||
|
||||
|
|
@ -58,6 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
{
|
||||
try
|
||||
{
|
||||
_actionContextAccessor.ActionContext = _actionContext;
|
||||
|
||||
_diagnosticListener.BeforeAction(
|
||||
_actionContext.ActionDescriptor,
|
||||
_actionContext.HttpContext,
|
||||
|
|
|
|||
|
|
@ -9,32 +9,25 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal class ActionEndpointFactory
|
||||
{
|
||||
private readonly RoutePatternTransformer _routePatternTransformer;
|
||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||
|
||||
public ActionEndpointFactory(
|
||||
RoutePatternTransformer routePatternTransformer,
|
||||
MvcEndpointInvokerFactory invokerFactory)
|
||||
public ActionEndpointFactory(RoutePatternTransformer routePatternTransformer)
|
||||
{
|
||||
if (routePatternTransformer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routePatternTransformer));
|
||||
}
|
||||
|
||||
if (invokerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(invokerFactory));
|
||||
}
|
||||
|
||||
_routePatternTransformer = routePatternTransformer;
|
||||
_invokerFactory = invokerFactory;
|
||||
}
|
||||
|
||||
public void AddEndpoints(
|
||||
|
|
@ -188,13 +181,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
bool suppressPathMatching,
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
|
||||
// We don't want to close over the retrieve the Invoker Factory in ActionEndpointFactory as
|
||||
// that creates cycles in DI. Since we're creating this delegate at startup time
|
||||
// we don't want to create all of the things we use at runtime until the action
|
||||
// actually matches.
|
||||
//
|
||||
// The request delegate is already a closure here because we close over
|
||||
// the action descriptor.
|
||||
IActionInvokerFactory invokerFactory = null;
|
||||
|
||||
RequestDelegate requestDelegate = (context) =>
|
||||
{
|
||||
var routeData = context.GetRouteData();
|
||||
|
||||
var actionContext = new ActionContext(context, routeData, action);
|
||||
|
||||
var invoker = _invokerFactory.CreateInvoker(actionContext);
|
||||
if (invokerFactory == null)
|
||||
{
|
||||
invokerFactory = context.RequestServices.GetRequiredService<IActionInvokerFactory>();
|
||||
}
|
||||
|
||||
var invoker = invokerFactory.CreateInvoker(actionContext);
|
||||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,32 +14,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
internal class MvcAttributeRouteHandler : IRouter
|
||||
{
|
||||
private readonly IActionContextAccessor _actionContextAccessor;
|
||||
private readonly IActionInvokerFactory _actionInvokerFactory;
|
||||
private readonly IActionSelector _actionSelector;
|
||||
private readonly ILogger _logger;
|
||||
private DiagnosticListener _diagnosticListener;
|
||||
private readonly DiagnosticListener _diagnosticListener;
|
||||
|
||||
public MvcAttributeRouteHandler(
|
||||
IActionInvokerFactory actionInvokerFactory,
|
||||
IActionSelector actionSelector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory)
|
||||
: this(actionInvokerFactory, actionSelector, diagnosticListener, loggerFactory, actionContextAccessor: null)
|
||||
{
|
||||
}
|
||||
|
||||
public MvcAttributeRouteHandler(
|
||||
IActionInvokerFactory actionInvokerFactory,
|
||||
IActionSelector actionSelector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory,
|
||||
IActionContextAccessor actionContextAccessor)
|
||||
{
|
||||
// The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext
|
||||
// if possible.
|
||||
_actionContextAccessor = actionContextAccessor;
|
||||
|
||||
_actionInvokerFactory = actionInvokerFactory;
|
||||
_actionSelector = actionSelector;
|
||||
_diagnosticListener = diagnosticListener;
|
||||
|
|
@ -94,11 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var routeData = c.GetRouteData();
|
||||
|
||||
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
||||
if (_actionContextAccessor != null)
|
||||
{
|
||||
_actionContextAccessor.ActionContext = actionContext;
|
||||
}
|
||||
|
||||
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
||||
if (invoker == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal sealed class MvcEndpointInvokerFactory : IActionInvokerFactory
|
||||
{
|
||||
private readonly IActionInvokerFactory _invokerFactory;
|
||||
private readonly IActionContextAccessor _actionContextAccessor;
|
||||
|
||||
public MvcEndpointInvokerFactory(
|
||||
IActionInvokerFactory invokerFactory)
|
||||
: this(invokerFactory, actionContextAccessor: null)
|
||||
{
|
||||
}
|
||||
|
||||
public MvcEndpointInvokerFactory(
|
||||
IActionInvokerFactory invokerFactory,
|
||||
IActionContextAccessor actionContextAccessor)
|
||||
{
|
||||
_invokerFactory = invokerFactory;
|
||||
|
||||
// The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext
|
||||
// if possible.
|
||||
_actionContextAccessor = actionContextAccessor;
|
||||
}
|
||||
|
||||
public IActionInvoker CreateInvoker(ActionContext actionContext)
|
||||
{
|
||||
if (_actionContextAccessor != null)
|
||||
{
|
||||
_actionContextAccessor.ActionContext = actionContext;
|
||||
}
|
||||
|
||||
return _invokerFactory.CreateInvoker(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
internal class MvcRouteHandler : IRouter
|
||||
{
|
||||
private readonly IActionContextAccessor _actionContextAccessor;
|
||||
private readonly IActionInvokerFactory _actionInvokerFactory;
|
||||
private readonly IActionSelector _actionSelector;
|
||||
private readonly ILogger _logger;
|
||||
|
|
@ -24,21 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
IActionSelector actionSelector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory)
|
||||
: this(actionInvokerFactory, actionSelector, diagnosticListener, loggerFactory, actionContextAccessor: null)
|
||||
{
|
||||
}
|
||||
|
||||
public MvcRouteHandler(
|
||||
IActionInvokerFactory actionInvokerFactory,
|
||||
IActionSelector actionSelector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory,
|
||||
IActionContextAccessor actionContextAccessor)
|
||||
{
|
||||
// The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext
|
||||
// if possible.
|
||||
_actionContextAccessor = actionContextAccessor;
|
||||
|
||||
_actionInvokerFactory = actionInvokerFactory;
|
||||
_actionSelector = actionSelector;
|
||||
_diagnosticListener = diagnosticListener;
|
||||
|
|
@ -82,11 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var routeData = c.GetRouteData();
|
||||
|
||||
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
||||
if (_actionContextAccessor != null)
|
||||
{
|
||||
_actionContextAccessor.ActionContext = actionContext;
|
||||
}
|
||||
|
||||
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
||||
if (invoker == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||
|
|
@ -59,5 +60,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
/// Gets or sets the <see cref="TypeInfo"/> of the page.
|
||||
/// </summary>
|
||||
public TypeInfo PageTypeInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the associated <see cref="Endpoint"/> of this page.
|
||||
/// </summary>
|
||||
public Endpoint Endpoint { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
|||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources;
|
||||
|
|
@ -82,6 +83,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorPagesRazorViewEngineOptionsSetup>());
|
||||
|
||||
// Routing
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, PageLoaderMatcherPolicy>());
|
||||
|
||||
// Action description and invocation
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
|
|
@ -17,12 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
||||
private readonly IViewCompilerProvider _viewCompilerProvider;
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly PageConventionCollection _conventions;
|
||||
private readonly FilterCollection _globalFilters;
|
||||
|
||||
public DefaultPageLoader(
|
||||
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
||||
IViewCompilerProvider viewCompilerProvider,
|
||||
ActionEndpointFactory endpointFactory,
|
||||
IOptions<RazorPagesOptions> pageOptions,
|
||||
IOptions<MvcOptions> mvcOptions)
|
||||
{
|
||||
|
|
@ -30,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.OrderBy(p => p.Order)
|
||||
.ToArray();
|
||||
_viewCompilerProvider = viewCompilerProvider;
|
||||
_endpointFactory = endpointFactory;
|
||||
_conventions = pageOptions.Value.Conventions;
|
||||
_globalFilters = mvcOptions.Value.Filters;
|
||||
}
|
||||
|
|
@ -59,7 +64,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
ApplyConventions(_conventions, context.PageApplicationModel);
|
||||
|
||||
return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters);
|
||||
var compiled = CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters);
|
||||
|
||||
// We need to create an endpoint for routing to use and attach it to the CompiledPageActionDescriptor...
|
||||
// routing for pages is two-phase. First we perform routing using the route info - we can do this without
|
||||
// compiling/loading the page. Then once we have a match we load the page and we can create an endpoint
|
||||
// with all of the information we get from the compiled action descriptor.
|
||||
var endpoints = new List<Endpoint>();
|
||||
_endpointFactory.AddEndpoints(endpoints, compiled, Array.Empty<ConventionalRouteEntry>(), Array.Empty<Action<EndpointBuilder>>());
|
||||
|
||||
// In some test scenarios there's no route so the endpoint isn't created. This is fine because
|
||||
// it won't happen for real.
|
||||
compiled.Endpoint = endpoints.SingleOrDefault();
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
internal static void ApplyConventions(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
|
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
|
|
@ -106,6 +107,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
descriptor.RouteValues.Add("page", model.ViewEnginePath);
|
||||
}
|
||||
|
||||
// Mark all pages as a "dynamic endpoint" - this is how we deal with the compilation of pages
|
||||
// in endpoint routing.
|
||||
descriptor.EndpointMetadata.Add(new DynamicEndpointMetadata());
|
||||
|
||||
actions.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
|
@ -138,5 +143,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
// Combine transformed page route with template
|
||||
return AttributeRouteModel.CombineTemplates(transformedPageRoute, pageRouteMetadata.RouteTemplate);
|
||||
}
|
||||
|
||||
private class DynamicEndpointMetadata : IDynamicEndpointMetadata
|
||||
{
|
||||
public bool IsDynamic => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
IPageHandlerMethodSelector handlerMethodSelector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILogger logger,
|
||||
IActionContextAccessor actionContextAccessor,
|
||||
IActionResultTypeMapper mapper,
|
||||
PageContext pageContext,
|
||||
IFilterMetadata[] filterMetadata,
|
||||
|
|
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
: base(
|
||||
diagnosticListener,
|
||||
logger,
|
||||
actionContextAccessor,
|
||||
mapper,
|
||||
pageContext,
|
||||
filterMetadata,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
private readonly DiagnosticListener _diagnosticListener;
|
||||
private readonly ILogger<PageActionInvoker> _logger;
|
||||
private readonly IActionResultTypeMapper _mapper;
|
||||
private readonly IActionContextAccessor _actionContextAccessor;
|
||||
|
||||
private volatile InnerCache _currentCache;
|
||||
|
||||
public PageActionInvokerProvider(
|
||||
|
|
@ -57,6 +59,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory,
|
||||
IActionResultTypeMapper mapper)
|
||||
: this(
|
||||
loader,
|
||||
pageFactoryProvider,
|
||||
modelFactoryProvider,
|
||||
razorPageFactoryProvider,
|
||||
collectionProvider,
|
||||
filterProviders,
|
||||
parameterBinder,
|
||||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
tempDataFactory,
|
||||
mvcOptions,
|
||||
htmlHelperOptions,
|
||||
selector,
|
||||
diagnosticListener,
|
||||
loggerFactory,
|
||||
mapper,
|
||||
actionContextAccessor: null)
|
||||
{
|
||||
}
|
||||
|
||||
public PageActionInvokerProvider(
|
||||
IPageLoader loader,
|
||||
IPageFactoryProvider pageFactoryProvider,
|
||||
IPageModelFactoryProvider modelFactoryProvider,
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||
IActionDescriptorCollectionProvider collectionProvider,
|
||||
IEnumerable<IFilterProvider> filterProviders,
|
||||
ParameterBinder parameterBinder,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
ITempDataDictionaryFactory tempDataFactory,
|
||||
IOptions<MvcOptions> mvcOptions,
|
||||
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
||||
IPageHandlerMethodSelector selector,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory,
|
||||
IActionResultTypeMapper mapper,
|
||||
IActionContextAccessor actionContextAccessor)
|
||||
{
|
||||
_loader = loader;
|
||||
_pageFactoryProvider = pageFactoryProvider;
|
||||
|
|
@ -75,6 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
_diagnosticListener = diagnosticListener;
|
||||
_logger = loggerFactory.CreateLogger<PageActionInvoker>();
|
||||
_mapper = mapper;
|
||||
_actionContextAccessor = actionContextAccessor ?? ActionContextAccessor.Null;
|
||||
}
|
||||
|
||||
public int Order { get; } = -1000;
|
||||
|
|
@ -154,6 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
_selector,
|
||||
_diagnosticListener,
|
||||
_logger,
|
||||
_actionContextAccessor,
|
||||
_mapper,
|
||||
pageContext,
|
||||
filters,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
internal class PageLoaderMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
|
||||
{
|
||||
private readonly IPageLoader _loader;
|
||||
|
||||
public PageLoaderMatcherPolicy(IPageLoader loader)
|
||||
{
|
||||
if (loader == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loader));
|
||||
}
|
||||
|
||||
_loader = loader;
|
||||
}
|
||||
|
||||
public override int Order => int.MinValue + 100;
|
||||
|
||||
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (!ContainsDynamicEndpoints(endpoints))
|
||||
{
|
||||
// Pages are always dynamic endpoints.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var page = endpoints[i].Metadata.GetMetadata<PageActionDescriptor>();
|
||||
if (page != null)
|
||||
{
|
||||
// Found a page
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (candidates == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(candidates));
|
||||
}
|
||||
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
ref var candidate = ref candidates[i];
|
||||
var endpoint = (RouteEndpoint)candidate.Endpoint;
|
||||
|
||||
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
|
||||
if (page != null)
|
||||
{
|
||||
var compiled = _loader.Load(page);
|
||||
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidate.Values);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -396,6 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||
: base(
|
||||
logger,
|
||||
diagnosticListener,
|
||||
ActionContextAccessor.Null,
|
||||
mapper,
|
||||
CreateControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
|
||||
CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory),
|
||||
|
|
|
|||
|
|
@ -1343,6 +1343,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
var invoker = new ControllerActionInvoker(
|
||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||
ActionContextAccessor.Null,
|
||||
new ActionResultTypeMapper(),
|
||||
controllerContext,
|
||||
cacheEntry,
|
||||
|
|
@ -1623,6 +1624,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
var invoker = new ControllerActionInvoker(
|
||||
logger,
|
||||
diagnosticSource,
|
||||
ActionContextAccessor.Null,
|
||||
new ActionResultTypeMapper(),
|
||||
controllerContext,
|
||||
cacheEntry,
|
||||
|
|
|
|||
|
|
@ -148,9 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var endpointFactory = new ActionEndpointFactory(
|
||||
serviceProvider.GetRequiredService<RoutePatternTransformer>(),
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())));
|
||||
var endpointFactory = new ActionEndpointFactory(serviceProvider.GetRequiredService<RoutePatternTransformer>());
|
||||
|
||||
return CreateDataSource(actions, endpointFactory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,112 +32,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
});
|
||||
|
||||
Services = serviceCollection.BuildServiceProvider();
|
||||
InvokerFactory = new Mock<IActionInvokerFactory>(MockBehavior.Strict);
|
||||
Factory = new ActionEndpointFactory(
|
||||
Services.GetRequiredService<RoutePatternTransformer>(),
|
||||
new MvcEndpointInvokerFactory(InvokerFactory.Object));
|
||||
Factory = new ActionEndpointFactory(Services.GetRequiredService<RoutePatternTransformer>());
|
||||
}
|
||||
|
||||
internal ActionEndpointFactory Factory { get; }
|
||||
|
||||
internal Mock<IActionInvokerFactory> InvokerFactory { get; }
|
||||
|
||||
internal IServiceProvider Services { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task AddEndpoints_AttributeRouted_UsesActionInvoker()
|
||||
{
|
||||
// Arrange
|
||||
var values = new
|
||||
{
|
||||
action = "Test",
|
||||
controller = "Test",
|
||||
page = (string)null,
|
||||
};
|
||||
|
||||
var action = CreateActionDescriptor(values, pattern: "/Test");
|
||||
|
||||
var endpointFeature = new EndpointSelectorContext
|
||||
{
|
||||
RouteValues = new RouteValueDictionary()
|
||||
};
|
||||
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||
featureCollection.Set<IRoutingFeature>(endpointFeature);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var actionInvokerCalled = false;
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() =>
|
||||
{
|
||||
actionInvokerCalled = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
InvokerFactory
|
||||
.Setup(m => m.CreateInvoker(It.IsAny<ActionContext>()))
|
||||
.Returns(actionInvokerMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoint = CreateAttributeRoutedEndpoint(action);
|
||||
|
||||
// Assert
|
||||
await endpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddEndpoints_ConventionalRouted_UsesActionInvoker()
|
||||
{
|
||||
// Arrange
|
||||
var values = new
|
||||
{
|
||||
action = "Test",
|
||||
controller = "Test",
|
||||
page = (string)null,
|
||||
};
|
||||
|
||||
var action = CreateActionDescriptor(values);
|
||||
|
||||
var endpointFeature = new EndpointSelectorContext
|
||||
{
|
||||
RouteValues = new RouteValueDictionary()
|
||||
};
|
||||
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||
featureCollection.Set<IRoutingFeature>(endpointFeature);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var actionInvokerCalled = false;
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() =>
|
||||
{
|
||||
actionInvokerCalled = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
InvokerFactory
|
||||
.Setup(m => m.CreateInvoker(It.IsAny<ActionContext>()))
|
||||
.Returns(actionInvokerMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoint = CreateConventionalRoutedEndpoint(action, "{controller}/{action}");
|
||||
|
||||
// Assert
|
||||
await endpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,8 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
ILoggerFactory loggerFactory = null,
|
||||
object diagnosticListener = null)
|
||||
{
|
||||
var actionContextAccessor = new ActionContextAccessor();
|
||||
|
||||
if (actionDescriptor == null)
|
||||
{
|
||||
var mockAction = new Mock<ActionDescriptor>();
|
||||
|
|
@ -105,8 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
invokerFactory,
|
||||
actionSelector,
|
||||
diagnosticSource,
|
||||
loggerFactory,
|
||||
actionContextAccessor);
|
||||
loggerFactory);
|
||||
}
|
||||
|
||||
private RouteContext CreateRouteContext()
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
|
|
@ -25,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||
|
||||
var provider1 = new Mock<IPageApplicationModelProvider>();
|
||||
var provider2 = new Mock<IPageApplicationModelProvider>();
|
||||
|
|
@ -75,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var loader = new DefaultPageLoader(
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
|
|
@ -86,6 +91,60 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
provider2.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CreatesEndpoint_WithRoute()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
};
|
||||
|
||||
var transformer = new Mock<RoutePatternTransformer>();
|
||||
transformer
|
||||
.Setup(t => t.SubstituteRequiredValues(It.IsAny<RoutePattern>(), It.IsAny<object>()))
|
||||
.Returns<RoutePattern, object>((p, v) => p);
|
||||
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(transformer.Object);
|
||||
|
||||
var provider = new Mock<IPageApplicationModelProvider>();
|
||||
|
||||
var pageApplicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty<object>());
|
||||
|
||||
provider.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
|
||||
.Callback((PageApplicationModelProviderContext c) =>
|
||||
{
|
||||
Assert.Null(c.PageApplicationModel);
|
||||
c.PageApplicationModel = pageApplicationModel;
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var providers = new[]
|
||||
{
|
||||
provider.Object,
|
||||
};
|
||||
|
||||
var loader = new DefaultPageLoader(
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
var result = loader.Load(descriptor);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_InvokesApplicationModelProviders_WithTheRightOrder()
|
||||
{
|
||||
|
|
@ -94,6 +153,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var compilerProvider = GetCompilerProvider();
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||
|
||||
var provider1 = new Mock<IPageApplicationModelProvider>();
|
||||
provider1.SetupGet(p => p.Order).Returns(10);
|
||||
|
|
@ -138,6 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var loader = new DefaultPageLoader(
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
|
|
|
|||
|
|
@ -232,8 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var result = Assert.Single(context.Results);
|
||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal(model.RelativePath, descriptor.RelativePath);
|
||||
var actual = Assert.Single(descriptor.EndpointMetadata);
|
||||
Assert.Same(expected, actual);
|
||||
Assert.Single(descriptor.EndpointMetadata, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1519,6 +1519,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
selector.Object,
|
||||
diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"),
|
||||
logger ?? NullLogger.Instance,
|
||||
ActionContextAccessor.Null,
|
||||
new ActionResultTypeMapper(),
|
||||
pageContext,
|
||||
filters ?? Array.Empty<IFilterMetadata>(),
|
||||
|
|
|
|||
|
|
@ -46,13 +46,6 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
|
||||
var endpoint = context.GetEndpoint();
|
||||
|
||||
// Workaround for https://github.com/aspnet/AspNetCore/issues/7011. Do not use the AuthorizationMiddleware for Razor Pages
|
||||
if (endpoint != null && endpoint.Metadata.Any(m => m.GetType().FullName == "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteMetadata"))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
|
||||
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue