Blazor 应用的项目结构

提示

此内容摘自电子书《面向 Azure ASP.NET Web Forms 开发人员的 Blazor》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail.

尽管 ASP.NET Web Forms 和 Blazor 的项目结构存在重大差异,但它们具有许多类似的概念。 在这里,我们将了解 Blazor 项目的结构,并将它与 ASP.NET Web Forms 项目进行比较。

若要创建第一个 Blazor 应用,请按照 Blazor 入门步骤中的说明进行操作。 可以按照说明创建 Blazor 服务器应用或是在 ASP.NET Core 中托管的 BlazorWebAssembly 应用。 除了特定于托管模型的逻辑外,这两个项目中的大多数代码是相同的。

项目文件

Blazor 服务器应用是 .NET 项目。 Blazor 服务器应用的项目文件可如同下面一样简单:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

</Project>

BlazorWebAssembly 应用的项目文件看起来稍微复杂一些(确切的版本号可能有所不同):

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
  </ItemGroup>

</Project>

BlazorWebAssembly 项目面向 Microsoft.NET.Sdk.BlazorWebAssembly 而不是 Microsoft.NET.Sdk.Web sdk,因为它们在基于 WebAssembly 的 .NET 运行时上的浏览器中运行。 不能如同在服务器或开发人员计算机上那样将 .NET 安装到 Web 浏览器中。 因此,项目会使用单个包引用来引用 Blazor 框架。

相比之下,默认 ASP.NET Web Forms 项目在其 .csproj 文件中包含将近 300 行 XML,大多数这种文件会显式列出项目中的各种代码和内容文件。 随着 .NET 5 的发布,Blazor 和 BlazorWebAssembly 应用可以轻松共享一个统一的运行时。

虽然单个程序集引用受支持,但是它们在 .NET 项目中不太常见。 大多数项目依赖项都作为 NuGet 包引用进行处理。 只需在 .NET 项目中引用顶级包依赖项。 可传递的依赖项会自动包含在内。 会使用 <PackageReference> 元素将包引用添加到项目文件,而不是使用 ASP.NET Web Forms 项目中常见的 packages.config 文件来引用包。

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

入口点

Blazor 服务器应用的入口点在 Program.cs 文件中进行定义,与控制台应用一样。 当应用执行时,它会使用特定于 Web 应用的默认值创建并运行 Web 主机实例。 Web 主机会管理 Blazor 服务器应用的生命周期,并设置主机级别服务。 此类服务的示例包括配置、日志记录、依赖项注入和 HTTP 服务器。 此代码主要作为样板,通常保持不变。

using BlazorApp3.Areas.Identity;
using BlazorApp3.Data;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
builder.Services.AddSingleton<WeatherForecastService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

BlazorWebAssembly 应用也在 Program.cs 中定义入口点。 代码看起来略有不同。 该代码的相似之处在于,它会设置应用主机以便向应用提供相同的主机级别服务。 不过,WebAssembly 应用主机不会设置 HTTP 服务器,因为它直接在浏览器中执行。

Blazor 应用不使用 Global.asax 文件定义应用的启动逻辑。 相反,此逻辑包含在 Program.cs 或从 Program.cs 引用的相关 Startup 类中。 不管怎样,此代码都用于配置应用和任何特定于应用的服务。

在 Blazor 服务器应用中,显示的 Program.cs 文件用于为 Blazor 在客户端浏览器与服务器之间使用的实时连接设置终结点。

在 BlazorWebAssembly 应用中,Program.cs 文件定义应用的根组件及其呈现位置:

using BlazorApp1;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

静态文件

与 ASP.NET Web Forms 项目不同,并非 Blazor 项目中的所有文件都可以作为静态文件进行请求。 只有 wwwroot 文件夹中的文件可进行 Web 寻址。 此文件夹称为应用的“Web 根目录”。 应用的 Web 根目录之外的任何内容都不可进行 Web 寻址。 此设置可提供额外的安全级别,防止在 Web 上意外公开项目文件。

配置

ASP.NET Web Forms 应用中的配置通常使用一个或多个 web.config 文件进行处理。 Blazor 应用通常没有 web.config 文件。 如果它们有这类文件,则该文件仅用于在 IIS 上进行托管时配置特定于 IIS 的设置。 相反,Blazor Server 应用使用 ASP.NET Core 配置抽象。 (BlazorWebAssembly 应用当前不支持相同的配置抽象,但这可能是将来会添加的功能。)例如,默认 Blazor Server 应用在 appsettings.json 中存储某些设置。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

你将在配置部分中详细了解 ASP.NET Core 项目中的配置。

Razor 组件

Blazor 项目中的大多数文件是 .razor 文件。 Razor 是一种基于 HTML 和 C# 的模板化语言,用于动态生成 Web UI。 .razor 文件定义组成应用 UI 的组件。 大多数情况下,组件对于 Blazor Server 和 BlazorWebAssembly 应用是相同的。 Blazor 中的组件类似于 ASP.NET Web Forms 中的用户控件。

生成项目时,每个 Razor 组件文件都会编译为 .NET 类。 生成的类会捕获组件的状态、呈现逻辑、生命周期方法、事件处理程序和其他逻辑。 可在使用 Blazor 生成可重用 UI 组件部分中详细了解如何创作组件。

