Blazor实战 第二章

2 您的第一个 Blazor 应用程序


本章包括

  • 为应用程序选择合适的项目模板
  • 构建和运行应用程序
  • 了解应用程序的关键部分
  • 编写第一个组件

读完第 1 章后,你应该对 Blazor 及其工作原理有所了解。您现在也应该理解了托管模型的概念,并有一些令人信服的理由来解释为什么要在下一个项目中选择 Blazor。但到目前为止,我们只是在理论上进行了讨论。我们还需要动手用 Blazor 做一些事情,而这将在本章中实现。

我们将创建贯穿本书其余部分的应用程序–“Blazing Trails”!该应用程序将允许步行者发现新的探索路线,并添加和更新自己的路线。在创建应用程序的过程中,您将了解 Blazor 的主要功能,如路由、表单和验证以及身份验证。

在本章中,我们首先要了解 Microsoft 提供的可用模板,以便创建新的应用程序。模板是快速入门的好方法,它们提供了我们创建一个工作应用程序所需的所有基本构件。了解选项后,我们将选择一个模板作为 Blazing Trails 应用程序的基础。我们将构建并运行模板,以便了解它的运行情况;然后我们将删除所有不必要的部分,只留下关键组件。我们将编写第一个组件,到本章结束时,我们将拥有一个如图 2.1 所示的应用程序。

图 2.1 我们将在本章中创建的 "Blazing Trails "应用程序预览
图 2.1 我们将在本章中创建的 "Blazing Trails "应用程序预览

2.1 设置应用程序

在其他框架中,建立一个新的应用程序需要从头开始手动创建一切。虽然这样做的好处是只创建必要的内容,但这可能是一个乏味和重复的过程。一般来说,.NET 应用程序不是这样创建的。即使不是全部,也有许多是从模板开始生成的。使用模板有很多好处:

  • 开发人员可以在几秒钟内开发出一个可运行的应用程序。
  • 模板代码已得到处理,无需为每个新应用程序编写。
  • 该模板是使用该框架的工作范例。
  • 这个过程是可重复的。使用模板可以让你一次又一次地找到相同的起点。

Blazor 自带两个模板,可用于创建新应用程序。在选择模板时,我们基本上是在选择要使用的托管模式,即Blazor服务器或Blazor WebAssembly。事实上,两个可用的模板分别被命名为Blazor Server和Blazor WebAssembly,这让我们可以直接知道它们使用的托管模式。在本书中,我们将使用 Blazor WebAssembly 构建我们的 Blazing Trails 应用程序,因此本章将重点介绍这种模板类型。

如果我想使用 Blazor 服务器怎么办?

如果你对 Blazor 服务器托管模式感兴趣,不用担心,本书对你仍然有用。正如我在第1章中提到的,托管模式只决定代码运行的位置,而不是代码的行为。因此,你可以使用 Blazor 服务器模板来学习本书的所有内容。有时,Blazor WebAssembly 和 Blazor Server 应用程序之间的配置会略有不同,我会在适当的时候指出。

2.1.1 Blazor WebAssembly 模板配置

在创建应用程序之前,我想先谈谈 Blazor WebAssembly 模板的配置选项。该模板是两个可用模板中较为复杂的一个,因为您可以在托管或独立两种模式下对其进行配置(图 2.2)。

在这里插入图片描述

图 2.2 左侧显示在独立模式下配置模板时创建的项目。右侧显示的是在托管模式下配置模板时创建的项目。

在默认配置的独立模式下,解决方案中只有一个 Blazor WebAssembly 项目。如果你想构建一个不需要任何后台或服务器元素的应用程序,或者你已经有了一个现有的应用程序接口,那么这个模板就非常适合你。

托管模式稍微复杂一些。如果在创建模板时启用 ASP.NET Core Hosted 选项,解决方案中最终会出现三个项目:

  • Client Project—Blazor WebAssembly
  • Server Project—ASP.NET Core Web API
  • Shared Project—.NET class library

在此配置中,您将获得一个全栈 .NET 应用程序–功能齐全的后端(ASP.NET Core Web API)、前端和后端项目(.NET 类库)之间共享的代码存放处以及前端项目(Blazor WebAssembly)。

我想强调的是,使用这种配置确实需要在主机服务器上安装 .NET 运行时。回顾第 1 章,使用 Blazor WebAssembly 的一个优点是不需要在服务器上安装 .NET 运行时。使用托管配置时,这一优点就不适用了。这是因为在解决方案中有一个完整的 ASP.NET Core Web API 项目,它确实需要服务器上的 .NET 运行时才能运行。

既然我们已经了解了我们的选项,那么我们要为我们的 Blazing Trails 应用程序选择哪一个呢?首先,我们将选择较为简单的独立配置。到本书结束时,我们将拥有一个与托管模板基本相同的应用程序,但为了学到更多关于 Blazor 的知识,我们将在接下来的章节中逐步学习。

2.1.2 创建应用程序

使用模板创建新应用程序有两种方法:.NET CLI(命令行界面)或 IDE(集成开发环境),如 Visual Studio 或 JetBrains Rider。根据我的经验,大多数 .NET 开发人员倾向于使用集成开发环境来创建应用程序。在本书中,我将使用 Visual Studio for Windows。不过,你也可以使用 .NET CLI 和编辑器(如 VS Code)轻松跟读。要创建应用程序,请打开 Visual Studio 并执行以下步骤(图 2.3)。其他集成开发环境中的文字或屏幕顺序可能略有不同:

  1. 文件 > 新建项目。
  2. 从项目模板列表中选择 Blazor WebAssembly App。
  3. 下一个屏幕允许我们设置项目和解决方案的名称,以及文件在磁盘上的保存位置。按照图 2.3 输入详细信息,然后点击创建进入下一步。
  4. 第二个配置屏幕允许我们为解决方案指定一些其他设置。此时,我们可以使用图 2.4 所示的默认设置。
  5. 几秒钟后,Visual Studio 将使用我们指定的模板和设置生成应用程序。完成后,您将看到项目文件,如图 2.5 所示。

在这里插入图片描述
图 2.3 第一个应用程序配置对话框。通过该对话框可以指定项目和解决方案的名称,以及创建最终应用程序文件的位置。

在这里插入图片描述
图 2.4 第二个应用程序配置对话框。通过该对话框可以配置有关解决方案的其他信息。

在这里插入图片描述
图 2.5 Visual Studio 在解决方案资源管理器窗口内显示从模板生成的文件

使用 .NET CLI

