Skip to content

Commit 79e6d0e

Browse files
committed
Make page load async
1 parent 93b195e commit 79e6d0e

File tree

10 files changed

+279
-54
lines changed

10 files changed

+279
-54
lines changed

src/Mvc/Mvc.RazorPages/src/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ internal static void AddServices(IServiceCollection services)
116116
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
117117
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactoryProvider>();
118118

119-
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
119+
#pragma warning disable CS0618 // Type or member is obsolete
120+
services.TryAddSingleton<IPageLoader>(s => s.GetRequiredService<PageLoaderBase>());
121+
#pragma warning restore CS0618 // Type or member is obsolete
122+
services.TryAddSingleton<PageLoaderBase, DefaultPageLoader>();
120123
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
121124

122125
// Action executors

src/Mvc/Mvc.RazorPages/src/Infrastructure/DefaultPageLoader.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,26 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Reflection;
8+
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Builder;
910
using Microsoft.AspNetCore.Http;
1011
using Microsoft.AspNetCore.Mvc.ApplicationModels;
1112
using Microsoft.AspNetCore.Mvc.Filters;
1213
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
1314
using Microsoft.AspNetCore.Mvc.Routing;
15+
using Microsoft.Extensions.Caching.Memory;
1416
using Microsoft.Extensions.Options;
1517