_Imports.razor 文件不是 Razor 组件文件。 相反,它们定义一组 Razor 指令以导入到相同文件夹及其子文件夹中的其他 .razor 文件中。 例如,_Imports.razor 文件是为常用命名空间添加 using 指令的常规方法:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorApp1
@using BlazorApp1.Shared

Blazor 应用中的页面位于何处? Blazor 不会为可寻址页面定义单独的文件扩展名,例如 ASP.NET Web Forms 应用中的 .aspx 文件。 而是通过将路由分配给组件来定义页面。 通常使用 @page Razor 指令分配路由。 例如,在 Pages/Counter.razor 文件中创作的 Counter 组件定义以下路由:

@page "/counter"

Blazor 中的路由在客户端(而不是服务器上)进行处理。 当用户在浏览器中导航时,Blazor 会截获导航,然后呈现具有匹配路由的组件。

组件路由当前不像在 .aspx 页面或 ASP.NET Core Razor Pages 中那样通过组件的文件位置进行推断。 将来可能会添加此功能。 必须在组件上显式指定每个路由。 将可路由组件存储在 Pages 文件夹中没有特殊含义,纯粹是一种约定。

你将在页面、路由和布局部分中详细了解 Blazor 中的路由。

Layout

在 ASP.NET Web Forms 应用中,使用母版页 (Site.Master) 处理常见页面布局。 在 Blazor 应用中,使用布局组件 (Shared/MainLayout.razor) 处理页面布局。 页面、路由和布局部分中更详细地讨论了布局组件。

启动 Blazor

若要启动 Blazor,应用必须:

  • 指定根组件 (App.Razor) 应在页面上的何处进行呈现。
  • 添加相应的 Blazor 框架脚本。

在 Blazor 服务器应用中,根组件的主机页面在 _Host.cshtml 文件中进行定义。 此文件定义一个 Razor 页面,而不是一个组件。 Razor Pages 使用 Razor 语法定义服务器可寻址页面(与 .aspx 页面非常类似)。

@page "/"
@namespace BlazorApp3.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = "_Layout";
}

<component type="typeof(App)" render-mode="ServerPrerendered" />

render-mode 特性用于定义根级别组件的呈现位置。 RenderMode 选项指示组件的呈现方式。 下表概述了支持的 RenderMode 选项。

选项 说明
RenderMode.Server 建立与浏览器的连接后以交互方式呈现
RenderMode.ServerPrerendered 首先预呈现,然后以交互方式呈现
RenderMode.Static 呈现为静态内容

_Layout.cshtml 文件包含应用的默认 HTML 及其组件。

@using Microsoft.AspNetCore.Components.Web
@namespace BlazorApp3.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="BlazorApp3.styles.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    @RenderBody()

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

对 _framework/blazor.server.js 的脚本引用会与服务器建立实时连接,然后处理所有用户交互和 UI 更新。

在 BlazorWebAssembly 应用中,主机页面是 wwwroot/index.html 下的简单静态 HTML 文件。 ID 名为 app<div> 元素用于指示根组件的呈现位置。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorApp1</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="BlazorApp1.styles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

要呈现的根组件在应用的 Program.cs 文件中指定,可通过依赖项注入灵活地注册服务。 有关详细信息,请参阅 ASP.NET Core Blazor 依赖项注入

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

生成输出

生成 Blazor 项目时,所有 Razor 组件和代码文件都会编译为单个程序集。 与 ASP.NET Web Forms 项目不同,Blazor 不支持 UI 逻辑的运行时编译。

使用热重载运行应用

若要运行 Blazor 服务器应用,请在 Visual Studio 中按 F5 以在附加调试器的情况下运行,或按 Ctrl + F5 以在不附加调试器的情况下运行。

若要运行 BlazorWebAssembly 应用,请选择以下方法之一:

  • 使用开发服务器直接运行客户端项目。
  • 在使用 ASP.NET Core 托管应用时运行服务器项目。

BlazorWebAssembly 应用可在浏览器和 Visual Studio 中进行调试。 有关详细信息,请参阅调试 ASP.NET Core BlazorWebAssembly

Blazor Server 和 BlazorWebAssembly 应用都支持 Visual Studio 中的热重载。 热重载是浏览器中的一项功能,可自动更新对 Blazor 应用动态所做的更改。 可以切换是否从工具栏的“热重载”图标启用该功能:

Visual Studio 2022: Hot Reload menu item and icon.

选择图标旁边的插入点会显示其他选项。 可以打开或关闭热重载、重启应用程序,以及在保存文件时执行/不执行热重载。

Visual Studio 2022: Hot Reload menu item with expanded options.

还可以访问其他配置选项。 使用配置对话框,可以指定在调试(以及编辑并继续)时、在未调试的情况下启动时或在保存文件时是否应启用热重载。

Visual Studio 2022: Hot Reload configuration options from the

“开发人员内部循环”已大大简化了热重载。 如果不使用热重载,Blazor 开发人员通常需要在每次更改后重启并重新运行应用,并根据需要导航到应用的适当部分。 使用热重载,在大多数情况下,无需重启即可对正在运行的应用进行更改。 热重载甚至会保留页面的状态,因此无需重新输入窗体值,也无需将应用返回到所需位置。