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);
|
||||
}
|
||||
[System.ObsoleteAttribute("This type is obsolete. Use PageLoader instead.")]
|
||||
public partial interface IPageLoader
|
||||
{
|
||||
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; } }
|
||||
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)]
|
||||
public partial class PageModelAttribute : System.Attribute
|
||||
{
|
||||
|
|
|
|||
|
|
@ -116,7 +116,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
|
||||
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>();
|
||||
|
||||
// Action executors
|
||||
|
|
|
|||
|
|
@ -2,37 +2,45 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
internal class DefaultPageLoader : IPageLoader
|
||||
internal class DefaultPageLoader : PageLoader
|
||||
{
|
||||
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
|
||||
private readonly IViewCompilerProvider _viewCompilerProvider;
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly PageConventionCollection _conventions;
|
||||
private readonly FilterCollection _globalFilters;
|
||||
private volatile InnerCache _currentCache;
|
||||
|
||||
public DefaultPageLoader(
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
|
||||
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
|
||||
IViewCompilerProvider viewCompilerProvider,
|
||||
ActionEndpointFactory endpointFactory,
|
||||
IOptions<RazorPagesOptions> pageOptions,
|
||||
IOptions<MvcOptions> mvcOptions)
|
||||
{
|
||||
_collectionProvider = actionDescriptorCollectionProvider;
|
||||
_applicationModelProviders = applicationModelProviders
|
||||
.OrderBy(p => p.Order)
|
||||
.ToArray();
|
||||
|
||||
_viewCompilerProvider = viewCompilerProvider;
|
||||
_endpointFactory = endpointFactory;
|
||||
_conventions = pageOptions.Value.Conventions;
|
||||
|
|
@ -41,16 +49,42 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
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)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||
}
|
||||
|
||||
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
|
||||
var viewDescriptor = compileTask.GetAwaiter().GetResult();
|
||||
var cache = CurrentCache;
|
||||
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());
|
||||
for (var i = 0; i < _applicationModelProviders.Length; i++)
|
||||
{
|
||||
|
|
@ -65,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
ApplyConventions(_conventions, context.PageApplicationModel);
|
||||
|
||||
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
|
||||
|
|
@ -128,5 +162,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
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.
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
||||
/// </summary>
|
||||
[Obsolete("This type is obsolete. Use " + nameof(PageLoader) + " instead.")]
|
||||
public interface IPageLoader
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
|
@ -19,9 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
internal class PageActionInvokerProvider : IActionInvokerProvider
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
private readonly IPageLoader _loader;
|
||||
private readonly PageLoader _loader;
|
||||
private readonly IPageFactoryProvider _pageFactoryProvider;
|
||||
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
||||
private readonly IModelBinderFactory _modelBinderFactory;
|
||||
|
|
@ -43,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
private volatile InnerCache _currentCache;
|
||||
|
||||
public PageActionInvokerProvider(
|
||||
IPageLoader loader,
|
||||
PageLoader loader,
|
||||
IPageFactoryProvider pageFactoryProvider,
|
||||
IPageModelFactoryProvider modelFactoryProvider,
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||
|
|
@ -81,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
public PageActionInvokerProvider(
|
||||
IPageLoader loader,
|
||||
PageLoader loader,
|
||||
IPageFactoryProvider pageFactoryProvider,
|
||||
IPageModelFactoryProvider modelFactoryProvider,
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||
|
|
@ -139,7 +138,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
IFilterMetadata[] filters;
|
||||
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);
|
||||
filters = filterFactoryResult.Filters;
|
||||
|
|
@ -285,7 +297,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
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>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.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;
|
||||
private readonly PageLoader _loader;
|
||||
|
||||
public PageLoaderMatcherPolicy(IPageLoader loader)
|
||||
public PageLoaderMatcherPolicy(PageLoader loader)
|
||||
{
|
||||
if (loader == null)
|
||||
{
|
||||
|
|
@ -69,21 +68,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
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 endpoint = candidate.Endpoint;
|
||||
|
||||
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
|
||||
if (page != null)
|
||||
{
|
||||
var compiled = _loader.Load(page);
|
||||
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidate.Values);
|
||||
// We found an endpoint instance that has a PageActionDescriptor, but not a
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
|
|
@ -18,8 +20,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
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]
|
||||
public void Load_InvokesApplicationModelProviders()
|
||||
public async Task LoadAsync_InvokesApplicationModelProviders()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor();
|
||||
|
|
@ -77,6 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
};
|
||||
|
||||
var loader = new DefaultPageLoader(
|
||||
ActionDescriptorCollectionProvider,
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
|
|
@ -84,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
mvcOptions);
|
||||
|
||||
// Act
|
||||
var result = loader.Load(new PageActionDescriptor());
|
||||
var result = await loader.LoadAsync(new PageActionDescriptor());
|
||||
|
||||
// Assert
|
||||
provider1.Verify();
|
||||
|
|
@ -92,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CreatesEndpoint_WithRoute()
|
||||
public async Task LoadAsync_CreatesEndpoint_WithRoute()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor()
|
||||
|
|
@ -132,6 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
};
|
||||
|
||||
var loader = new DefaultPageLoader(
|
||||
ActionDescriptorCollectionProvider,
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
|
|
@ -139,14 +151,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
mvcOptions);
|
||||
|
||||
// Act
|
||||
var result = loader.Load(descriptor);
|
||||
var result = await loader.LoadAsync(descriptor);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_InvokesApplicationModelProviders_WithTheRightOrder()
|
||||
public async Task LoadAsync_InvokesApplicationModelProviders_WithTheRightOrder()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor();
|
||||
|
|
@ -196,6 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
};
|
||||
|
||||
var loader = new DefaultPageLoader(
|
||||
ActionDescriptorCollectionProvider,
|
||||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
|
|
@ -203,13 +216,134 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
mvcOptions);
|
||||
|
||||
// Act
|
||||
var result = loader.Load(new PageActionDescriptor());
|
||||
var result = await loader.LoadAsync(new PageActionDescriptor());
|
||||
|
||||
// Assert
|
||||
provider1.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]
|
||||
public void ApplyConventions_InvokesApplicationModelConventions()
|
||||
{
|
||||
|
|
@ -553,11 +687,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
private static IViewCompilerProvider GetCompilerProvider()
|
||||
{
|
||||
var descriptor = new CompiledViewDescriptor
|
||||
{
|
||||
Item = TestRazorCompiledItem.CreateForView(typeof(object), "/Views/Index.cshtml"),
|
||||
};
|
||||
|
||||
var compiledItem = TestRazorCompiledItem.CreateForView(typeof(object), "/Views/Index.cshtml");
|
||||
var descriptor = new CompiledViewDescriptor(compiledItem);
|
||||
var compiler = new Mock<IViewCompiler>();
|
||||
compiler.Setup(c => c.CompileAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(descriptor);
|
||||
|
|
|
|||
|
|
@ -30,19 +30,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
public void OnProvidersExecuting_WithEmptyModel_PopulatesCacheEntry()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
});
|
||||
|
||||
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
||||
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
||||
var loader = Mock.Of<PageLoader>();
|
||||
|
||||
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
||||
pageFactoryProvider
|
||||
|
|
@ -53,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.Returns(releaser);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
loader,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
pageFactoryProvider.Object);
|
||||
|
||||
|
|
@ -83,25 +80,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
public void OnProvidersExecuting_WithModel_PopulatesCacheEntry()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0]
|
||||
};
|
||||
var descriptor = CreateCompiledPageActionDescriptor(
|
||||
new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0]
|
||||
},
|
||||
pageType: typeof(PageWithModel),
|
||||
modelType: typeof(DerivedTestPageModel));
|
||||
|
||||
Func<PageContext, ViewContext, object> factory = (a, b) => null;
|
||||
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
|
||||
Func<PageContext, object> modelFactory = _ => null;
|
||||
Action<PageContext, object> modelDisposer = (_, __) => { };
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(
|
||||
descriptor,
|
||||
pageType: typeof(PageWithModel),
|
||||
modelType: typeof(DerivedTestPageModel)));
|
||||
|
||||
var loader = Mock.Of<PageLoader>();
|
||||
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
|
||||
pageFactoryProvider
|
||||
.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
|
||||
|
|
@ -119,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.Returns(modelDisposer);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
loader,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
pageFactoryProvider.Object,
|
||||
modelFactoryProvider.Object);
|
||||
|
|
@ -162,18 +155,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
public void OnProvidersExecuting_CachesViewStartFactories()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "/Home/Path1/File.cshtml",
|
||||
ViewEnginePath = "/Home/Path1/File.cshtml",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
|
||||
}, pageType: typeof(PageWithModel));
|
||||
|
||||
var loader = Mock.Of<PageLoader>();
|
||||
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
||||
|
||||
Func<IRazorPage> factory1 = () => null;
|
||||
|
|
@ -191,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
fileProvider.AddFile("/_ViewStart.cshtml", "content2");
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
loader,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
razorPageFactoryProvider: razorPageFactoryProvider.Object);
|
||||
|
||||
|
|
@ -216,19 +205,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
public void OnProvidersExecuting_CachesEntries()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
var descriptor = CreateCompiledPageActionDescriptor(new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
});
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
||||
var loader = Mock.Of<PageLoader>();
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
loader,
|
||||
CreateActionDescriptorCollection(descriptor));
|
||||
|
||||
var context = new ActionInvokerProviderContext(new ActionContext
|
||||
|
|
@ -263,7 +249,39 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
[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
|
||||
var descriptor = new PageActionDescriptor
|
||||
|
|
@ -272,6 +290,41 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
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 descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
|
||||
|
||||
|
|
@ -281,13 +334,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.Returns(descriptorCollection1)
|
||||
.Returns(descriptorCollection2);
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(descriptor));
|
||||
var loader = Mock.Of<PageLoader>();
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
loader,
|
||||
actionDescriptorProvider.Object);
|
||||
|
||||
var context = new ActionInvokerProviderContext(new ActionContext()
|
||||
|
|
@ -332,10 +382,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
PageTypeInfo = typeof(object).GetTypeInfo(),
|
||||
};
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
var loader = new Mock<PageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(compiledPageDescriptor);
|
||||
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
||||
.ReturnsAsync(compiledPageDescriptor);
|
||||
|
||||
var mock = new Mock<IRazorPageFactoryProvider>(MockBehavior.Strict);
|
||||
mock
|
||||
|
|
@ -382,10 +432,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
ViewEnginePath = "/Views/Deeper/Index.cshtml"
|
||||
};
|
||||
|
||||
var loader = new Mock<IPageLoader>();
|
||||
var loader = new Mock<PageLoader>();
|
||||
loader
|
||||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
|
||||
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
||||
.ReturnsAsync(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
|
||||
|
||||
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
||||
pageFactory
|
||||
|
|
@ -447,11 +497,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
private static PageActionInvokerProvider CreateInvokerProvider(
|
||||
IPageLoader loader,
|
||||
PageLoader loader,
|
||||
IActionDescriptorCollectionProvider actionDescriptorProvider,
|
||||
IPageFactoryProvider pageProvider = null,
|
||||
IPageModelFactoryProvider modelProvider = null,
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider = null)
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider = null,
|
||||
MvcOptions mvcOptions = null)
|
||||
{
|
||||
var tempDataFactory = new Mock<ITempDataDictionaryFactory>();
|
||||
tempDataFactory
|
||||
|
|
@ -460,7 +511,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
var mvcOptions = new MvcOptions();
|
||||
mvcOptions = mvcOptions ?? new MvcOptions();
|
||||
|
||||
var parameterBinder = new ParameterBinder(
|
||||
modelMetadataProvider,
|
||||
|
|
@ -480,7 +531,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
modelMetadataProvider,
|
||||
modelBinderFactory,
|
||||
tempDataFactory.Object,
|
||||
Options.Create(new MvcOptions()),
|
||||
Options.Create(mvcOptions),
|
||||
Options.Create(new HtmlHelperOptions()),
|
||||
Mock.Of<IPageHandlerMethodSelector>(),
|
||||
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