1618
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
1719
{
18-
internal class DefaultPageLoader : IPageLoader
20+
internal class DefaultPageLoader : PageLoaderBase
1921
{
2022
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
2123
private readonly IViewCompilerProvider _viewCompilerProvider;
2224
private readonly ActionEndpointFactory _endpointFactory;
2325
private readonly PageConventionCollection _conventions;
2426
private readonly FilterCollection _globalFilters;
27+
private readonly MemoryCache _memoryCache;
2528

2629
public DefaultPageLoader(
2730
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
@@ -33,6 +36,9 @@ public DefaultPageLoader(
3336
_applicationModelProviders = applicationModelProviders
3437
.OrderBy(p => p.Order)
3538
.ToArray();
39+
40+
_memoryCache = new MemoryCache(new MemoryCacheOptions());
41+
3642
_viewCompilerProvider = viewCompilerProvider;
3743
_endpointFactory = endpointFactory;
3844
_conventions = pageOptions.Value.Conventions;
@@ -41,16 +47,31 @@ public DefaultPageLoader(
4147

4248
private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler();
4349

44-
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
50+
public async override ValueTask<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor)
4551
{
4652
if (actionDescriptor == null)
4753
{
4854
throw new ArgumentNullException(nameof(actionDescriptor));
4955
}
5056

51-
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
52-
var viewDescriptor = compileTask.GetAwaiter().GetResult();
57+
if (_memoryCache.TryGetValue(actionDescriptor, out CompiledPageActionDescriptor compiledDescriptor))
58+
{
59+
return compiledDescriptor;
60+
}
5361

62+
var viewDescriptor = await Compiler.CompileAsync(actionDescriptor.RelativePath);
63+
var compiledPageActionDescriptor = GetCompiledPageActionDescriptor(actionDescriptor, viewDescriptor);
64+
var entryOptions = new MemoryCacheEntryOptions();
65+
for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++)
66+
{
67+
entryOptions.ExpirationTokens.Add(viewDescriptor.ExpirationTokens[i]);
68+
}
69+
70+
return _memoryCache.Set(actionDescriptor, compiledPageActionDescriptor, entryOptions);
71+
}
72+
73+
private CompiledPageActionDescriptor GetCompiledPageActionDescriptor(PageActionDescriptor actionDescriptor, CompiledViewDescriptor viewDescriptor)
74+
{
5475
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
5576
for (var i = 0; i < _applicationModelProviders.Length; i++)
5677
{
@@ -65,7 +86,7 @@ public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
6586
ApplyConventions(_conventions, context.PageApplicationModel);
6687

6788
var compiled = CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters);
68-
89+
6990
// We need to create an endpoint for routing to use and attach it to the CompiledPageActionDescriptor...
7091
// routing for pages is two-phase. First we perform routing using the route info - we can do this without
7192
// compiling/loading the page. Then once we have a match we load the page and we can create an endpoint

src/Mvc/Mvc.RazorPages/src/Infrastructure/IPageLoader.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
46
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
57
{
68
/// <summary>
79
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
810
/// </summary>
11+
[Obsolete("This type is obsolete. Use PageLoaderBase instead.")]
912
public interface IPageLoader
1013
{
1114
/// <summary>

src/Mvc/Mvc.RazorPages/src/Infrastructure/PageActionInvokerProvider.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.Linq;
9+
using Microsoft.AspNetCore.Http.Features;
910
using Microsoft.AspNetCore.Mvc.Abstractions;
1011
using Microsoft.AspNetCore.Mvc.Filters;
1112
using Microsoft.AspNetCore.Mvc.Infrastructure;
@@ -19,9 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
1920
{
2021
internal class PageActionInvokerProvider : IActionInvokerProvider
2122
{
22-
private const string ViewStartFileName = "_ViewStart.cshtml";
23-
24-
private readonly IPageLoader _loader;
23+
private readonly PageLoaderBase _loader;
2524
private readonly IPageFactoryProvider _pageFactoryProvider;
2625
private readonly IPageModelFactoryProvider _modelFactoryProvider;
2726
private readonly IModelBinderFactory _modelBinderFactory;
@@ -43,7 +42,7 @@ internal class PageActionInvokerProvider : IActionInvokerProvider
4342
private volatile InnerCache _currentCache;
4443

4544
public PageActionInvokerProvider(
46-
IPageLoader loader,
45+
PageLoaderBase loader,
4746
IPageFactoryProvider pageFactoryProvider,
4847
IPageModelFactoryProvider modelFactoryProvider,
4948
IRazorPageFactoryProvider razorPageFactoryProvider,
@@ -81,7 +80,7 @@ public PageActionInvokerProvider(
8180
}
8281

8382
public PageActionInvokerProvider(
84-
IPageLoader loader,
83+
PageLoaderBase loader,
8584
IPageFactoryProvider pageFactoryProvider,
8685
IPageModelFactoryProvider modelFactoryProvider,
8786
IRazorPageFactoryProvider razorPageFactoryProvider,
@@ -139,7 +138,19 @@ public void OnProvidersExecuting(ActionInvokerProviderContext context)
139138
IFilterMetadata[] filters;
140139
if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
141140
{
142-
actionContext.ActionDescriptor = _loader.Load(actionDescriptor);
141+
CompiledPageActionDescriptor compiledPageActionDescriptor;
142+
var endpointFeature = actionContext.HttpContext.Features.Get<IEndpointFeature>();
143+
if (endpointFeature != null)
144+
{
145+
// With endpoint routing, PageLoaderMatcherPolicy should have already produced a CompiledPageActionDescriptor.
146+
compiledPageActionDescriptor = (CompiledPageActionDescriptor)actionDescriptor;
147+
}
148+
else
149+
{
150+
compiledPageActionDescriptor = _loader.LoadAsync(actionDescriptor).GetAwaiter().GetResult();
151+
}
152+
153+
actionContext.ActionDescriptor = compiledPageActionDescriptor;
143154

144155
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
145156
filters = filterFactoryResult.Filters;
@@ -285,7 +296,7 @@ private static PageHandlerExecutorDelegate[] GetHandlerExecutors(CompiledPageAct
285296

286297
private PageHandlerBinderDelegate[] GetHandlerBinders(CompiledPageActionDescriptor actionDescriptor)
287298
{
288-
if (actionDescriptor.HandlerMethods == null ||actionDescriptor.HandlerMethods.Count == 0)
299+
if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0)
289300
{
290301
return Array.Empty<PageHandlerBinderDelegate>();
291302
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
6+
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
7+
{
8+
/// <summary>
9+
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
10+
/// </summary>
11+
#pragma warning disable CS0618 // Type or member is obsolete
12+
public abstract class PageLoaderBase : IPageLoader
13+
#pragma warning restore CS0618 // Type or member is obsolete
14+
{
15+
public abstract ValueTask<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor);
16+
17+
CompiledPageActionDescriptor IPageLoader.Load(PageActionDescriptor actionDescriptor)
18+
=> LoadAsync(actionDescriptor).GetAwaiter().GetResult();
19+
}
20+
}

src/Mvc/Mvc.RazorPages/src/Infrastructure/PageLoaderMatcherPolicy.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.Routing;
99
using Microsoft.AspNetCore.Routing.Matching;
10-
using Microsoft.Extensions.DependencyInjection;
1110

1211
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
1312
{
1413
internal class PageLoaderMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
1514
{
16-
private readonly IPageLoader _loader;
15+
private readonly PageLoaderBase _loader;
1716

18-
public PageLoaderMatcherPolicy(IPageLoader loader)
17+
public PageLoaderMatcherPolicy(PageLoaderBase loader)
1918
{
2019
if (loader == null)
2120
{
@@ -73,17 +72,44 @@ public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context,
7372
for (var i = 0; i < candidates.Count; i++)
7473
{
7574
ref var candidate = ref candidates[i];
76-
var endpoint = (RouteEndpoint)candidate.Endpoint;
75+
var endpoint = candidate.Endpoint;
7776

7877
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
7978
if (page != null)
8079
{
81-
var compiled = _loader.Load(page);
82-
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidate.Values);
80+
// We found an endpoint instance that has a PageActionDescriptor, but not a
81+
// CompiledPageActionDescriptor. Update the CandidateSet.
82+
var compiled = _loader.LoadAsync(page);
83+
if (compiled.IsCompletedSuccessfully)
84+
{
85+
candidates.ReplaceEndpoint(i, compiled.Result.Endpoint, candidate.Values);
86+
}
87+
else
88+
{
89+
// In the most common case, LoadAsync will return a synchronous result.
90+
// Avoid going async since this is a fairly hot path.
91+
return ApplyAsyncAwaited(candidates);
92+
}
8393
}
8494
}
8595

8696
return Task.CompletedTask;
8797
}
98+
99+
private async Task ApplyAsyncAwaited(CandidateSet candidates)
100+
{
101+
for (var i = 0; i < candidates.Count; i++)
102+
{
103+
var candidate = candidates[i];
104+
var endpoint = candidate.Endpoint;
105+
106+
var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>();
107+
if (page != null)
108+
{
109+
var compiled = await _loader.LoadAsync(page);
110+
candidates.ReplaceEndpoint(i, compiled.Endpoint, candidates[i].Values);
111+
}
112+
}
113+
}
88114
}
89115
}

src/Mvc/Mvc.RazorPages/src/PageActionDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,6 @@ public override string DisplayName
8181
}
8282
}
8383

84-
private string DebuggerDisplayString => $"{{ViewEnginePath = {nameof(ViewEnginePath)}, RelativePath = {nameof(RelativePath)}}}";
84+
private string DebuggerDisplayString => $"{nameof(ViewEnginePath)} = {ViewEnginePath}, {nameof(RelativePath)} = {RelativePath}";
8585
}
8686
}

0 commit comments

Comments
 (0)