如果您喜欢使用命令行,则可以使用 .NET CLI 创建相同的应用程序。为了完全复制我们在集成开发环境中创建的结构,您需要运行以下命令:

dotnet new blazorwasm -o BlazingTrails/BlazingTrails.Client
dotnet new sln -n BlazingTrails
dotnet sln add BlazingTrails\BlazingTrails.Client

这将创建一个新的应用程序,其配置和文件夹结构与我们使用 Visual Studio 设置的相同。我们将告诉 .NET CLI 创建一个新的 Blazor WebAssembly 应用程序,并将命令结果输出到使用 -o 开关指定的文件夹中。然后,我们将创建一个新的解决方案,并将 Blazor WebAssembly 项目添加到该解决方案中。

至此,你就创建了第一个 Blazor 应用程序。祝贺你 现在我们有了闪亮的新应用程序,让我们看看如何构建和运行它。

2.2 构建和运行应用程序

在运行 .NET 应用程序时,有三个步骤需要完成:

  1. 恢复任何软件包(也称为依赖包)。
  2. 编译或构建应用程序。
  3. 启动网络服务器,为应用程序提供服务。

在以前的 .NET 版本中,这些步骤都需要手动执行,因此您需要先还原任何软件包,然后构建代码,最后运行应用程序。不过,现在情况已不再如此。现在我们可以直接运行应用程序,如果需要执行前两个步骤,Visual Studio 或 .NET CLI 会帮您完成。

不过,了解如何在需要时自己手动执行这些步骤总是有好处的。使用 Visual Studio 时,您可以右键单击解决方案并从上下文菜单中选择还原 NuGet 包来还原软件包。如果使用的是 .NET CLI,则可以执行 dotnet restore 命令。

要从 Visual Studio 执行构建,请从顶部菜单中选择 “构建”>“构建解决方案”。你也可以使用键盘快捷键来执行相同的任务: Ctrl+Shift+B。在 .NET CLI 中,可以使用 dotnet build 命令。在使用 Visual Studio 或 CLI 时,如果需要,执行构建也会执行软件包还原。因此,手动还原软件包应该不成问题。

剩下的就是运行应用程序了。在 Visual Studio 中,有几种方法可以实现这一目的。首先,可以单击主工具栏上的 "播放 "按钮。还可以使用键盘快捷键 F5。最后,可以从顶部菜单中选择调试 > 开始调试。以上任何一种方法都可以运行应用程序,Visual Studio 会启动浏览器并自动加载应用程序。

根据你以前创建和运行的应用程序类型,你可能会看到一个额外的步骤,询问你是否要信任开发的 SSL 证书。如果回答 “是”,就会在机器上安装开发证书,这样就可以通过 https 而不是 http 运行应用程序。我建议信任并安装开发 SSL 证书–通过 https 运行网站是最佳做法,即使在开发过程中也是如此,因为这样可以模拟实时环境。

如果您按照上述步骤操作,应该可以看到应用程序正在运行,如图 2.6 所示。如果使用 CLI 运行应用程序,则可以执行 dotnet watch 命令。这比标准的 dotnet run 命令要好得多,因为它不仅能自动启动浏览器,还能观察项目中源文件的变化,并通过 .NET 6 附带的热重载功能应用这些变化。这意味着大多数简单的编辑,如应用新的 CSS 类或更改标记,几乎都能立即应用,而不会丢失应用程序的状态–极大地提高了工作效率。

在这里插入图片描述
图 2.6 使用 Blazor WebAssembly 模板生成的应用程序初始页面

欢迎使用您的第一个 Blazor 应用程序!模板提供了三个页面:主页、计数器页面和获取数据页面。图 2.6 显示了主页。计数器页面显示一个简单的计数器,点击按钮可以增加计数。获取数据 "页面显示从外部源(独立模板中的 JSON 文件)加载的表格中的天气预报数据列表。

请随意点击,感受一下使用它的感觉。熟悉应用程序后,我们将继续了解组成应用程序的关键组件。

2.3 Blazor 应用程序的关键组件

虽然模板生成了多个文件,但并非所有文件都需要了解。在本节中,我们将介绍你应该了解的关键文件、它们的作用以及它们的重要性。然后,我们将删除项目中的所有其他文件,为开始创建 Blazing Trails 提供一个干净的基础。

2.3.1 Index.html

Index.html 是 Blazor WebAssembly 应用程序最重要的组件之一。它位于项目的 wwwroot 目录中,是 Blazor 应用程序的主机页面。请参见图 2.7,了解关键元素的细分。
在这里插入图片描述
图 2.7 Blazor WebAssembly 应用程序 index.html 页面中关键元素的细分

index.html 文件中的关键元素是指向 Blazor JavaScript 运行时(blazor.webassembly.js)的链接,该链接位于页面底部附近。正如我们在第 1 章中看到的,该文件用于下载基于 .NET WebAssembly 的运行时、应用程序及其任何依赖项。下载完成后,它还会初始化运行时,加载并运行应用程序。

应用程序运行时,其内容需要输出到页面的某个位置;默认情况下,输出到一个 ID 为 appdiv 中。该元素是一个 div 并不重要,它可以是任何 HTML 元素。重要的是 id=“app”。这是可以配置的,并在 Program.cs 文件中进行设置,我们稍后将查看该文件。标签中存在的任何默认内容都将在运行时替换为应用程序的输出。这样做有一个好处:初始内容可以用作占位符,在应用程序准备就绪之前一直显示给用户。图 2.7 显示了一个加载信息,就是一个例子。

如果应用程序中出现未处理的异常,Blazor 将显示一个特殊的用户界面,向用户发出出错的信号。该界面在 index.html 中定义。用户可以根据自己的喜好自定义界面,但包含的元素必须有一个值为 blazor-error-ui 的 id 属性。默认信息(如图 2.8 所示)将说明出现了问题,并为用户提供一个按钮,该按钮将导致重新加载整个页面,即重启应用程序。这是目前唯一安全的选择,因为应用程序将处于未知状态。

index.html 文件的最后一个关键部分是 base 标签。当涉及到客户端路由时,这是一个重要的标签,我们将在第 4 章中详细讨论。这个标签之所以重要,是因为它告诉 Blazor 的路由器哪些 URL 或路由在它的处理范围内。如果缺少这个标签或配置不正确,那么在浏览应用程序时,你可能会看到一些意想不到或无法预测的行为。默认情况下,该标记的配置值为 /。这意味着应用程序运行在域的根目录下(例如 www.blazingtrails.com),路由器应处理该域内的所有导航请求。但是,如果应用程序作为子应用程序运行(例如 www.blazorapps.com/blazingtrails),则基础标签需要反映这一点,其值为 /blazingtrails/。这意味着路由器将只处理以 /blazingtrails/ 开头的导航请求。重要的是,要确保输入的基本标签值以 / 结尾。如果漏掉了这一点,浏览器将删除任何值,直到找到 /。例如,/blazingtrails/ 将按原样使用,而 /blazingtrails 将变成 /

