C# 13 和 .NET 9 全知道 :14 使用 Blazor 构建交互式 Web 组件 (2)

如何导航路线并传递路线参数

微软提供了一项名为 NavigationManager 的依赖服务,该服务理解 Blazor 路由和 NavLink 组件。 NavigateTo 方法用于跳转到指定的 URL。

Blazor 路由可以包含不区分大小写的命名参数,您的代码可以通过将参数绑定到代码块中的属性,使用 [Parameter] 属性,最轻松地访问传递的值,如以下标记所示:

@page "/customers/{country}"
Country parameter as the value: @Country
@code { [Parameter] public string Country { get; set; } }

处理缺少默认值的参数的推荐方法是将参数后缀为 ? ,并在 OnParametersSet 方法中使用空合并运算符,如下标记所示:

@page "/customers/{country?}"
Country parameter as the value: @Country
@code { [Parameter] public string Country { get; set; } protected override void OnParametersSet() { // If the automatically set property is null, then // set its value to USA. Country = Country ?? "USA"; } }

如何使用带有路由的导航链接组件

在 HTML 中,您使用 元素来定义导航链接,如下所示的标记:

Customers

在 Blazor 中,使用 组件,如下标记所示:

Customers

NavLink 组件优于锚元素,因为它会自动将其类设置为 active ,如果其 href 与当前位置的 URL 匹配。如果您的 CSS 使用不同的类名,则可以在 NavLink.ActiveClass 属性中设置类名。

默认情况下,在匹配算法中, href 是路径前缀,因此如果 NavLink 具有 href/customers ,如前面的代码示例所示,则它将匹配所有以下路径并将它们全部设置为具有 active 类样式:

/customers
/customers/USA
/customers/Germany/Berlin

为了确保匹配算法仅在路径中的所有文本上执行匹配(换句话说,只有当整个完整文本匹配时才会匹配,而不是仅当路径的一部分匹配时),请将 Match 参数设置为 NavLinkMatch.All ,如下代码所示:

Customers

如果您设置其他属性,例如 target ,它们会传递到生成的基础 元素中。

理解基础组件类

OnParametersSet 方法由组件默认继承的基类定义,名为 ComponentBase ,如下代码所示:

using Microsoft.AspNetCore.Components;
public abstract class ComponentBase
  : IComponent, IHandleAfterRender, IHandleEvent
{
  // Members not shown.
}

ComponentBase 有一些有用的方法,您可以调用和重写,如表 14.1 所示:

方法(们)

描述

InvokeAsync

调用此方法以在关联渲染器的同步上下文中执行函数。

OnAfterRender,

OnAfterRenderAsync

重写这些方法以在组件每次渲染后调用代码。

OnInitialized,

OnInitializedAsync

重写这些方法以在组件从其父级的渲染树接收到初始参数后调用代码。

OnParametersSet,

OnParametersSetAsync

重写这些方法以在组件接收到参数并将值分配给属性后调用代码。

ShouldRender

重写此方法以指示组件是否应渲染。

StateHasChanged

调用此方法以使组件重新渲染。

表 14.1:在 ComponentBase 中重写的有用方法

正如您在第 13 章《使用 ASP.NET Core 构建网站》中看到的,Blazor 组件可以具有共享布局。您需要创建一个 .razor 组件文件,并明确继承自 LayoutComponentBase ,如下所示的标记:

@inherits LayoutComponentBase
... @Body ...

基类有一个名为 Body 的属性,您可以在布局中的正确位置渲染该属性。

您可以在 App.razor 文件及其 Router 组件中设置组件的默认布局。要为组件显式设置布局,请使用 @layout 指令,如以下标记所示:

@page "/customers"
@layout AlternativeLayout
...

运行 Blazor Web 应用程序项目模板

现在我们已经审查了项目模板以及特定于 Blazor 服务器的重要部分,我们可以启动网站并查看其行为:

  1. Northwind.Blazor 项目中,在 Properties 文件夹内,在 launchSettings.json ,对于 https 配置文件,将 applicationUrl 修改为使用端口 5141 进行 HTTPS 和端口 5140 进行 HTTP,如下标记中所示的高亮部分:
"applicationUrl": "https://localhost:5141;http://localhost:5140",
  1. 使用 https 启动配置开始 Northwind.Blazor 项目。
  2. 启动 Chrome 并导航到 https://localhost:5141/
  3. 在左侧导航菜单中,点击天气,然后点击“点击我”按钮三次,注意标签显示为 3,如图 14.1 所示:
  1. 在左侧导航菜单中,点击天气,注意到加载…消息出现了半秒钟,然后被五个随机天气预报的表格替代,如图 14.2 所示:
  1. 关闭 Chrome 并关闭网络服务器。

现在您已经查看了 Blazor Web 应用程序项目模板中的示例 Blazor 组件,让我们深入研究并构建我们自己的组件。

使用 Blazor 构建组件

在本节中,我们将构建一个组件,以列出、创建和编辑北风数据库中的客户。

我们将分几个步骤来构建它:

  1. 制作一个 Blazor 组件,该组件渲染作为参数设置的国家名称。
  2. 使其既能作为可路由页面,又能作为组件工作。
  3. 实现对数据库中客户进行 CRUD(创建、读取、更新和删除)操作的功能。

定义和测试一个简单的 Blazor 组件

我们将把新组件添加到现有的 Blazor Web 应用项目中:

  1. Northwind.Blazor 项目中,在 Components\Pages 文件夹中,添加一个名为 Customers.razor 的新文件。在 Visual Studio 中,项目项模板名为 Razor 组件。在 Rider 中,项目项模板名为 Blazor 组件。

良好实践:请记住,Blazor 组件文件名必须以大写字母开头;否则,您将会遇到编译错误!

  1. 添加语句以输出 Customers 组件的标题,并定义一个代码块来定义一个属性以存储国家名称,如以下标记中突出显示的那样:

Customers @(string.IsNullOrWhiteSpace(Country) ? "Worldwide" : "in " + Country)

@code { [Parameter] public string? Country { get; set; } }

@code 块可以在文件中的任何位置。一些开发者更喜欢将其放在顶部,以便在下面输入标记时可以使用其中定义的任何属性,并且可以更轻松地引用它们。

  1. Components\Pages 文件夹中,在 Home.razor ,在文件底部添加语句以实例化 Customers 组件两次,一次将 Germany 设置为 Country 参数,另一次不设置国家,如下标记所示:

  1. 使用 https 启动配置开始 Northwind.Blazor 项目。
  2. 启动 Chrome,导航到 https://localhost:5141/ ,并注意 Customers 组件,如图 14.3 所示:
  1. 关闭 Chrome 并关闭网络服务器。

使用 Bootstrap 图标

在较早的 Blazor 项目模板中,使用 .NET 7 及更早版本时,包含了所有 Bootstrap 图标。在新的项目模板中,使用 .NET 8 及更高版本时,仅定义了三个使用 SVG 的图标。让我们看看 Blazor 团队是如何定义这些图标的,然后我们将为自己的使用添加更多图标:

  1. Components\Layout 文件夹中,在 NavMenu.razor.css 中,找到文本 bi-house ,并注意使用 SVG 定义的三个图标,如以下代码所示的部分内容:
.bi-house-door-fill-nav-menu {
    background-image: url("data:image/svg+xml,...");
}
.bi-plus-square-fill-nav-menu {
    background-image: url("data:image/svg+xml,...");
}
.bi-list-nested-nav-menu {
    background-image: url("data:image/svg+xml,...");
}
  1. 在您喜欢的浏览器中,导航到 https://icon-sets.iconify.design/bi/,请注意 Bootstrap 图标具有 MIT 许可证,并包含超过 2,000 个图标。
  2. 网页上有两个输入框,一个标记为搜索图标,另一个标记为过滤图标。在过滤图标框中输入 globe ,并注意到找到了六个地球图标。
  3. 点击第一个地球仪,在 CSS 部分,点击 CSS 按钮,并注意您可以点击“复制到剪贴板”按钮以复制并粘贴此图标的定义以便在 CSS 样式表中使用;但是,您不需要这样做,因为我已经为您创建了一个 CSS 文件,其中定义了五个图标供您在 Blazor 项目中使用。
  4. 在您喜欢的浏览器中,导航到 https://github.com/markjprice/cs13net9/blob/main/code/ModernWeb/Northwind.Blazor/Northwind.Blazor/wwwroot/icons.css,下载该文件,并将其保存在您自己项目的 wwwroot 文件夹中。
  5. Components 文件夹中,在 App.razor 组件中,在 中,添加一个 元素以引用 icons.css 样式表,如下所示的标记:
  1. 保存并关闭文件。

将组件制作成可路由的页面组件

将此组件转换为具有国家路由参数的可路由页面组件非常简单:

  1. Components\Pages 文件夹中,在 Customers.razor 组件的顶部添加一条语句,将 /customers 注册为其路由,并带有可选的 country 路由参数,如下所示:
@page "/customers/{country?}"

Components\Layout 文件夹中,在 NavMenu.razor ,在现有列表项元素的底部,添加两个列表项元素,用于我们的可路由页面组件,向全球和德国的客户展示,两个都使用一个人物图标,如以下标记所示:


  1. Components\Pages 文件夹中,在 Home.razor ,删除两个 组件,因为我们可以从现在开始使用它们的导航菜单项进行测试,并且我们希望保持主页尽可能简单。
  2. 使用 https 启动配置开始 Northwind.Blazor 项目。
  3. 启动 Chrome 并导航到 https://localhost:5141/
  4. 在左侧导航菜单中,点击德国客户。请注意,国家名称正确传递到页面组件,并且该组件使用与其他页面组件相同的布局,如 Home.razor 。还请注意 URL,https://localhost:5141/customers/Germany,如图 14.4 所示:
  1. 关闭 Chrome 并关闭网络服务器。

将实体引入 Blazor 组件

现在您已经看到了 Blazor 页面组件的最小实现,我们可以为其添加一些有用的功能。在这种情况下,我们将使用 Northwind 数据库上下文从数据库中获取客户:

  1. Northwind.Blazor.csproj 中,为 SQL Server 或 SQLite 添加对 Northwind 数据库上下文项目的引用,并全局导入命名空间以便与 Northwind 实体一起使用,如下所示的标记:

  
  


  
  1. 警告!数据上下文项目的相对路径比当前项目高两个目录,因此我们必须使用 ..\..\
  1. 构建 Northwind.Blazor 项目。
  2. Program.cs 中,在调用 Build 之前,添加一条语句以在依赖服务集合中注册 Northwind 数据库上下文,如以下代码所示:
builder.Services.AddNorthwindContext(
  relativePath: @"..\..");

我们需要明确设置 relativePath ,因为 Northwind.db 文件位于 ModernWeb 文件夹中,而项目在 ModernWeb\Northwind.Blazor\Northwind.Blazor 文件夹中运行。

为 Blazor 组件抽象服务

我们可以实现 Blazor 组件,使其直接调用 Northwind 数据库上下文,通过实体模型获取客户。如果 Blazor 组件在服务器上执行,这样是可行的。然而,如果组件在浏览器中使用 WebAssembly 运行,则无法实现。

我们现在将创建一个本地依赖服务,以便更好地重用组件:

  1. 使用您首选的编码工具添加一个新项目,如下列表所定义:项目模板:类库 / classlib项目文件和文件夹: Northwind.Blazor.Services解决方案文件和文件夹: ModernWeb
  2. Northwind.Blazor.Services.csproj 项目文件中,添加对 Northwind 实体模型库的项目引用,如下标记所示:

  
  
  1. 构建 Northwind.Blazor.Services 项目。
  2. Northwind.Blazor.Services 项目中,将 Class1.cs 重命名为 INorthwindService.cs
  3. INorthwindService.cs 中,定义一个抽象 CRUD 操作的本地服务合同,如下代码所示:
using Northwind.EntityModels; // To use Customer.
namespace Northwind.Blazor.Services;
public interface INorthwindService
{
  Task> GetCustomersAsync();
  Task> GetCustomersAsync(string country);
  Task GetCustomerAsync(string id);
  Task CreateCustomerAsync(Customer c);
  Task UpdateCustomerAsync(Customer c);
  Task DeleteCustomerAsync(string id);
}

Northwind.Blazor.csproj 项目文件中,添加对服务类库的项目引用,如下标记中所示的高亮部分:


  
  
  
  1. 构建 Northwind.Blazor 项目。
  2. Northwind.Blazor 项目中,添加一个名为 Services 的新文件夹。
  3. Services 文件夹中,添加一个名为 NorthwindServiceServerSide.cs 的新文件,并修改其内容以使用 Northwind 数据库上下文实现 INorthwindService 接口,如下代码所示:
using Microsoft.EntityFrameworkCore; // To use ToListAsync.
namespace Northwind.Blazor.Services;
public class NorthwindServiceServerSide : INorthwindService
{
  private readonly NorthwindContext _db;
  public NorthwindServiceServerSide(NorthwindContext db)
  {
    _db = db;
  }
  public Task> GetCustomersAsync()
  {
    return _db.Customers.ToListAsync();
  }
  public Task> GetCustomersAsync(string country)
  {
    return _db.Customers.Where(c => c.Country == country).ToListAsync();
  }
  public Task GetCustomerAsync(string id)
  {
    return _db.Customers.FirstOrDefaultAsync
      (c => c.CustomerId == id);
  }
  public Task CreateCustomerAsync(Customer c)
  {
    _db.Customers.Add(c);
    _db.SaveChangesAsync();
    return Task.FromResult(c);
  }
  public Task UpdateCustomerAsync(Customer c)
  {
    _db.Entry(c).State = EntityState.Modified;
    _db.SaveChangesAsync();
    return Task.FromResult(c);
  }
  public Task DeleteCustomerAsync(string id)
  {
    Customer? customer = _db.Customers.FirstOrDefaultAsync
      (c => c.CustomerId == id).Result;
    if (customer == null)
    {
      return Task.CompletedTask;
    }
    else
    {
      _db.Customers.Remove(customer);
      return _db.SaveChangesAsync();
    }
  }
}

Program.cs 中,导入我们服务的命名空间,如下代码所示:

using Northwind.Blazor.Services; // To use INorthwindService.

Program.cs 中,在调用 Build 之前,添加一条语句以将
NorthwindServiceServerSide
注册为实现 INorthwindService 接口的瞬态服务,如以下代码所示:

builder.Services.AddTransient();

瞬态服务是为每个请求创建新实例的服务。您可以在以下链接中阅读有关服务的不同生命周期的更多信息:https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes。

  1. Components 文件夹中,在 _Imports.razor ,导入命名空间以便与 Northwind 实体和我们的服务一起工作,这样我们构建的 Blazor 组件就不需要单独导入命名空间,如以下标记所示:
@using Northwind.Blazor.Services @* To use INorthwindService. *@
@using Northwind.EntityModels @* To use Northwind entities. *@

_Imports.razor 文件仅适用于 .razor 文件。如果您使用代码隐藏 .cs 文件来实现组件代码,则必须单独导入命名空间或使用全局 usings 来隐式导入命名空间。

  1. Components\Pages 文件夹中,在 Customers.razor 中,添加语句以注入服务,然后使用它输出所有客户的表格,使用同步数据库操作,如以下代码中突出显示的内容所示:
@page "/customers/{country?}"
@inject INorthwindService _service

Customers @(string.IsNullOrWhiteSpace(Country) ? "Worldwide" : "in " + Country)

@if (customers is null) {

Loading...

} else { @foreach (Customer c in customers) { }
Id Company Name Address Phone
@c.CustomerId @c.CompanyName @c.Address
@c.City
@c.PostalCode
@c.Country
@c.Phone
} @code { [Parameter] public string? Country { get; set; } private IEnumerable? customers; protected override async Task OnParametersSetAsync() { if (string.IsNullOrWhiteSpace(Country)) { customers = await _service.GetCustomersAsync(); } else { customers = await _service.GetCustomersAsync(Country); } } }
  1. 使用 https 启动配置开始 Northwind.Blazor 项目。
  2. 启动 Chrome 并导航到 https://localhost:5141/
  3. 在左侧导航菜单中,点击德国客户,注意客户表格从数据库加载并在网页中呈现,如图 14.5 所示:
  1. 在浏览器地址栏中,将 Germany 更改为 UK ,并注意客户表仅显示英国客户。
  2. 在左侧导航菜单中,点击全球客户,注意客户表格未按国家过滤。
  3. 点击任何编辑或删除按钮,注意它们返回一条消息,显示错误:404,因为我们尚未实现该功能。同时,请注意编辑由五个字符标识符 ALFKI 确定的客户的链接,如下所示: https://localhost:5141/editcustomer/ALFKI
  4. 关闭 Chrome 并关闭网络服务器。