| File: PageLoaderMatcherPolicy.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Razor.RuntimeCompilation\src\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj (Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; internal sealed class PageLoaderMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy { private PageLoader? _loader; /// <remarks> /// The <see cref="PageLoader"/> service is configured by <c>app.AddRazorPages()</c>. /// If the app is configured as <c>app.AddControllersWithViews().AddRazorRuntimeCompilation()</c>, the <see cref="PageLoader"/> /// service will not be registered. Since Razor Pages is not a pre-req for runtime compilation, we'll defer reading the service /// until we need to load a page in the body of <see cref="ApplyAsync(HttpContext, CandidateSet)"/>. /// </remarks> public PageLoaderMatcherPolicy() : this(loader: null) { } public PageLoaderMatcherPolicy(PageLoader? loader) { _loader = loader; } public override int Order => int.MinValue + 100; public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints) { ArgumentNullException.ThrowIfNull(endpoints); // We don't mark Pages as dynamic endpoints because that causes all matcher policies // to run in *slow mode*. Instead we produce the same metadata for things that would affect matcher // policies on both endpoints (uncompiled and compiled). // // This means that something like putting [Consumes] on a page wouldn't work. We've never said that it would. for (var i = 0; i < endpoints.Count; i++) { var page = endpoints[i].Metadata.GetMetadata<PageActionDescriptor>(); if (page is not null and not CompiledPageActionDescriptor) { // Found an uncompiled page return true; } } return false; } public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) { ArgumentNullException.ThrowIfNull(httpContext); ArgumentNullException.ThrowIfNull(candidates); for (var i = 0; i < candidates.Count; i++) { if (!candidates.IsValidCandidate(i)) { continue; } ref var candidate = ref candidates[i]; var endpoint = candidate.Endpoint; var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>(); if (page != null) { _loader ??= httpContext.RequestServices.GetRequiredService<PageLoader>(); // We found an endpoint instance that has a PageActionDescriptor, but not a // CompiledPageActionDescriptor. Update the CandidateSet. var compiled = _loader.LoadAsync(page, endpoint.Metadata); 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(_loader, candidates, compiled, i); } } } return Task.CompletedTask; } private static async Task ApplyAsyncAwaited(PageLoader pageLoader, 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++) { if (!candidates.IsValidCandidate(i)) { continue; } var candidate = candidates[i]; var endpoint = candidate.Endpoint; var page = endpoint.Metadata.GetMetadata<PageActionDescriptor>(); if (page != null) { compiled = await pageLoader.LoadAsync(page, endpoint.Metadata); candidates.ReplaceEndpoint(i, compiled.Endpoint, candidates[i].Values); } } } }