在这里插入图片描述
图 2.8 当应用程序出现无法处理的异常时,Blazor 显示的默认错误 UI。用户可以选择重新加载应用程序,以恢复工作状态。

2.3.2 Program.cs

与其他 ASP.NET Core 应用程序一样,Blazor 应用程序一开始也是 .NET 控制台应用程序。使它们成为 Blazor 应用程序的是它们运行的主机类型。就 Blazor WebAssembly 而言,它运行的是 WebAssemblyHost。该文件中包含的代码旨在配置和创建该主机。图 2.9 显示了程序类的默认配置。

在这里插入图片描述
图 2.9 Blazor WebAssembly 应用程序的默认程序类,可在 Program.cs 文件中找到

从 .NET 6 开始,默认情况下,Program.cs 文件使用一种称为顶级语句的新语法(最初在 C# 9 中引入)。从根本上说,这删除了文件中的大量模板,目的是降低进入 .NET 生态系统的门槛。这意味着不再需要命名空间定义或带有静态 Main 方法的Program类。我们只需开始编写代码,一切都能正常运行。正如你可能已经猜到的那样,在编译引擎盖下,编译器仍然会生成一个带有静态 Main 方法的 Program 类;只是我们现在不需要担心这个问题了。有关顶级语句的更多信息,请访问 Microsoft 文档网站 (http://mng.bz/nNE4)。

在图 2.9 中,有两个关键的配置部分:定义应用程序的根组件,以及配置任何服务并将其添加到 IServiceCollection 中。在定义根组件时,我们向构建程序提供了两个信息–组件的类型和组件应注入主机页面的位置。

默认情况下,模板定义了两个根元素。第一个是 App 组件。这是应用程序的入口点,我们接下来将对此进行研究。第二个是 HeadOutlet 组件。这是在 .NET 6 中新增的组件,可以让我们对主页面中的 head 元素进行修改,如更新页面标题或设置元标签。

Blazor 需要知道这些根组件在主机页面中的位置。builder.RootComponents.Add方法的参数是一个CSS选择器,用于确定注入组件的目标元素。可以针对特定元素或具有特定 ID 的元素–例如,#root-component 或任何其他有效的 CSS 选择器。要了解有关 CSS 选择器的更多信息,请访问 www.w3schools.com/cssref/css_selectors.asp。

下一行显示的是 HttpClient 已配置并注册到 IServiceCollection,通过依赖注入(DI)将其提供给类和组件。Blazor 使用与其他 ASP.NET Core 应用程序相同的 DI 容器,并允许使用三种生命周期之一注册服务:

  • Transient 瞬态–每次从服务容器请求时都会提供新实例。在单次请求中,如果两个对象需要一个瞬时服务实例,它们将分别收到一个不同的实例。
  • Scoped 作用域–每个请求只创建一次新实例。在一个请求中,您将始终在整个应用程序中获得相同的服务实例。
  • Singleton 单例–首次从服务容器请求时或运行 Program.Main 方法时创建一个实例,并在注册时指定一个实例。在应用程序的整个生命周期内,每次请求都会使用同一个实例。

虽然在 DI 中使用的是相同的系统,但在 Blazor 应用程序中,作用域的行为略有不同。对于 Blazor WebAssembly,Scoped 和 Singleton 的行为相同。这是因为在 Blazor WebAssembly 应用程序中没有请求。它是在客户端浏览器中下载并执行的。由于Scoped生命周期没有请求绑定,因此任何Scoped服务都将在应用程序生命周期内有效,这就使它们与Singleton服务相同。

Main 方法的最后一项工作是使用 WebAssemblyHostBuilder 指定的所有配置,并调用其 Build 方法。这将创建一个 WebAssemblyHost 实例,它是 Blazor 应用程序的核心。它包含运行应用程序所需的所有应用程序配置和服务。

Startup class在哪里?

如果您过去使用过其他 ASP.NET Core 应用程序,您可能会问自己,为什么我们要在 Program.cs 而不是 Startup 类中配置服务。答案是,默认模板中不再存在Startup类。

从 .NET 6 开始,默认 ASP.NET Core 模板不再指定 Program.cs 文件和 Startup.cs 文件。这是因为模板现在默认使用顶级语句,并将所有配置推送到单个 Program.cs 文件中。

不过,Blazor WebAssembly 早在 .NET Core 3.1 中就放弃了 Startup 类,因为它没有必要。Startup 类包含两个方法:ConfigureServices 和 Configure。其中第一个方法 ConfigureServices 用于向服务容器配置和注册服务。Configure 用于配置应用程序的中间件管道。

然而,在Blazor WebAssembly中,没有中间件,因为没有请求。Blazor WebAssembly(或任何SPA框架)的行为更像桌面应用程序,而不是传统的基于服务器的网络应用程序。一旦加载完成,它就会在用户浏览器中本地处理交互,只向服务器发送额外数据请求。

这意味着 Startup 类中不需要 Configure 方法,因此只剩下 ConfigureServices 方法。Blazor 团队认为没有必要在两个文件中都只包含一个方法,因此他们将服务注册移到了 Program.Main 方法中,并删除了Startup 类。

2.3.3 App.razor

这是 Blazor 应用程序的根组件,我们在上一节中看到了如何在 Program.cs 文件中进行配置。不过,您也可以根据需要将其他组件配置为根组件。或者,你甚至可以拥有多个根组件。您只需更新 Program.Main 方法中的配置即可。

App 组件包含一个构建多页面应用程序的重要组件–Router 组件。该组件负责管理客户端路由的所有方面。当应用程序首次启动时,Router 组件将使用反射功能扫描应用程序的程序集,以查找任何可路由组件(我们将在第 4 章中详细介绍这些组件,但它们本质上都是页面)。然后,路由器会在路由表中存储这些组件的信息。每当点击链接或以编程方式触发导航时,路由器都会查看请求的路由,并尝试在路由表中找到匹配的路由。如果找到匹配,就会加载该组件;否则就会加载一个未找到的模板,该模板在 Router 组件中指定。

我们将在第 4 章中详细介绍 Router 组件,所以如果现在听起来有点混乱也不用担心。我们只需知道,Router 组件位于 App 组件内部,负责处理应用程序中的路由。

2.3.4 wwwroot folder and _Imports.razor

我将在本节中介绍这两个文件,因为关于它们的内容并不多。事实上,_Imports.razor 文件是运行 Blazor 应用程序所不需要的一个组件,但有了它,事情就变得简单多了。

按照惯例,所有 ASP.NET Core 应用程序都有一个 wwwroot 文件夹,用于存储公共静态资产。您可以在这里放置图片、CSS 文件、JavaScript 文件或任何其他需要的静态文件。您放在该文件夹中的任何内容都将随应用程序一起发布,并在运行时可用。如前所述,这里也是存放 index.html 文件的地方。

在构建 Blazor 应用程序时,_Imports.razor 文件是可选的。不过,至少有一个这样的文件还是很有用的。它的作用是存储using 语句。这样做的好处是,文件目录和任何子目录中的所有组件都可以使用这些using 语句。这样,您就不必为应用程序中的每个组件添加通用的 using 语句了。

正如我所提到的,在整个项目中可以有多个 _Import.razor 文件。这样,我们就可以根据文件结构指定仅适用于特定组件集的 using 语句。例如,如果我们有一个 BlazingTrails > Features > Home 的结构,并且我们希望一组使用语句只应用于 Home 文件夹中的组件,那么我们可以在该文件夹中添加一个 _Imports.razor 文件,并添加我们需要的使用语句。这样,这些语句就只能应用于Home 文件夹中的组件。

2.4 编写第一个组件

我们已经了解了模板创建的应用程序,并介绍了每个关键文件以及它们的基本功能。现在是时候编写一些我们自己的代码了。正如我在本章开头所说,我们将构建 Blazing Trails 应用程序的基础,如图 2.10 所示。

首先,我们将讨论如何组织应用程序文件。其次,我们将删除模板生成的所有不需要的文件。这样我们就有了一个干净的基础来开始构建。最后,我们将定义几个新的组件来创建图 2.10 所示的内容:一个布局组件、一个页面组件和几个常规组件。听起来很有趣?让我们开始吧!

在这里插入图片描述
图 2.10 我们将在本章中创建的 "Blazing Trails "应用程序

2.4.1 使用功能文件夹整理文件

在开始添加自己的代码之前,我们需要删除模板生成的所有不必要文件。默认情况下,模板使用的应用程序结构按责任划分文件。有一个 Pages 文件夹用于存放可路由组件,有一个 Shared 文件夹用于存放在多个地方使用的文件或全局性文件。这种划分方式不能很好地扩展,而且会增加或更改功能的难度,因为文件最终会分散到各个地方。相反,我们将使用一种名为 "功能文件夹 "的结构来组织我们的应用程序。

使用功能文件夹时,与该功能相关的所有文件都存储在同一个地方。这有两大好处。首先,当您要处理某个特定功能时,您需要的所有文件都在同一个地方,这使得一切都更容易理解和发现。其次,它具有良好的扩展性。每次在应用程序中添加新功能时,只需添加一个新文件夹,所有文件都会放在其中。如果每个功能包含大量文件,还可以用子功能来排列它们。图 2.11 展示了两种结构并列的示例。

在这里插入图片描述
图 2.11 按责任/类型或按特征整理文件的并排比较。左边的列表显示按责任/类型整理的文件,右边的列表显示按功能整理的相同文件。

在图 2.11 中,我只显示了组件,但您应该将与该功能相关的任何文件放到该文件夹中–C# 类、TypeScript 文件、CSS 文件等任何文件。图片等静态资产是唯一的例外。这些文件需要放在 wwwroot 文件夹中,否则它们在运行时将不可用,因为静态文件只能从该文件夹中提供。不过,如果您愿意,也可以在 wwwroot 文件夹中镜像您的功能文件夹结构。

在使用 Blazor 组织系统时,我喜欢做的另一件小事是在任何可路由组件后面加上 “页面”(Page)一词。当一个功能包含多个其他组件时,几乎不可能轻易识别可路由组件。唯一真正的办法是打开文件,检查顶部的 @page 指令。通过在末尾添加page ,这一点一目了然,也省去了在不同文件中四处查找的麻烦。

既然我已经让你相信了使用功能文件夹的价值,那么我们就开始将其付诸实践吧。我们将删除 Blazing Trails 应用程序中的大量文件,让我们有一个全新的开始。然后,我们将开始使用功能文件夹结构添加新功能。

首先删除 "Pages "和 "Shared"文件夹及其内容。然后删除 wwwroot 文件夹中的 Sample-Data 文件夹。同时删除 app.css 中的大部分内容;只保留打开图标样式的导入语句以及名为 #blazor-error-ui#blazor-error-ui .dismiss 的类。我们还需要删除 _Imports.razor 文件中的最后一条 using 语句,即 @using BlazingTrails.Client.Shared

我们已经删除了所有我们不需要的部分,所以项目现在看起来应该如图 2.12 所示。但不用担心,我们将添加一些内容,然后就可以重新构建了。

在这里插入图片描述
图 2.12 右侧的解决方案资源管理器面板显示了删除所有不需要的文件后应用程序的状态。主面板显示的是剥离了boilerplate CSS 类之后的 app.css。

在项目根目录下添加一个名为 "Features"的新文件夹。在该文件夹中,添加名为 "Layout"和 "Home "的文件夹。在 "Layout"中,添加名为 MainLayout.razor 的新 Razor 组件。在主页中,添加一个名为 HomePage.razor 的新 Razor 组件。完成后,返回 _Imports.razor 并添加以下 using 语句:

@using BlazingTrails.Client.Features.Home
@using BlazingTrails.Client.Features.Layout

此时,您的解决方案应与图 2.13 一致。
在这里插入图片描述

图 2.13 使用特征文件夹的项目新结构

现在就可以构建应用程序了,应该不会出现任何构建错误。不过,您还不能运行应用程序,否则会出错。我们还有一些东西需要配置。接下来,我们先简单了解一下样式,然后再继续为 Blazing Trails 定义布局。

2.4.2 设置样式

Blazor 模板随附一个名为 Bootstrap 的 CSS 框架。由于它是开发人员常用的应用程序样式选择,而且已经包含在内,因此我们将在 Blazing Trails 中使用它。(您可以访问 Bootstrap 文档网站了解更多关于该框架如何工作以及它所提供的全部功能:http://mng.bz/J26a)。

我创建了一些自定义图片来为应用程序打上品牌。有一个徽标(logo.png)和一个导航栏背景(navbar-bg.jpg)。这些图片可以在本书附带的 GitHub repo (https://github.com/chrissainty/blazor -in-action)上找到,需要放在 wwwroot 下名为 Images 的新文件夹中。如果你愿意,也可以使用自己的图片。保持相同的名称意味着本书中的所有代码都能正常工作。如果要更改名称,只需确保更新相关位置的代码即可。

在开始布局之前,我们要在 app.css 文件中添加一些自定义样式。在这里添加的样式将影响整个应用程序。这些样式将自定义一些常用元素的外观,如链接和按钮以及导航栏。打开 wwwroot > css 文件夹中的 app.css。然后复制以下代码。
清单 2.1 app.css

:root {--brand: #448922;
  --brand-hover: #5da030;
}
 
body {
  background-color: #f9f9f9;
}
 
a {
  color: var(--brand);
  text-decoration: underline;
}
 
a:hover {
  color: var(--brand-hover);
    text-decoration: none;
}
 
.navbar {
  border-bottom: 2px solid var(--brand);
  background: linear-gradient(90deg, rgba(255,255,255,1) 5%,rgba(255,255,255,0) 100%), url("../images/navbar-bg.jpg") no-repeat 
  ➥center;
  background-size: cover;
}
 
.btn-primary {
  background-color: var(--brand);
  border-color: var(--brand);
}
 
.btn-primary:hover {
  background-color: var(--brand-hover);
  border-color: var(--brand-hover);
}
 
.btn-outline-primary {
  border-color: var(--brand);
  color: var(--brand);
}
 
.btn-outline-primary:hover {
  background-color: var(--brand);
  border-color: var(--brand);
}
 
.grid {
    display: grid;
    grid-template-columns: repeat(3, 288px);
    column-gap: 123px;
    row-gap: 75px;
}

❶ CSS 变量用于避免重复使用 brand 颜色代码。这也使得将来更新颜色变得更加容易。

我不会详细介绍每种样式的作用,但我要指出 CSS 中的一个很好的功能,即 CSS 变量。它允许我们定义变量,这些变量可以在整个应用程序的样式表中使用。使用变量意味着我们只需指定一次brand 颜色。它们还能让未来的绑定变更更容易完成。

关于样式设计,我们要做的最后一件事是对 index.html 页面进行一些调整。首先,我们将添加对 Bootstrap 图标 (https://icons.getbootstrap.com/) 的引用。这个免费图标集来自 Bootstrap 的创建者,看起来很棒。为了使用它们,我们将在 wwwroot 中 index.html 页面的 head 元素中添加以下一行:

<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">

这行内容可以直接粘贴到 Bootstrap.min.css 文件的现有链接下。该链接将从内容交付网络(CDN)中提取 Bootstrap 图标 CSS 文件。如果你更喜欢使用本地文件而不是 CDN,你可以从我提供的 Bootstrap Icons 网站链接中找到下载文件的方法。

其次,我们要将标签的标题从 BlazingTrails.Client 更新为 Blazing Trails。更新后的标签应如下所示:

<title>Blazing Trails</title>

我们要做的第三个也是最后一个改动是删除以下样式表引用:

<link href="BlazingTrails.Client.styles.css" rel="stylesheet" />

这是用于范围 CSS 的,我们将在第 3 章中介绍这一主题。届时,我们会将其添加回去。但现在,我们要删除它,因为它会导致浏览器控制台出错。现在,我们的基本样式已经就位,下面让我们继续设置布局。

2.4.3 确定布局

Blazor 从 ASP.NET Core 的其他部分借鉴了布局的概念。从本质上讲,它允许我们定义多个页面所需的通用用户界面。页眉、页脚和导航菜单等都是布局中可能包含的内容。我们还添加了一个名为 "Body "的参数的引用,我们希望在这里呈现页面内容。这个参数来自一个特殊的基类,Blazor 中的所有布局都必须继承这个基类,即 LayoutComponentBase。图 2.14 举例说明了布局中可能定义的内容,以及渲染的页面内容将在何处显示。
在这里插入图片描述
图 2.14 一个定义共享用户界面的布局示例。任何使用此布局的页面,其内容都将呈现在标有 "Page Content "的中心面板中。

您不必为整个应用程序坚持使用单一布局;您可以为应用程序的不同部分使用多种布局。因此,如果您想为面向公众的页面采用特定的布局,而为管理页面采用不同的布局,您就可以这样做。在 Blazor 中,默认布局是在 App.razor 中的 Router 组件(图 2.15)中定义的。这将自动应用于应用程序中的所有页面。
在这里插入图片描述
图 2.15 显示了在 Router 组件上定义默认布局的位置。该布局将自动应用于应用程序中的所有页面。

如果想在某些页面上使用不同的布局,可以使用 @layout 指令指定一个替代方案。该指令位于页面顶部,你可以通过它传递你希望使用的组件名称。例如,如果我们有一个名为 AdminLayout 的替代布局,我们的布局指令将如下所示: @layout AdminLayout.

既然我们已经了解了什么是布局以及布局的用处,那么就让我们开始为 Blazing Trails 定义初始布局吧。我们将更新 MainLayout 组件。首先,我们要做两件事。首先,我们将使用 @inherits 指令从 LayoutComponentBase 类继承。这就将该组件标记为布局组件,并使我们可以访问 Body 参数。其次,我们将使用 Body 参数定义页面内容的呈现位置。清单 2.2 显示了 MainLayout 的外观。

清单 2.2 Blazing Trails 的 MainLayout 组件

@inherits LayoutComponentBase       ❶
 
<main class="container mt-5 mb-5">
    @Body                           ❷
</main>

❶ 将组件定义为布局组件
❷ 标记页面内容在布局中的呈现位置

现在我们的布局中唯一缺少的就是标题。我们将把它定义为一个单独的组件,由于它是整个布局功能的一部分,因此将放在 MainLayout 组件旁边的布局功能文件夹中。像之前一样,添加一个名为 Header.razor 的新 Razor 组件,然后添加清单 2.3 所示的标记,添加一个显示文字 Blazing Trails 的 Bootstrap 导航条。
清单 2.3 定义 Bootstrap 导航条的页眉组件

<nav class="navbar mb-5 shadow">
    <a class="navbar-brand" href="/">
        <img src="/images/logo.png">
    </a>
</nav>

这就是我们需要的 Header 组件。现在我们可以将其添加到 MainLayout 中,就像声明任何普通 HTML 元素一样。MainLayout 的最终代码如下所示。
清单 2.4 已完成的 MainLayout 组件

@inherits LayoutComponentBase
 
<Header /><main class="container mt-5 mb-5">
    @Body
</main>

❶ 这是对 Header 组件的引用。在 Blazor 中,组件引用区分大小写,并应以大写字母开头,以避免与常规 HTML 元素发生冲突。

布局就是这样。如果此时尝试运行应用程序,就能看到我们刚刚创建的页眉,但会出现 "抱歉,此地址无内容 "的提示。这是因为我们还没有定义任何可路由组件(页面)。接下来,让我们创建 Blazing Trails 的主页。

2.4.4 Blazing Trails 主页

在本节中,我们将为应用程序创建并加载一些测试数据(轨迹),并创建一个卡片组件来显示测试数据中的每条轨迹。到本章结束时,我们将拥有本章开始时看到的应用程序。在第 2.4.1 节中,我们已经创建了主页组件,但它仍然带有新组件的模板代码。我们需要更新这些代码,使组件可以路由。完成更新后,我们将定义一个表示路径的类。然后,我们可以定义一些测试数据,用于构建用户界面的其余部分。最后,我们将把测试数据加载到主页中,并通过我们将创建的可重复使用的 TrailCard 组件循环显示各种路径。听起来不错吧?让我们开始吧!

如前所述,要使组件可路由,我们需要使用 @page 指令和路由模板来指定它将负责的路由。在 HomePage.razor 文件的顶部,添加指令和路由模板:

@page "/"

当路由模板只包含一个正斜线 (/) 时,它会告诉路由器这是应用程序的根页面。此时可以运行应用程序,检查主页内容是否正在显示。您应该会看到与之前相同的页眉,并显示 HomePage 文本(图 2.16)。

在这里插入图片描述
图 2.16 显示标题组件和主页组件及其默认文本的 Blazing Trails 应用程序的当前状态

我们需要一种在代码中表示路径的方法。为此,我们将在主功能文件夹中添加一个名为 Trail 的新类。它将包含路径的各种数据点,如名称、长度和完成路径所需的时间。我们还将在文件中加入第二个名为 RouteInstruction 的类。这个类代表路径上的一个路标,可以帮助引导其他步行者。我喜欢在同一个文件中包含密切相关的类,因为这样可以让它们的工作变得更容易。清单 2.5 显示了 Trail.cs 文件的内容。
清单2.5 Trail.cs

public class Trail
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public string Description { get; set; } = "";
    public string Image { get; set; } = "";
    public string Location { get; set; } = "";
    public int TimeInMinutes { get; set; }
    public string TimeFormatted => $"{TimeInMinutes / 60}h {TimeInMinutes % 60}m";
    public int Length { get; set; }
    public IEnumerable<RouteInstruction> Route { get; set; } = Array.Empty<RouteInstruction>();
}
 
public class RouteInstruction
{
    public int Stage { get; set; }
    public string Description { get; set; } = "";
}

现在我们有了路径的定义,我们将定义一些测试数据来使用。目前,我们的应用程序没有后台–我们无法调用 API 来检索或保存数据。为了模拟通过 http 调用从 API 加载数据,我们将在 JSON 文件中定义测试数据。这是开发当前没有可用服务器元素的前端应用程序的好方法。我们可以使用 HttpClient 从 JSON 文件加载数据,就像从 API 加载数据一样。一旦服务器元素建立起来,只需更新 http 调用,使其指向 API 端点而不是 JSON 文件即可。

在 wwwroot 文件夹中创建名为 trails 的目录。在该文件夹中,添加一个名为 trails-data.json 的新 JSON 文件,代码如下所示。
清单 2.6 trail-data.json: 包含路径测试数据

[
    {
    "id": 1,
    "image": "trails/1.jpg","name": "Countryside Ramble",
    "location": "Durbach, Germany",
    "timeInMinutes": 195,"length": 11,
    "description": "A really nice walk in some very scenic countryside.",
    "route": [{
        "stage": 1,
        "description": "Follow the path to the fork and go left."
      },
      {
        "stage": 2,
        "description": "Cross the bridge and turn right."
      },
      {
        "stage": 3,
        "description": "The trail finishes at the end of the valley."
      }
    ]
  },
  {
    "id": 2,
    "image": "trails/2.jpg",
    "name": "Woodland Walk",
    "location": "Nottingham, UK",
    "timeInMinutes": 80,
    "length": 4,
    "description": "Lots of tall trees and bubbling streams. 
    ➥A very calming hike.",
    "route": [
      {
        "stage": 1,
        "description": "The walk is one big loop. Just keep following the 
        ➥signs."
      }
    ]
  }]

❶ 显示运行时跟踪图像相对于应用程序根目录的位置。
❷ 这是走完路径所需的时间,即总分钟数。
❸ 这是一个路径指示阵列。

我在本代码片段中只包含了两条轨迹,但你可以随意在文件中添加更多的轨迹。GitHub 上本章的配套代码 (https://github.com/chrissainty/blazor-in-action/tree/main/chapter-02) 将包含更多的轨迹,如果你喜欢,也可以复制它。

至于图片,我只是从 Pixabay (https://pixabay.com) 下载了一些免费图片,并将它们添加到 wwwroot > Trails 文件夹中。

测试数据就绪后,我们将返回主页组件,并在此加载数据。我们将使用 HttpClient 来加载数据,但要使用它,我们需要使用依赖注入来获得它的实例。Blazor 提供了一个注入指令,让这一切变得简单: @inject [TYPE] [NAME],其中[Type]是我们需要的对象类型,[Name]是我们在组件中处理该实例时使用的名称。在页面指令下,添加 @inject HttpClient Http,这将为我们提供一个可以使用的 HttpClient 实例。

注入指令: 到底发生了什么?

注入指令允许我们快速、轻松地将在 Program.cs 中向服务容器注册的对象实例注入到组件中。但该指令只是一些语法糖,为我们节省了大量的键入工作。

Blazor 对其组件使用属性注入。注入指令编译为一个属性,该属性带有一个名为 Inject 的属性。因此,如果我们以上面的 Inject 指令为例,它最终会被编译成这样:

[Inject]
public HttpClient Http { get; set; }

事实上,如果你愿意,可以在组件底部的 @code 块中写入这些代码,而完全不使用注入指令。只是要输入更多的代码而已!

在使用 HttpClient 之前,我们需要存储调用返回的结果。我们的 JSON 测试数据是一个轨迹数组,由于我们不会修改返回的结果,只是将其列出,因此我们可以创建一个 IEnumerable 类型的私有字段。如下所示,我们可以在组件的 @code 代码块中这样做。

清单 2.7 注入 HttpClient 和 trails 字段的主页

@page "/"                                   ❶
@inject HttpClient Http                     ❷
 
<h3>HomePage</h3>
 
@code {
 
    private IEnumerable<Trail> _trails;}

❶ 页面指令定义了此组件负责的路由。
❷ Inject 指令用于从依赖注入容器中获取对象实例。
❸"Private "字段保存跟踪数据。

既然我们已经有了存储测试数据的地方,就可以调用它来检索数据了。OnInitialized(初始化)生命周期方法就是一个很好的地方。该方法由 ComponentBase 提供–所有 Blazor 组件都继承自 ComponentBase–它是三个主要生命周期方法之一。其他两个是 OnParametersSetOnAfterRender,它们也都有异步版本。OnInitialized 在组件生命周期中只运行一次,因此非常适合加载初始数据,就像我们需要的那样。我们将在第 3 章中详细介绍组件生命周期方法,所以如果你现在还有问题,请不要担心。

要从 JSON 文件中获取数据,我们可以像访问 API 一样发出 GET 请求。不过,我们在调用中传递的不是 API 的地址,而是 JSON 文件的相对位置。由于该文件位于 wwwroot 文件夹中,因此在运行时它将作为静态资产可用,就像 CSS 文件一样。这意味着我们需要在 GET 请求中传递的路径是 “trails/trail-data.json”。

Blazor 带来的一大生产力提升是为 HttpClient 增加了一些扩展方法:

  • GetFromJsonAsync
  • PostAsJsonAsync
  • PutAsJsonAsync

这些方法使用 System.Text.Json 库。第一个方法将把包含 JSON 有效负载的成功响应反序列化为我们指定的类型 (T)。第二个和第三个方法将把对象序列化为 JSON 发送给服务器。所有这三种方法都在一行中完成。无需再手动序列化和反序列化对象或检查成功代码。这使得一切都变得更加简洁,并删除了大量的模板。

使用这些新方法时要注意,当服务器返回非成功代码时,它们将抛出 HttpRequestException 类型的异常。这意味着,将这些调用封装在 try catch 语句中通常是一种好的做法,这样就能优雅地处理非成功代码。主页组件 @code 块的最终代码如下所示。

清单 2.8 主页组件的最终代码块

@code {
    private IEnumerable<Trail> _trails;protected override async Task OnInitializedAsync()
    {
        try
        {
            _trails = await Http.GetFromJsonAsync
             ➥<IEnumerable<Trail>>("trails/trail-data.json");}
        catch (HttpRequestException ex){
            Console.WriteLine($"There was a problem 
            ➥loading trail data: {ex.Message}");
        }
    }
}

❶ 私有字段保存 http 调用返回的结果。
❷ http 调用从 trail-data.json 文件加载测试数据。
❸ Catch 块处理来自 http 调用的非成功响应。

您可能已经注意到,Visual Studio(或您使用的任何集成开发环境)通过在 _trails 字段和加载路径数据的调用(图 2.17)下显示绿色波浪线,说明这些部分出现了问题。
在这里插入图片描述
图 2.17 代码部分下的波浪线表示代码中存在需要解决的问题。

我们之所以看到这些警告,是因为.NET 6 的默认项目设置发生了变化–默认情况下启用了可化为空的引用类型(NRTs)(http://mng.bz/v6Or)。该功能旨在帮助我们在代码中明确空值的流向。从本质上讲,它使我们能够正确处理空值。

我们在 _trails 字段上看到的警告是编译器在告诉我们,这是一个不可为空的类型,并且没有给定默认值或在构造函数中初始化。这意味着我们可以尝试使用它,但其值可能为空。GetFromJsonAsync 方法可以返回空值;这里的警告就是告诉我们这一点,以便我们采取适当的行动。解决这些警告的方法是使用 ? 操作符使 _trails 字段为空:

private IEnumerable<Trail>? _trails;

现在我们已经声明我们知道这个字段可能为空,你会发现警告已经消失了。如果你是第一次使用 NRT,可能需要一点时间来适应。但正确处理空值将有助于我们编写更好、更稳定的应用程序。

太好了!现在我们已经将数据加载到了组件中,但我们还需要做一些事情。如果能在加载数据时向用户显示一条信息,让他们知道我们正在加载数据,以防数据加载需要一段时间,那就更好了。有了数据后,我们需要使用图 2.18 所示的卡片来显示数据。

在这里插入图片描述
图 2.18 卡片用于显示每条路径及其相关信息。

我们可以在标记中使用一个简单的 if 语句来检查 _trails 字段的值。如果值为空,那么我们可以推测数据仍在加载中,当然不包括出错的情况。如果值不为空,那么我们就有了一些数据,可以继续并显示这些数据(见下面的列表)。

清单 2.9 HomePage.razor: 主页组件的标记

<PageTitle>Blazing Trails</PageTitle>      ❶
 
@if (_trails == null){
    <p>Loading trails...</p>
}
else
{
    <div class="grid">
        @foreach (var trail in _trails){
            <div class="card shadow" style="width: 18rem;">
                <img src="@trail.Image" class="card-img-top" 
                ➥alt="@trail.Name">
                <div class="card-body">
                    <h5 class="card-title">@trail.Name</h5>
                    <h6 class="card-subtitle mb-3 text-muted">
                        <span class="oi oi-map-marker"></span>
                        @trail.Location
                    </h6>
                    <div class="d-flex justify-content-between">
                        <span>
                            <span class="oi oi-clock mr-2"></span> 
                            @trail.TimeFormatted
                        </span>
                        <span>
                            <span class="oi oi-infinity mr-2"></span> 
                            @trail.Length km
                        </span>
                    </div>
                </div>
            </div>
           }
    </div>
}

❶ PageTitle 组件用于设置浏览器标签页中显示的页面标题。
❷ 检查数据是否已加载。
❸ 数据加载完毕后,对其进行循环,为每条线索创建一张卡片。

我们在这里的代码中使用了 Blazor 的另一个很酷的功能–PageTitle 组件。它用于更改浏览器标签页中的网页标题–这在.NET 6之前的Blazor中是很难实现的。网页的标题元素位于主机页面的头部元素中,不在 Blazor 应用程序的范围内。在以前的 Blazor 版本中,要操作页面标题,我们需要使用 JavaScript 互操作,或者找到社区中某个人创建的 NuGet 软件包。

此时,您就可以创建并运行应用程序了。如果一切按计划进行,你应该能在主页上看到路径显示。现在,我们可以到此为止了,但我认为我们应该先做一个小小的重构。

您可能已经注意到,在列表 2.9 中,有相当多的代码是用来创建足迹卡的。虽然这些代码都是完全正确的,但如果能将它们封装在一个组件中不是更好吗?这样,HomePage 组件中的代码就更容易阅读了。

在主页功能文件夹中创建一个名为 TrailCard.razor 的新组件。然后用主页中卡片的标记替换模板代码。

这很简单。但现在我们遇到了一个问题。我们如何获取当前的路径数据?答案就是parameters

我们可以通过参数向组件传递数据。可以把这些参数看作组件的公共 API,它们的工作方式是单向的,即从父组件到子组件。我们可以在代码块中创建一个公共属性,并用参数属性对其进行装饰,从而定义参数。我们可以使用组件标签上的属性从父代向子代传递数据。

对于我们的 TrailCard 组件,我们将创建一个参数,允许我们从父级传入当前路径。然后我们就可以更新 Razor 代码以使用该参数。TrailCard 组件完成后,请参阅以下列表。
清单 2.10 TrailCard.razor

<div class="card shadow" style="width: 18rem;">
    <img src="@Trail.Image" class="card-img-top" alt="@Trail.Name">
    <div class="card-body">
        <h5 class="card-title">@Trail.Name</h5>
        <h6 class="card-subtitle mb-3 text-muted">
            <span class="oi oi-map-marker"></span>
            @Trail.Location
        </h6>
        <div class="d-flex justify-content-between">
            <span>
                <span class="oi oi-clock mr-2"></span>
                @Trail.TimeFormatted
            </span>
            <span>
                <span class="oi oi-infinity mr-2"></span>
                @Trail.Length km
            </span>
        </div>
    </div>
</div>
 
@code {
    [Parameter, EditorRequired]
    public Trail Trail { get; set; } = default!;}

❶ 定义所需的组件参数

除了使用参数属性外,我们还添加了另一个名为 EditorRequired 的属性。这个属性是在 .NET 6 中引入的,我们可以用它来表示某个参数是必需的。如果我们现在尝试使用 TrailCard 组件,但没有向 Trail 参数传递线索,我们将收到警告。

如前所述,由于启用了可归零引用类型,我们需要处理参数的潜在可归零性。处理方法有两种:将参数标记为可归零或赋予其默认值。我们使用哪种方法取决于具体情况,但在本例中,我们将选择后者,并将其与null forgiving operator (!) 结合使用。

允许空值操作符允许我们告诉编译器某个值不是空值或不会是空值。在编译器无法自行判断的情况下,这个操作符非常有用,而这种情况确实时有发生。对于一个必填的组件参数(也是引用类型),我们可以合理地假设该参数的值不会为空,除非我们作为开发人员做了一些奇怪的事情。因此,使用 default 关键字和 null 宽松操作符初始化参数是一个不错的选择。在使用该参数时,我们无需在标记或方法中编写任何额外的空值检查代码,如果在运行时该参数的值为空,我们会看到一个明确的错误,从而可以调试该问题。

现在只需更新主页组件以使用新的 TrailCard 组件。主页组件的最终代码如下所示。
清单 2.11 更新主页组件以使用 TrailCard

@page "/"                                                      ❶
@inject HttpClient Http                                        ❷
 
@if (_trails == null){
    <p>Loading trails...</p>
}
else
{
    <div class="grid">
        @foreach (var trail in _trails){
            <TrailCard Trail="trail" />}
    </div>
}
 
@code {
    private IEnumerable<Trail>? _trails;protected override async Task OnInitializedAsync()
    {
        try
        {
            _trails = await Http.GetFromJsonAsync
             ➥<IEnumerable<Trail>>("trails/trail-data.json");}
        catch (HttpRequestException ex){
            Console.WriteLine($"There was a problem loading trail data: 
            ➥{ex.Message}");
        }
    }
}

❶ Page 指令将组件标记为可路由组件,路由模板说明了它负责的路由。
❷ Inject 指令允许从服务容器向组件注入服务。
❸ If 块会检查 _trails 字段是否为空,并显示加载信息,直到该字段有了值。
❹ Foreach 循环遍历 _trails 字段中包含的路径。
❺子组件定义了一个 TrailCard 组件,并通过 TrailCard 上定义的 Trail 参数传递当前路径。
❻ 该 "私有"字段保存当前路径。
❼ HttpClient 用于从本地 JSON 文件加载测试数据。
❽ Catch 块允许从容处理来自 http 调用的错误。

恭喜您,我们刚刚建成了 "开拓之路 "的第一小部分!

摘要

本章涉及的内容很多,所以如果你感到有些不知所措,也不必担心。在本书的其余部分,我们将深入探讨所涉及的所有内容,当我们完成《Blazing Trails》时,你将成为一名 Blazor 专家!

  • Blazor 附带两个模板–Blazor Server 和 Blazor WebAssembly–可帮助您更快地开始构建应用程序。
  • Blazor WebAssembly模板较为复杂,有独立和托管两种配置。单机版生成一个单一的Blazor WebAssembly项目,这在您有现有后端或您的应用程序不需要后端的情况下非常有用。托管版提供了一个全栈应用程序,包含 ASP.NET Core 后端、Blazor 前端和用于共享代码的 .NET 类库。
  • Blazor 应用程序既可以通过 Visual Studio 等集成开发环境创建,也可以通过 .NET CLI 命令行创建。
  • 构建应用程序时会自动恢复所需的依赖关系。
  • Blazor 应用程序使用的主机页面包含渲染 Blazor 应用程序的 HTML 元素,以及指向 Blazor JavaScript 运行时的链接。
  • 由于没有中间件管道,Blazor WebAssembly 应用程序没有Startup 类,只有Program 类。服务配置和注册被移至 Program.cs
  • 对于 Blazor WebAssembly,Program.cs 用于创建和运行 WebAssemblyHost 实例。这是通过 WebAssemblyHostBuilder 完成的。我们使用生成器来配置 Blazor 应用程序的各个方面,如根组件和服务容器。
  • App.razor 是默认的根组件,包含 Router 组件。所有其他组件都将作为 App.razor 的子组件呈现。
  • 在组织应用程序中的文件时,功能文件夹可以带来很多好处。与功能相关的所有内容都集中在一个地方,使更新和维护更加容易。
  • 布局组件是定义通用用户界面(每个页面都会重复出现)的好方法,例如页眉和导航菜单。
  • 值可以通过参数传递到组件中,参数可以被视为组件的 API。参数必须是public 属性,不能是private属性。
  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值