Make page load async when using endpoint routing (#7938)
* Make page load async Fixes #8016
This commit is contained in:
parent
3652b7a927
commit
39e52578d3
|
|
@ -516,6 +516,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor Select(Microsoft.AspNetCore.Mvc.RazorPages.PageContext context);
|
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor Select(Microsoft.AspNetCore.Mvc.RazorPages.PageContext context);
|
||||||
}
|
}
|
||||||
|
[System.ObsoleteAttribute("This type is obsolete. Use PageLoader instead.")]
|
||||||
public partial interface IPageLoader
|
public partial interface IPageLoader
|
||||||
{
|
{
|
||||||
Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor Load(Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor actionDescriptor);
|
Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor Load(Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor actionDescriptor);
|
||||||
|
|
@ -534,6 +535,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
System.Reflection.PropertyInfo Microsoft.AspNetCore.Mvc.Infrastructure.IPropertyInfoParameterDescriptor.PropertyInfo { get { throw null; } }
|
System.Reflection.PropertyInfo Microsoft.AspNetCore.Mvc.Infrastructure.IPropertyInfoParameterDescriptor.PropertyInfo { get { throw null; } }
|
||||||
public System.Reflection.PropertyInfo Property { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public System.Reflection.PropertyInfo Property { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
|
public abstract partial class PageLoader : Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader
|
||||||
|
{
|
||||||
|
protected PageLoader() { }
|
||||||
|
public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor> LoadAsync(Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor actionDescriptor);
|
||||||
|
Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader.Load(Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor actionDescriptor) { throw null; }
|
||||||
|
}
|
||||||
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false, Inherited=true)]
|
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false, Inherited=true)]
|
||||||
public partial class PageModelAttribute : System.Attribute
|
public partial class PageModelAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
|
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
|
||||||
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactoryProvider>();
|
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactoryProvider>();
|
||||||
|
|
||||||
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
services.TryAddSingleton<IPageLoader>(s => s.GetRequiredService<PageLoader>());
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
services.TryAddSingleton<PageLoader, DefaultPageLoader>();
|
||||||
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
||||||
|
|
||||||
// Action executors
|
// Action executors
|
||||||
|
|
|
||||||
|
|
@ -2,37 +2,45 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
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.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
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
|
||||||
{
|
{
|
||||||
internal class DefaultPageLoader : IPageLoader
|
internal class DefaultPageLoader : PageLoader
|
||||||
{
|
{
|
||||||
|
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||||
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
||||||
private readonly IViewCompilerProvider _viewCompilerProvider;
|
private readonly IViewCompilerProvider _viewCompilerProvider;
|
||||||
private readonly ActionEndpointFactory _endpointFactory;
|
private readonly ActionEndpointFactory _endpointFactory;
|
||||||
private readonly PageConventionCollection _conventions;
|
private readonly PageConventionCollection _conventions;
|
||||||
private readonly FilterCollection _globalFilters;
|
private readonly FilterCollection _globalFilters;
|
||||||
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
public DefaultPageLoader(
|
public DefaultPageLoader(
|
||||||
|
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
|
||||||
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
||||||
IViewCompilerProvider viewCompilerProvider,
|
IViewCompilerProvider viewCompilerProvider,
|
||||||
ActionEndpointFactory endpointFactory,
|
ActionEndpointFactory endpointFactory,
|
||||||
IOptions<RazorPagesOptions> pageOptions,
|
IOptions<RazorPagesOptions> pageOptions,
|
||||||
IOptions<MvcOptions> mvcOptions)
|
IOptions<MvcOptions> mvcOptions)
|
||||||
{
|
{
|
||||||
|
_collectionProvider = actionDescriptorCollectionProvider;
|
||||||
_applicationModelProviders = applicationModelProviders
|
_applicationModelProviders = applicationModelProviders
|
||||||
.OrderBy(p => p.Order)
|
.OrderBy(p => p.Order)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
_viewCompilerProvider = viewCompilerProvider;
|
_viewCompilerProvider = viewCompilerProvider;
|
||||||
_endpointFactory = endpointFactory;
|
_endpointFactory = endpointFactory;
|
||||||
_conventions = pageOptions.Value.Conventions;
|
_conventions = pageOptions.Value.Conventions;
|
||||||
|
|
@ -41,16 +49,42 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler();
|
private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler();
|
||||||
|
|
||||||
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
|
private ConcurrentDictionary<PageActionDescriptor, Task<CompiledPageActionDescriptor>> CurrentCache
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var current = _currentCache;
|
||||||
|
var actionDescriptors = _collectionProvider.ActionDescriptors;
|
||||||
|
|
||||||
|
if (current == null || current.Version != actionDescriptors.Version)
|
||||||
|
{
|
||||||
|
current = new InnerCache(actionDescriptors.Version);
|
||||||
|
_currentCache = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current.Entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor)
|
||||||
{
|
{
|
||||||
if (actionDescriptor == null)
|
if (actionDescriptor == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(actionDescriptor));
|
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
|
var cache = CurrentCache;
|
||||||
var viewDescriptor = compileTask.GetAwaiter().GetResult();
|
if (cache.TryGetValue(actionDescriptor, out var compiledDescriptorTask))
|
||||||
|
{
|
||||||
|
return compiledDescriptorTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.GetOrAdd(actionDescriptor, LoadAsyncCore(actionDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CompiledPageActionDescriptor> LoadAsyncCore(PageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
var viewDescriptor = await Compiler.CompileAsync(actionDescriptor.RelativePath);
|
||||||
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
||||||
for (var i = 0; i < _applicationModelProviders.Length; i++)
|
for (var i = 0; i < _applicationModelProviders.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -65,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
ApplyConventions(_conventions, context.PageApplicationModel);
|
ApplyConventions(_conventions, context.PageApplicationModel);
|
||||||
|
|
||||||
var compiled = 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...
|
// 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
|
// 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
|
// compiling/loading the page. Then once we have a match we load the page and we can create an endpoint
|
||||||
|
|
@ -128,5 +162,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
attributes.OfType<TConvention>());
|
attributes.OfType<TConvention>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class InnerCache
|
||||||
|
{
|
||||||
|
public InnerCache(int version)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
Entries = new ConcurrentDictionary<PageActionDescriptor, Task<CompiledPageActionDescriptor>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentDictionary<PageActionDescriptor, Task<CompiledPageActionDescriptor>> Entries { get; }
|
||||||
|
|
||||||
|
public int Version { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
// 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;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("This type is obsolete. Use " + nameof(PageLoader) + " instead.")]
|
||||||
public interface IPageLoader
|
public interface IPageLoader
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
@ -19,9 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
internal class PageActionInvokerProvider : IActionInvokerProvider
|
internal class PageActionInvokerProvider : IActionInvokerProvider
|
||||||
{
|
{
|
||||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
private readonly PageLoader _loader;
|
||||||
|
|
||||||
private readonly IPageLoader _loader;
|
|
||||||
private readonly IPageFactoryProvider _pageFactoryProvider;
|
private readonly IPageFactoryProvider _pageFactoryProvider;
|
||||||
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
||||||
private readonly IModelBinderFactory _modelBinderFactory;
|
private readonly IModelBinderFactory _modelBinderFactory;
|
||||||
|
|
@ -43,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
private volatile InnerCache _currentCache;
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
public PageActionInvokerProvider(
|
public PageActionInvokerProvider(
|
||||||
IPageLoader loader,
|
PageLoader loader,
|
||||||
IPageFactoryProvider pageFactoryProvider,
|
IPageFactoryProvider pageFactoryProvider,
|
||||||
IPageModelFactoryProvider modelFactoryProvider,
|
IPageModelFactoryProvider modelFactoryProvider,
|
||||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||||
|
|
@ -81,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageActionInvokerProvider(
|
public PageActionInvokerProvider(
|
||||||
IPageLoader loader,
|
PageLoader loader,
|
||||||
IPageFactoryProvider pageFactoryProvider,
|
IPageFactoryProvider pageFactoryProvider,
|
||||||
IPageModelFactoryProvider modelFactoryProvider,
|
IPageModelFactoryProvider modelFactoryProvider,
|
||||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||||
|
|
@ -139,7 +138,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
IFilterMetadata[] filters;
|
IFilterMetadata[] filters;
|
||||||
if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
|
if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
|
||||||
{
|
{
|
||||||
actionContext.ActionDescriptor = _loader.Load(actionDescriptor);
|
CompiledPageActionDescriptor compiledPageActionDescriptor;
|
||||||
|
if (_mvcOptions.EnableEndpointRouting)
|
||||||
|
{
|
||||||
|
// With endpoint routing, PageLoaderMatcherPolicy should have already produced a CompiledPageActionDescriptor.
|
||||||
|
compiledPageActionDescriptor = (CompiledPageActionDescriptor)actionDescriptor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// With legacy routing, we're forced to perform a blocking call. The exceptation is that
|
||||||
|
// in the most common case - build time views or successsively cached runtime views - this should finish synchronously.
|
||||||
|
compiledPageActionDescriptor = _loader.LoadAsync(actionDescriptor).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
actionContext.ActionDescriptor = compiledPageActionDescriptor;
|
||||||
|
|
||||||
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
|
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
|
||||||
filters = filterFactoryResult.Filters;
|
filters = filterFactoryResult.Filters;
|
||||||
|
|
@ -285,7 +297,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
private PageHandlerBinderDelegate[] GetHandlerBinders(CompiledPageActionDescriptor actionDescriptor)
|
private PageHandlerBinderDelegate[] GetHandlerBinders(CompiledPageActionDescriptor actionDescriptor)
|
||||||
{
|
{
|
||||||
if (actionDescriptor.HandlerMethods == null ||actionDescriptor.HandlerMethods.Count == 0)
|
if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<PageHandlerBinderDelegate>();
|
return Array.Empty<PageHandlerBinderDelegate>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
public abstract class PageLoader : IPageLoader
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Produces a <see cref="CompiledPageActionDescriptor"/> given a <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionDescriptor">The <see cref="PageActionDescriptor"/>.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns a <see cref="CompiledPageActionDescriptor"/>.</returns>
|
||||||
|
public abstract Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor);
|
||||||
|
|
||||||
|
CompiledPageActionDescriptor IPageLoader.Load(PageActionDescriptor actionDescriptor)
|
||||||
|
=> LoadAsync(actionDescriptor).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,15 +7,14 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
internal class PageLoaderMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
|
internal class PageLoaderMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
|
||||||
{
|
{
|
||||||
private readonly IPageLoader _loader;
|
private readonly PageLoader _loader;
|
||||||
|
|
||||||
public PageLoaderMatcherPolicy(IPageLoader loader)
|
public PageLoaderMatcherPolicy(PageLoader loader)
|
||||||
{
|
{
|
||||||
if (loader == null)
|
if (loader == null)
|
||||||
{
|
{
|
||||||
|
|
@ -69,21 +68,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(candidates));
|
throw new ArgumentNullException(nameof(candidates));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < candidates.Count; i++)
|
for (var i = 0; i < candidates.Count; i++)
|
||||||
{
|
{
|
||||||
ref var candidate = ref candidates[i];
|
ref var candidate = ref candidates[i];
|
||||||
var endpoint = (RouteEndpoint)candidate.Endpoint;
|
var endpoint = candidate.Endpoint;
|
||||||
|
|
||||||
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
|
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
|
||||||
if (page != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
var compiled = _loader.Load(page);
|
// We found an endpoint instance that has a PageActionDescriptor, but not a
|
||||||
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidate.Values);
|
// CompiledPageActionDescriptor. Update the CandidateSet.
|
||||||
|
var compiled = _loader.LoadAsync(page);
|
||||||
|
if (compiled.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
candidates.ReplaceEndpoint(i, compiled.Result.Endpoint, candidate.Values);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// In the most common case, GetOrAddAsync will return a synchronous result.
|
||||||
|
// Avoid going async since this is a fairly hot path.
|
||||||
|
return ApplyAsyncAwaited(candidates, compiled, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplyAsyncAwaited(CandidateSet candidates, Task<CompiledPageActionDescriptor> actionDescriptorTask, int index)
|
||||||
|
{
|
||||||
|
var compiled = await actionDescriptorTask;
|
||||||
|
candidates.ReplaceEndpoint(index, compiled.Endpoint, candidates[index].Values);
|
||||||
|
|
||||||
|
for (var i = index + 1; i < candidates.Count; i++)
|
||||||
|
{
|
||||||
|
var candidate = candidates[i];
|
||||||
|
var endpoint = candidate.Endpoint;
|
||||||
|
|
||||||
|
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
|
||||||
|
if (page != null)
|
||||||
|
{
|
||||||
|
compiled = await _loader.LoadAsync(page);
|
||||||
|
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidates[i].Values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DebuggerDisplayString => $"{{ViewEnginePath = {nameof(ViewEnginePath)}, RelativePath = {nameof(RelativePath)}}}";
|
private string DebuggerDisplayString => $"{nameof(ViewEnginePath)} = {ViewEnginePath}, {nameof(RelativePath)} = {RelativePath}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
|
@ -18,8 +20,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
public class DefaultPageLoaderTest
|
public class DefaultPageLoaderTest
|
||||||
{
|
{
|
||||||
|
private readonly IActionDescriptorCollectionProvider ActionDescriptorCollectionProvider;
|
||||||
|
|
||||||
|
public DefaultPageLoaderTest()
|
||||||
|
{
|
||||||
|
var actionDescriptors = new ActionDescriptorCollection(Array.Empty<ActionDescriptor>(), 1);
|
||||||
|
ActionDescriptorCollectionProvider = Mock.Of<IActionDescriptorCollectionProvider>(v => v.ActionDescriptors == actionDescriptors);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Load_InvokesApplicationModelProviders()
|
public async Task LoadAsync_InvokesApplicationModelProviders()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor();
|
var descriptor = new PageActionDescriptor();
|
||||||
|
|
@ -77,6 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
};
|
};
|
||||||
|
|
||||||
var loader = new DefaultPageLoader(
|
var loader = new DefaultPageLoader(
|
||||||
|
ActionDescriptorCollectionProvider,
|
||||||
providers,
|
providers,
|
||||||
compilerProvider,
|
compilerProvider,
|
||||||
endpointFactory,
|
endpointFactory,
|
||||||
|
|
@ -84,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
mvcOptions);
|
mvcOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = loader.Load(new PageActionDescriptor());
|
var result = await loader.LoadAsync(new PageActionDescriptor());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
provider1.Verify();
|
provider1.Verify();
|
||||||
|
|
@ -92,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Load_CreatesEndpoint_WithRoute()
|
public async Task LoadAsync_CreatesEndpoint_WithRoute()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor()
|
var descriptor = new PageActionDescriptor()
|
||||||
|
|
@ -132,6 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
};
|
};
|
||||||
|
|
||||||
var loader = new DefaultPageLoader(
|
var loader = new DefaultPageLoader(
|
||||||
|
ActionDescriptorCollectionProvider,
|
||||||
providers,
|
providers,
|
||||||
compilerProvider,
|
compilerProvider,
|
||||||
endpointFactory,
|
endpointFactory,
|
||||||
|
|
@ -139,14 +151,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
mvcOptions);
|
mvcOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = loader.Load(descriptor);
|
var result = await loader.LoadAsync(descriptor);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(result.Endpoint);
|
Assert.NotNull(result.Endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Load_InvokesApplicationModelProviders_WithTheRightOrder()
|
public async Task LoadAsync_InvokesApplicationModelProviders_WithTheRightOrder()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor();
|
var descriptor = new PageActionDescriptor();
|
||||||
|
|
@ -196,6 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
};
|
};
|
||||||
|
|
||||||
var loader = new DefaultPageLoader(
|
var loader = new DefaultPageLoader(
|
||||||
|
ActionDescriptorCollectionProvider,
|
||||||
providers,
|
providers,
|
||||||
compilerProvider,
|
compilerProvider,
|
||||||
endpointFactory,
|
endpointFactory,
|
||||||
|
|
@ -203,13 +216,134 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
mvcOptions);
|
mvcOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = loader.Load(new PageActionDescriptor());
|
var result = await loader.LoadAsync(new PageActionDescriptor());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
provider1.Verify();
|
provider1.Verify();
|
||||||
provider2.Verify();
|
provider2.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoadAsync_CachesResults()
|
||||||
|
{
|
||||||
|
// 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(
|
||||||
|
ActionDescriptorCollectionProvider,
|
||||||
|
providers,
|
||||||
|
compilerProvider,
|
||||||
|
endpointFactory,
|
||||||
|
razorPagesOptions,
|
||||||
|
mvcOptions);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = await loader.LoadAsync(descriptor);
|
||||||
|
var result2 = await loader.LoadAsync(descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(result1, result2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoadAsync_UpdatesResults()
|
||||||
|
{
|
||||||
|
// 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 descriptorCollection1 = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
|
var descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
|
||||||
|
|
||||||
|
var actionDescriptorCollectionProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
|
actionDescriptorCollectionProvider
|
||||||
|
.SetupSequence(p => p.ActionDescriptors)
|
||||||
|
.Returns(descriptorCollection1)
|
||||||
|
.Returns(descriptorCollection2);
|
||||||
|
|
||||||
|
var loader = new DefaultPageLoader(
|
||||||
|
actionDescriptorCollectionProvider.Object,
|
||||||
|
providers,
|
||||||
|
compilerProvider,
|
||||||
|
endpointFactory,
|
||||||
|
razorPagesOptions,
|
||||||
|
mvcOptions);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = await loader.LoadAsync(descriptor);
|
||||||
|
var result2 = await loader.LoadAsync(descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(result1, result2);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ApplyConventions_InvokesApplicationModelConventions()
|
public void ApplyConventions_InvokesApplicationModelConventions()
|
||||||
{
|
{
|
||||||
|
|
@ -553,11 +687,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
private static IViewCompilerProvider GetCompilerProvider()
|
private static IViewCompilerProvider GetCompilerProvider()
|
||||||
{
|
{
|
||||||
var descriptor = new CompiledViewDescriptor
|
var compiledItem = TestRazorCompiledItem.CreateForView(typeof(object), "/Views/Index.cshtml");
|
||||||
{
|
var descriptor = new CompiledViewDescriptor(compiledItem);
|
||||||
Item = TestRazorCompiledItem.CreateForView(typeof(object), "/Views/Index.cshtml"),
|
|
||||||
};
|
|
||||||
|
|
||||||
var compiler = new Mock<IViewCompiler>();
|
var compiler = new Mock<IViewCompiler>();
|
||||||
compiler.Setup(c => c.CompileAsync(It.IsAny<string>()))
|
compiler.Setup(c => c.CompileAsync(It.IsAny<string>()))
|
||||||
.ReturnsAsync(descriptor);
|
.ReturnsAsync(descriptor);
|
||||||
|
|
|
||||||
|
|
@ -30,19 +30,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public void OnProvidersExecuting_WithEmptyModel_PopulatesCacheEntry()
|
public void OnProvidersExecuting_WithEmptyModel_PopulatesCacheEntry()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor
|
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||||
{
|
{
|
||||||
RelativePath = "/Path1",
|
RelativePath = "/Path1",
|
||||||
FilterDescriptors = new FilterDescriptor[0],
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
};
|
});
|
||||||
|
|
||||||
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
||||||
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = Mock.Of<PageLoader>();
|
||||||
loader
|
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
|
||||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
|
||||||
|
|
||||||
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
||||||
pageFactoryProvider
|
pageFactoryProvider
|
||||||
|
|
@ -53,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
.Returns(releaser);
|
.Returns(releaser);
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader,
|
||||||
CreateActionDescriptorCollection(descriptor),
|
CreateActionDescriptorCollection(descriptor),
|
||||||
pageFactoryProvider.Object);
|
pageFactoryProvider.Object);
|
||||||
|
|
||||||
|
|
@ -83,25 +80,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public void OnProvidersExecuting_WithModel_PopulatesCacheEntry()
|
public void OnProvidersExecuting_WithModel_PopulatesCacheEntry()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor
|
var descriptor = CreateCompiledPageActionDescriptor(
|
||||||
{
|
new PageActionDescriptor
|
||||||
RelativePath = "/Path1",
|
{
|
||||||
FilterDescriptors = new FilterDescriptor[0]
|
RelativePath = "/Path1",
|
||||||
};
|
FilterDescriptors = new FilterDescriptor[0]
|
||||||
|
},
|
||||||
|
pageType: typeof(PageWithModel),
|
||||||
|
modelType: typeof(DerivedTestPageModel));
|
||||||
|
|
||||||
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
||||||
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
||||||
Func<PageContext, object> modelFactory = _ => null;
|
Func<PageContext, object> modelFactory = _ => null;
|
||||||
Action<PageContext, object> modelDisposer = (_, __) => { };
|
Action<PageContext, object> modelDisposer = (_, __) => { };
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = Mock.Of<PageLoader>();
|
||||||
loader
|
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
|
||||||
.Returns(CreateCompiledPageActionDescriptor(
|
|
||||||
descriptor,
|
|
||||||
pageType: typeof(PageWithModel),
|
|
||||||
modelType: typeof(DerivedTestPageModel)));
|
|
||||||
|
|
||||||
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
||||||
pageFactoryProvider
|
pageFactoryProvider
|
||||||
.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
|
.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
|
||||||
|
|
@ -119,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
.Returns(modelDisposer);
|
.Returns(modelDisposer);
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader,
|
||||||
CreateActionDescriptorCollection(descriptor),
|
CreateActionDescriptorCollection(descriptor),
|
||||||
pageFactoryProvider.Object,
|
pageFactoryProvider.Object,
|
||||||
modelFactoryProvider.Object);
|
modelFactoryProvider.Object);
|
||||||
|
|
@ -162,18 +155,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public void OnProvidersExecuting_CachesViewStartFactories()
|
public void OnProvidersExecuting_CachesViewStartFactories()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor
|
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||||
{
|
{
|
||||||
RelativePath = "/Home/Path1/File.cshtml",
|
RelativePath = "/Home/Path1/File.cshtml",
|
||||||
ViewEnginePath = "/Home/Path1/File.cshtml",
|
ViewEnginePath = "/Home/Path1/File.cshtml",
|
||||||
FilterDescriptors = new FilterDescriptor[0],
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
};
|
}, pageType: typeof(PageWithModel));
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
|
||||||
loader
|
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
|
||||||
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
|
|
||||||
|
|
||||||
|
var loader = Mock.Of<PageLoader>();
|
||||||
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
||||||
|
|
||||||
Func<IRazorPage> factory1 = () => null;
|
Func<IRazorPage> factory1 = () => null;
|
||||||
|
|
@ -191,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
fileProvider.AddFile("/_ViewStart.cshtml", "content2");
|
fileProvider.AddFile("/_ViewStart.cshtml", "content2");
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader,
|
||||||
CreateActionDescriptorCollection(descriptor),
|
CreateActionDescriptorCollection(descriptor),
|
||||||
razorPageFactoryProvider: razorPageFactoryProvider.Object);
|
razorPageFactoryProvider: razorPageFactoryProvider.Object);
|
||||||
|
|
||||||
|
|
@ -216,19 +205,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public void OnProvidersExecuting_CachesEntries()
|
public void OnProvidersExecuting_CachesEntries()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor
|
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||||
{
|
{
|
||||||
RelativePath = "/Path1",
|
RelativePath = "/Path1",
|
||||||
FilterDescriptors = new FilterDescriptor[0],
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
};
|
});
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = Mock.Of<PageLoader>();
|
||||||
loader
|
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
|
||||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader,
|
||||||
CreateActionDescriptorCollection(descriptor));
|
CreateActionDescriptorCollection(descriptor));
|
||||||
|
|
||||||
var context = new ActionInvokerProviderContext(new ActionContext
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
|
@ -263,7 +249,39 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnProvidersExecuting_UpdatesEntriesWhenActionDescriptorProviderCollectionIsUpdated()
|
public void OnProvidersExecuting_DoesNotInvokePageLoader_WhenEndpointRoutingIsUsed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "/Path1",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
});
|
||||||
|
|
||||||
|
var loader = new Mock<PageLoader>();
|
||||||
|
var invokerProvider = CreateInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
CreateActionDescriptorCollection(descriptor),
|
||||||
|
mvcOptions: new MvcOptions { EnableEndpointRouting = true });
|
||||||
|
|
||||||
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = descriptor,
|
||||||
|
HttpContext = new DefaultHttpContext(),
|
||||||
|
RouteData = new RouteData(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
invokerProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.Result);
|
||||||
|
Assert.IsType<PageActionInvoker>(context.Result);
|
||||||
|
loader.Verify(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_InvokesPageLoader_WithoutEndpointRouting()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor
|
var descriptor = new PageActionDescriptor
|
||||||
|
|
@ -272,6 +290,41 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
FilterDescriptors = new FilterDescriptor[0],
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var loader = new Mock<PageLoader>();
|
||||||
|
loader.Setup(l => l.LoadAsync(descriptor))
|
||||||
|
.ReturnsAsync(CreateCompiledPageActionDescriptor(descriptor));
|
||||||
|
|
||||||
|
var invokerProvider = CreateInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
CreateActionDescriptorCollection(descriptor),
|
||||||
|
mvcOptions: new MvcOptions { EnableEndpointRouting = false });
|
||||||
|
|
||||||
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = descriptor,
|
||||||
|
HttpContext = new DefaultHttpContext(),
|
||||||
|
RouteData = new RouteData(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
invokerProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.Result);
|
||||||
|
Assert.IsType<PageActionInvoker>(context.Result);
|
||||||
|
loader.Verify(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_UpdatesEntriesWhenActionDescriptorProviderCollectionIsUpdated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "/Path1",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
});
|
||||||
|
|
||||||
var descriptorCollection1 = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
var descriptorCollection1 = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
var descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
|
var descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
|
||||||
|
|
||||||
|
|
@ -281,13 +334,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
.Returns(descriptorCollection1)
|
.Returns(descriptorCollection1)
|
||||||
.Returns(descriptorCollection2);
|
.Returns(descriptorCollection2);
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = Mock.Of<PageLoader>();
|
||||||
loader
|
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
|
||||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader,
|
||||||
actionDescriptorProvider.Object);
|
actionDescriptorProvider.Object);
|
||||||
|
|
||||||
var context = new ActionInvokerProviderContext(new ActionContext()
|
var context = new ActionInvokerProviderContext(new ActionContext()
|
||||||
|
|
@ -332,10 +382,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
PageTypeInfo = typeof(object).GetTypeInfo(),
|
PageTypeInfo = typeof(object).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = new Mock<PageLoader>();
|
||||||
loader
|
loader
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
||||||
.Returns(compiledPageDescriptor);
|
.ReturnsAsync(compiledPageDescriptor);
|
||||||
|
|
||||||
var mock = new Mock<IRazorPageFactoryProvider>(MockBehavior.Strict);
|
var mock = new Mock<IRazorPageFactoryProvider>(MockBehavior.Strict);
|
||||||
mock
|
mock
|
||||||
|
|
@ -382,10 +432,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
ViewEnginePath = "/Views/Deeper/Index.cshtml"
|
ViewEnginePath = "/Views/Deeper/Index.cshtml"
|
||||||
};
|
};
|
||||||
|
|
||||||
var loader = new Mock<IPageLoader>();
|
var loader = new Mock<PageLoader>();
|
||||||
loader
|
loader
|
||||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
||||||
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
|
.ReturnsAsync(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
|
||||||
|
|
||||||
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
||||||
pageFactory
|
pageFactory
|
||||||
|
|
@ -447,11 +497,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PageActionInvokerProvider CreateInvokerProvider(
|
private static PageActionInvokerProvider CreateInvokerProvider(
|
||||||
IPageLoader loader,
|
PageLoader loader,
|
||||||
IActionDescriptorCollectionProvider actionDescriptorProvider,
|
IActionDescriptorCollectionProvider actionDescriptorProvider,
|
||||||
IPageFactoryProvider pageProvider = null,
|
IPageFactoryProvider pageProvider = null,
|
||||||
IPageModelFactoryProvider modelProvider = null,
|
IPageModelFactoryProvider modelProvider = null,
|
||||||
IRazorPageFactoryProvider razorPageFactoryProvider = null)
|
IRazorPageFactoryProvider razorPageFactoryProvider = null,
|
||||||
|
MvcOptions mvcOptions = null)
|
||||||
{
|
{
|
||||||
var tempDataFactory = new Mock<ITempDataDictionaryFactory>();
|
var tempDataFactory = new Mock<ITempDataDictionaryFactory>();
|
||||||
tempDataFactory
|
tempDataFactory
|
||||||
|
|
@ -460,7 +511,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||||
var mvcOptions = new MvcOptions();
|
mvcOptions = mvcOptions ?? new MvcOptions();
|
||||||
|
|
||||||
var parameterBinder = new ParameterBinder(
|
var parameterBinder = new ParameterBinder(
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
|
|
@ -480,7 +531,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
modelBinderFactory,
|
modelBinderFactory,
|
||||||
tempDataFactory.Object,
|
tempDataFactory.Object,
|
||||||
Options.Create(new MvcOptions()),
|
Options.Create(mvcOptions),
|
||||||
Options.Create(new HtmlHelperOptions()),
|
Options.Create(new HtmlHelperOptions()),
|
||||||
Mock.Of<IPageHandlerMethodSelector>(),
|
Mock.Of<IPageHandlerMethodSelector>(),
|
||||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
// 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 System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
{
|
||||||
|
public class PageLoaderMatcherPolicyTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_UpdatesCandidateSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var compiled = new CompiledPageActionDescriptor();
|
||||||
|
compiled.Endpoint = CreateEndpoint(new PageActionDescriptor());
|
||||||
|
|
||||||
|
var candidateSet = CreateCandidateSet(compiled);
|
||||||
|
|
||||||
|
var loader = Mock.Of<PageLoader>(p => p.LoadAsync(It.IsAny<PageActionDescriptor>()) == Task.FromResult(compiled));
|
||||||
|
var policy = new PageLoaderMatcherPolicy(loader);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(new DefaultHttpContext(), new EndpointSelectorContext(), candidateSet);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(compiled.Endpoint, candidateSet[0].Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_UpdatesCandidateSet_IfLoaderReturnsAsynchronously()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var compiled = new CompiledPageActionDescriptor();
|
||||||
|
compiled.Endpoint = CreateEndpoint(new PageActionDescriptor());
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
var candidateSet = CreateCandidateSet(compiled);
|
||||||
|
|
||||||
|
var loadTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await tcs.Task;
|
||||||
|
return compiled;
|
||||||
|
});
|
||||||
|
var loader = Mock.Of<PageLoader>(p => p.LoadAsync(It.IsAny<PageActionDescriptor>()) == loadTask);
|
||||||
|
var policy = new PageLoaderMatcherPolicy(loader);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var applyTask = policy.ApplyAsync(new DefaultHttpContext(), new EndpointSelectorContext(), candidateSet);
|
||||||
|
tcs.SetResult(0);
|
||||||
|
await applyTask;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(compiled.Endpoint, candidateSet[0].Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Endpoint CreateEndpoint(ActionDescriptor action)
|
||||||
|
{
|
||||||
|
var metadata = new List<object>() { action, };
|
||||||
|
return new Endpoint(
|
||||||
|
(context) => Task.CompletedTask,
|
||||||
|
new EndpointMetadataCollection(metadata),
|
||||||
|
$"test: {action?.DisplayName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CandidateSet CreateCandidateSet(params ActionDescriptor[] actions)
|
||||||
|
{
|
||||||
|
var values = new RouteValueDictionary[actions.Length];
|
||||||
|
for (var i = 0; i < actions.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = new RouteValueDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidateSet = new CandidateSet(
|
||||||
|
actions.Select(CreateEndpoint).ToArray(),
|
||||||
|
values,
|
||||||
|
new int[actions.Length]);
|
||||||
|
return candidateSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue