* 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
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()
|
private static void ThrowIndexArgumentOutOfRangeException()
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException("index");
|
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.
|
// 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;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
@ -24,5 +27,30 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
/// property.
|
/// property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract int Order { get; }
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
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
|
// We special case low numbers of candidates, so we want to verify that it works correctly for a variety
|
||||||
// of input sizes.
|
// of input sizes.
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
||||||
|
|
@ -114,10 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
{
|
{
|
||||||
var dataSource = new ActionEndpointDataSource(
|
var dataSource = new ActionEndpointDataSource(
|
||||||
actionDescriptorCollectionProvider,
|
actionDescriptorCollectionProvider,
|
||||||
new ActionEndpointFactory(
|
new ActionEndpointFactory(new MockRoutePatternTransformer()));
|
||||||
new MockRoutePatternTransformer(),
|
|
||||||
new MvcEndpointInvokerFactory(
|
|
||||||
new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>()))));
|
|
||||||
|
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddSingleton<ActionEndpointDataSource>();
|
services.TryAddSingleton<ActionEndpointDataSource>();
|
||||||
services.TryAddSingleton<ControllerActionEndpointDataSource>();
|
services.TryAddSingleton<ControllerActionEndpointDataSource>();
|
||||||
services.TryAddSingleton<ActionEndpointFactory>();
|
services.TryAddSingleton<ActionEndpointFactory>();
|
||||||
services.TryAddSingleton<MvcEndpointInvokerFactory>();
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Middleware pipeline filter related
|
// Middleware pipeline filter related
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
{
|
{
|
||||||
public class ActionContextAccessor : IActionContextAccessor
|
public class ActionContextAccessor : IActionContextAccessor
|
||||||
{
|
{
|
||||||
|
internal static readonly IActionContextAccessor Null = new NullActionContextAccessor();
|
||||||
|
|
||||||
private static readonly AsyncLocal<ActionContext> _storage = new AsyncLocal<ActionContext>();
|
private static readonly AsyncLocal<ActionContext> _storage = new AsyncLocal<ActionContext>();
|
||||||
|
|
||||||
public ActionContext ActionContext
|
public ActionContext ActionContext
|
||||||
|
|
@ -14,5 +16,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
get { return _storage.Value; }
|
get { return _storage.Value; }
|
||||||
set { _storage.Value = 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(
|
internal ControllerActionInvoker(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
|
IActionContextAccessor actionContextAccessor,
|
||||||
IActionResultTypeMapper mapper,
|
IActionResultTypeMapper mapper,
|
||||||
ControllerContext controllerContext,
|
ControllerContext controllerContext,
|
||||||
ControllerActionInvokerCacheEntry cacheEntry,
|
ControllerActionInvokerCacheEntry cacheEntry,
|
||||||
IFilterMetadata[] filters)
|
IFilterMetadata[] filters)
|
||||||
: base(diagnosticListener, logger, mapper, controllerContext, filters, controllerContext.ValueProviderFactories)
|
: base(diagnosticListener, logger, actionContextAccessor, mapper, controllerContext, filters, controllerContext.ValueProviderFactories)
|
||||||
{
|
{
|
||||||
if (cacheEntry == null)
|
if (cacheEntry == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly DiagnosticListener _diagnosticListener;
|
private readonly DiagnosticListener _diagnosticListener;
|
||||||
private readonly IActionResultTypeMapper _mapper;
|
private readonly IActionResultTypeMapper _mapper;
|
||||||
|
private readonly IActionContextAccessor _actionContextAccessor;
|
||||||
|
|
||||||
public ControllerActionInvokerProvider(
|
public ControllerActionInvokerProvider(
|
||||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
|
|
@ -28,6 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
IActionResultTypeMapper mapper)
|
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;
|
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||||
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
||||||
|
|
@ -35,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
|
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
|
||||||
_diagnosticListener = diagnosticListener;
|
_diagnosticListener = diagnosticListener;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_actionContextAccessor = actionContextAccessor ?? ActionContextAccessor.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order => -1000;
|
public int Order => -1000;
|
||||||
|
|
@ -61,6 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
var invoker = new ControllerActionInvoker(
|
var invoker = new ControllerActionInvoker(
|
||||||
_logger,
|
_logger,
|
||||||
_diagnosticListener,
|
_diagnosticListener,
|
||||||
|
_actionContextAccessor,
|
||||||
_mapper,
|
_mapper,
|
||||||
controllerContext,
|
controllerContext,
|
||||||
cacheEntry,
|
cacheEntry,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
{
|
{
|
||||||
protected readonly DiagnosticListener _diagnosticListener;
|
protected readonly DiagnosticListener _diagnosticListener;
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
|
protected readonly IActionContextAccessor _actionContextAccessor;
|
||||||
protected readonly IActionResultTypeMapper _mapper;
|
protected readonly IActionResultTypeMapper _mapper;
|
||||||
protected readonly ActionContext _actionContext;
|
protected readonly ActionContext _actionContext;
|
||||||
protected readonly IFilterMetadata[] _filters;
|
protected readonly IFilterMetadata[] _filters;
|
||||||
|
|
@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
public ResourceInvoker(
|
public ResourceInvoker(
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
IActionContextAccessor actionContextAccessor,
|
||||||
IActionResultTypeMapper mapper,
|
IActionResultTypeMapper mapper,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IFilterMetadata[] filters,
|
IFilterMetadata[] filters,
|
||||||
|
|
@ -46,6 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
{
|
{
|
||||||
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
|
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
|
||||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||||
_actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
|
_actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
|
||||||
|
|
||||||
|
|
@ -58,6 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_actionContextAccessor.ActionContext = _actionContext;
|
||||||
|
|
||||||
_diagnosticListener.BeforeAction(
|
_diagnosticListener.BeforeAction(
|
||||||
_actionContext.ActionDescriptor,
|
_actionContext.ActionDescriptor,
|
||||||
_actionContext.HttpContext,
|
_actionContext.HttpContext,
|
||||||
|
|
|
||||||
|
|
@ -9,32 +9,25 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
internal class ActionEndpointFactory
|
internal class ActionEndpointFactory
|
||||||
{
|
{
|
||||||
private readonly RoutePatternTransformer _routePatternTransformer;
|
private readonly RoutePatternTransformer _routePatternTransformer;
|
||||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
|
||||||
|
|
||||||
public ActionEndpointFactory(
|
public ActionEndpointFactory(RoutePatternTransformer routePatternTransformer)
|
||||||
RoutePatternTransformer routePatternTransformer,
|
|
||||||
MvcEndpointInvokerFactory invokerFactory)
|
|
||||||
{
|
{
|
||||||
if (routePatternTransformer == null)
|
if (routePatternTransformer == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(routePatternTransformer));
|
throw new ArgumentNullException(nameof(routePatternTransformer));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invokerFactory == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(invokerFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
_routePatternTransformer = routePatternTransformer;
|
_routePatternTransformer = routePatternTransformer;
|
||||||
_invokerFactory = invokerFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddEndpoints(
|
public void AddEndpoints(
|
||||||
|
|
@ -188,13 +181,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
bool suppressPathMatching,
|
bool suppressPathMatching,
|
||||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
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) =>
|
RequestDelegate requestDelegate = (context) =>
|
||||||
{
|
{
|
||||||
var routeData = context.GetRouteData();
|
var routeData = context.GetRouteData();
|
||||||
|
|
||||||
var actionContext = new ActionContext(context, routeData, action);
|
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();
|
return invoker.InvokeAsync();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,32 +14,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
internal class MvcAttributeRouteHandler : IRouter
|
internal class MvcAttributeRouteHandler : IRouter
|
||||||
{
|
{
|
||||||
private readonly IActionContextAccessor _actionContextAccessor;
|
|
||||||
private readonly IActionInvokerFactory _actionInvokerFactory;
|
private readonly IActionInvokerFactory _actionInvokerFactory;
|
||||||
private readonly IActionSelector _actionSelector;
|
private readonly IActionSelector _actionSelector;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private DiagnosticListener _diagnosticListener;
|
private readonly DiagnosticListener _diagnosticListener;
|
||||||
|
|
||||||
public MvcAttributeRouteHandler(
|
public MvcAttributeRouteHandler(
|
||||||
IActionInvokerFactory actionInvokerFactory,
|
IActionInvokerFactory actionInvokerFactory,
|
||||||
IActionSelector actionSelector,
|
IActionSelector actionSelector,
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
ILoggerFactory loggerFactory)
|
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;
|
_actionInvokerFactory = actionInvokerFactory;
|
||||||
_actionSelector = actionSelector;
|
_actionSelector = actionSelector;
|
||||||
_diagnosticListener = diagnosticListener;
|
_diagnosticListener = diagnosticListener;
|
||||||
|
|
@ -94,11 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var routeData = c.GetRouteData();
|
var routeData = c.GetRouteData();
|
||||||
|
|
||||||
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
||||||
if (_actionContextAccessor != null)
|
|
||||||
{
|
|
||||||
_actionContextAccessor.ActionContext = actionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
||||||
if (invoker == null)
|
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
|
internal class MvcRouteHandler : IRouter
|
||||||
{
|
{
|
||||||
private readonly IActionContextAccessor _actionContextAccessor;
|
|
||||||
private readonly IActionInvokerFactory _actionInvokerFactory;
|
private readonly IActionInvokerFactory _actionInvokerFactory;
|
||||||
private readonly IActionSelector _actionSelector;
|
private readonly IActionSelector _actionSelector;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
@ -24,21 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
IActionSelector actionSelector,
|
IActionSelector actionSelector,
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
ILoggerFactory loggerFactory)
|
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;
|
_actionInvokerFactory = actionInvokerFactory;
|
||||||
_actionSelector = actionSelector;
|
_actionSelector = actionSelector;
|
||||||
_diagnosticListener = diagnosticListener;
|
_diagnosticListener = diagnosticListener;
|
||||||
|
|
@ -82,11 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var routeData = c.GetRouteData();
|
var routeData = c.GetRouteData();
|
||||||
|
|
||||||
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
|
||||||
if (_actionContextAccessor != null)
|
|
||||||
{
|
|
||||||
_actionContextAccessor.ActionContext = actionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
|
||||||
if (invoker == null)
|
if (invoker == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
|
@ -59,5 +60,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
/// Gets or sets the <see cref="TypeInfo"/> of the page.
|
/// Gets or sets the <see cref="TypeInfo"/> of the page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TypeInfo PageTypeInfo { get; set; }
|
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.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources;
|
using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources;
|
||||||
|
|
@ -82,6 +83,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorPagesRazorViewEngineOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorPagesRazorViewEngineOptionsSetup>());
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, PageLoaderMatcherPolicy>());
|
||||||
|
|
||||||
// Action description and invocation
|
// Action description and invocation
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
@ -17,12 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
||||||
private readonly IViewCompilerProvider _viewCompilerProvider;
|
private readonly IViewCompilerProvider _viewCompilerProvider;
|
||||||
|
private readonly ActionEndpointFactory _endpointFactory;
|
||||||
private readonly PageConventionCollection _conventions;
|
private readonly PageConventionCollection _conventions;
|
||||||
private readonly FilterCollection _globalFilters;
|
private readonly FilterCollection _globalFilters;
|
||||||
|
|
||||||
public DefaultPageLoader(
|
public DefaultPageLoader(
|
||||||
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
||||||
IViewCompilerProvider viewCompilerProvider,
|
IViewCompilerProvider viewCompilerProvider,
|
||||||
|
ActionEndpointFactory endpointFactory,
|
||||||
IOptions<RazorPagesOptions> pageOptions,
|
IOptions<RazorPagesOptions> pageOptions,
|
||||||
IOptions<MvcOptions> mvcOptions)
|
IOptions<MvcOptions> mvcOptions)
|
||||||
{
|
{
|
||||||
|
|
@ -30,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
.OrderBy(p => p.Order)
|
.OrderBy(p => p.Order)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
_viewCompilerProvider = viewCompilerProvider;
|
_viewCompilerProvider = viewCompilerProvider;
|
||||||
|
_endpointFactory = endpointFactory;
|
||||||
_conventions = pageOptions.Value.Conventions;
|
_conventions = pageOptions.Value.Conventions;
|
||||||
_globalFilters = mvcOptions.Value.Filters;
|
_globalFilters = mvcOptions.Value.Filters;
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +64,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
ApplyConventions(_conventions, context.PageApplicationModel);
|
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(
|
internal static void ApplyConventions(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
@ -106,6 +107,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
descriptor.RouteValues.Add("page", model.ViewEnginePath);
|
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);
|
actions.Add(descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,5 +143,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
// Combine transformed page route with template
|
// Combine transformed page route with template
|
||||||
return AttributeRouteModel.CombineTemplates(transformedPageRoute, pageRouteMetadata.RouteTemplate);
|
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,
|
IPageHandlerMethodSelector handlerMethodSelector,
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
IActionContextAccessor actionContextAccessor,
|
||||||
IActionResultTypeMapper mapper,
|
IActionResultTypeMapper mapper,
|
||||||
PageContext pageContext,
|
PageContext pageContext,
|
||||||
IFilterMetadata[] filterMetadata,
|
IFilterMetadata[] filterMetadata,
|
||||||
|
|
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
: base(
|
: base(
|
||||||
diagnosticListener,
|
diagnosticListener,
|
||||||
logger,
|
logger,
|
||||||
|
actionContextAccessor,
|
||||||
mapper,
|
mapper,
|
||||||
pageContext,
|
pageContext,
|
||||||
filterMetadata,
|
filterMetadata,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
private readonly DiagnosticListener _diagnosticListener;
|
private readonly DiagnosticListener _diagnosticListener;
|
||||||
private readonly ILogger<PageActionInvoker> _logger;
|
private readonly ILogger<PageActionInvoker> _logger;
|
||||||
private readonly IActionResultTypeMapper _mapper;
|
private readonly IActionResultTypeMapper _mapper;
|
||||||
|
private readonly IActionContextAccessor _actionContextAccessor;
|
||||||
|
|
||||||
private volatile InnerCache _currentCache;
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
public PageActionInvokerProvider(
|
public PageActionInvokerProvider(
|
||||||
|
|
@ -57,6 +59,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
DiagnosticListener diagnosticListener,
|
DiagnosticListener diagnosticListener,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IActionResultTypeMapper mapper)
|
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;
|
_loader = loader;
|
||||||
_pageFactoryProvider = pageFactoryProvider;
|
_pageFactoryProvider = pageFactoryProvider;
|
||||||
|
|
@ -75,6 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
_diagnosticListener = diagnosticListener;
|
_diagnosticListener = diagnosticListener;
|
||||||
_logger = loggerFactory.CreateLogger<PageActionInvoker>();
|
_logger = loggerFactory.CreateLogger<PageActionInvoker>();
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_actionContextAccessor = actionContextAccessor ?? ActionContextAccessor.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order { get; } = -1000;
|
public int Order { get; } = -1000;
|
||||||
|
|
@ -154,6 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
_selector,
|
_selector,
|
||||||
_diagnosticListener,
|
_diagnosticListener,
|
||||||
_logger,
|
_logger,
|
||||||
|
_actionContextAccessor,
|
||||||
_mapper,
|
_mapper,
|
||||||
pageContext,
|
pageContext,
|
||||||
filters,
|
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(
|
: base(
|
||||||
logger,
|
logger,
|
||||||
diagnosticListener,
|
diagnosticListener,
|
||||||
|
ActionContextAccessor.Null,
|
||||||
mapper,
|
mapper,
|
||||||
CreateControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
|
CreateControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
|
||||||
CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory),
|
CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory),
|
||||||
|
|
|
||||||
|
|
@ -1343,6 +1343,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
var invoker = new ControllerActionInvoker(
|
var invoker = new ControllerActionInvoker(
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
|
ActionContextAccessor.Null,
|
||||||
new ActionResultTypeMapper(),
|
new ActionResultTypeMapper(),
|
||||||
controllerContext,
|
controllerContext,
|
||||||
cacheEntry,
|
cacheEntry,
|
||||||
|
|
@ -1623,6 +1624,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
var invoker = new ControllerActionInvoker(
|
var invoker = new ControllerActionInvoker(
|
||||||
logger,
|
logger,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
|
ActionContextAccessor.Null,
|
||||||
new ActionResultTypeMapper(),
|
new ActionResultTypeMapper(),
|
||||||
controllerContext,
|
controllerContext,
|
||||||
cacheEntry,
|
cacheEntry,
|
||||||
|
|
|
||||||
|
|
@ -148,9 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
|
||||||
var endpointFactory = new ActionEndpointFactory(
|
var endpointFactory = new ActionEndpointFactory(serviceProvider.GetRequiredService<RoutePatternTransformer>());
|
||||||
serviceProvider.GetRequiredService<RoutePatternTransformer>(),
|
|
||||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())));
|
|
||||||
|
|
||||||
return CreateDataSource(actions, endpointFactory);
|
return CreateDataSource(actions, endpointFactory);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,112 +32,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
});
|
});
|
||||||
|
|
||||||
Services = serviceCollection.BuildServiceProvider();
|
Services = serviceCollection.BuildServiceProvider();
|
||||||
InvokerFactory = new Mock<IActionInvokerFactory>(MockBehavior.Strict);
|
Factory = new ActionEndpointFactory(Services.GetRequiredService<RoutePatternTransformer>());
|
||||||
Factory = new ActionEndpointFactory(
|
|
||||||
Services.GetRequiredService<RoutePatternTransformer>(),
|
|
||||||
new MvcEndpointInvokerFactory(InvokerFactory.Object));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ActionEndpointFactory Factory { get; }
|
internal ActionEndpointFactory Factory { get; }
|
||||||
|
|
||||||
internal Mock<IActionInvokerFactory> InvokerFactory { get; }
|
|
||||||
|
|
||||||
internal IServiceProvider Services { 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]
|
[Fact]
|
||||||
public void AddEndpoints_ConventionalRouted_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName()
|
public void AddEndpoints_ConventionalRouted_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
ILoggerFactory loggerFactory = null,
|
ILoggerFactory loggerFactory = null,
|
||||||
object diagnosticListener = null)
|
object diagnosticListener = null)
|
||||||
{
|
{
|
||||||
var actionContextAccessor = new ActionContextAccessor();
|
|
||||||
|
|
||||||
if (actionDescriptor == null)
|
if (actionDescriptor == null)
|
||||||
{
|
{
|
||||||
var mockAction = new Mock<ActionDescriptor>();
|
var mockAction = new Mock<ActionDescriptor>();
|
||||||
|
|
@ -105,8 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
invokerFactory,
|
invokerFactory,
|
||||||
actionSelector,
|
actionSelector,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
loggerFactory,
|
loggerFactory);
|
||||||
actionContextAccessor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RouteContext CreateRouteContext()
|
private RouteContext CreateRouteContext()
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Razor.Hosting;
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -25,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||||
var mvcOptions = Options.Create(new MvcOptions());
|
var mvcOptions = Options.Create(new MvcOptions());
|
||||||
|
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||||
|
|
||||||
var provider1 = new Mock<IPageApplicationModelProvider>();
|
var provider1 = new Mock<IPageApplicationModelProvider>();
|
||||||
var provider2 = new Mock<IPageApplicationModelProvider>();
|
var provider2 = new Mock<IPageApplicationModelProvider>();
|
||||||
|
|
@ -75,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var loader = new DefaultPageLoader(
|
var loader = new DefaultPageLoader(
|
||||||
providers,
|
providers,
|
||||||
compilerProvider,
|
compilerProvider,
|
||||||
|
endpointFactory,
|
||||||
razorPagesOptions,
|
razorPagesOptions,
|
||||||
mvcOptions);
|
mvcOptions);
|
||||||
|
|
||||||
|
|
@ -86,6 +91,60 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
provider2.Verify();
|
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]
|
[Fact]
|
||||||
public void Load_InvokesApplicationModelProviders_WithTheRightOrder()
|
public void Load_InvokesApplicationModelProviders_WithTheRightOrder()
|
||||||
{
|
{
|
||||||
|
|
@ -94,6 +153,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var compilerProvider = GetCompilerProvider();
|
var compilerProvider = GetCompilerProvider();
|
||||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||||
var mvcOptions = Options.Create(new MvcOptions());
|
var mvcOptions = Options.Create(new MvcOptions());
|
||||||
|
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||||
|
|
||||||
var provider1 = new Mock<IPageApplicationModelProvider>();
|
var provider1 = new Mock<IPageApplicationModelProvider>();
|
||||||
provider1.SetupGet(p => p.Order).Returns(10);
|
provider1.SetupGet(p => p.Order).Returns(10);
|
||||||
|
|
@ -138,6 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var loader = new DefaultPageLoader(
|
var loader = new DefaultPageLoader(
|
||||||
providers,
|
providers,
|
||||||
compilerProvider,
|
compilerProvider,
|
||||||
|
endpointFactory,
|
||||||
razorPagesOptions,
|
razorPagesOptions,
|
||||||
mvcOptions);
|
mvcOptions);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,8 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var result = Assert.Single(context.Results);
|
var result = Assert.Single(context.Results);
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal(model.RelativePath, descriptor.RelativePath);
|
Assert.Equal(model.RelativePath, descriptor.RelativePath);
|
||||||
var actual = Assert.Single(descriptor.EndpointMetadata);
|
Assert.Single(descriptor.EndpointMetadata, expected);
|
||||||
Assert.Same(expected, actual);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -1519,6 +1519,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
selector.Object,
|
selector.Object,
|
||||||
diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"),
|
diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
logger ?? NullLogger.Instance,
|
logger ?? NullLogger.Instance,
|
||||||
|
ActionContextAccessor.Null,
|
||||||
new ActionResultTypeMapper(),
|
new ActionResultTypeMapper(),
|
||||||
pageContext,
|
pageContext,
|
||||||
filters ?? Array.Empty<IFilterMetadata>(),
|
filters ?? Array.Empty<IFilterMetadata>(),
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,6 @@ namespace Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
var endpoint = context.GetEndpoint();
|
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
|
// Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
|
||||||
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
|
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue