Blazor 应用程序如何能够在不调用 Startup.cs 中的 MapRazorPages() 的情况下公开 Razor Pages 端点?

How are Blazor apps able to expose Razor Pages endpoints without a call to MapRazorPages() in Startup.cs?

在默认的 Razor Pages 项目模板中,Startup.cs 中启用 Razor Pages 的部分代码是在 Configure() 的端点配置部分调用 MapRazorPages():

app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });

此调用的必要性已在 Rick Anderson 和 Ryan Nowak 的优秀 Introduction to Razor Pages in ASP.NET Core 文章中得到证实。

虽然 Blazor 是一种不同的 UI 技术,但 Blazor 项目也能够公开 Razor Pages 端点。例如,包含 ASP.NET 身份验证的 Blazor 项目将登录和注销页面公开为 Razor 页面。

但是,公开 Razor Pages 的 Blazor 项目中的端点初始化似乎不涉及对 MapRazorPages() 的调用。如果您使用具有个人用户帐户身份验证的默认 Blazor 模板创建一个新项目,然后在 ASP.NET Identity 使用的所有 Razor 页面中建立脚手架,端点初始化最终看起来像这样:

app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });

生成的应用程序能够正确地将请求路由到 Login.cshtml 和 LogOut.cshtml 等 Razor Pages 端点。如果不调用 MapRazorPages() 这怎么可能?

首先看一下下面的代码片段(注意这两行中突出显示的行)

引用MapRazorPages

/// <summary>
/// Adds endpoints for Razor Pages to the <see cref="IEndpointRouteBuilder"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <returns>An <see cref="PageActionEndpointConventionBuilder"/> for endpoints associated with Razor Pages.</returns>
public static PageActionEndpointConventionBuilder MapRazorPages(this IEndpointRouteBuilder endpoints)
{
    if (endpoints == null)
    {
        throw new ArgumentNullException(nameof(endpoints));
    }

    EnsureRazorPagesServices(endpoints); //<-- NOTE THIS

    return GetOrCreateDataSource(endpoints).DefaultBuilder;
}

引用MapFallbackToPage

public static IEndpointConventionBuilder MapFallbackToPage(this IEndpointRouteBuilder endpoints, string page)
{
    if (endpoints == null)
    {
        throw new ArgumentNullException(nameof(endpoints));
    }

    if (page == null)
    {
        throw new ArgumentNullException(nameof(page));
    }

    PageConventionCollection.EnsureValidPageName(page, nameof(page));

    EnsureRazorPagesServices(endpoints); //<-- NOTE THIS

    // Called for side-effect to make sure that the data source is registered.
    GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;

    // Maps a fallback endpoint with an empty delegate. This is OK because
    // we don't expect the delegate to run.
    var builder = endpoints.MapFallback(context => Task.CompletedTask);
    builder.Add(b =>
    {
        // MVC registers a policy that looks for this metadata.
        b.Metadata.Add(CreateDynamicPageMetadata(page, area: null));
    });
    return builder;
}

它们都调用核心函数来配置功能以处理剃刀页面请求。