Blazor 模板化组件的开发使用指南
在我之前的文章 Blazor 组件初学者指南中,我介绍了组件参数,并向您展示了如何将数据作为参数传递给 Blazor 组件以自定义其功能。在这篇文章中,我将更进一步,向您展示如何将一个或多个 UI 模板作为参数传递到称为模板化组件的不同类型的 Blazor 组件中。
Blazor 模板化组件概述
Blazor 模板化组件是一种接受一个或多个 UI 模板作为参数的组件。这有助于组件的可重用性,因为您只需要创建一次模板化组件,然后使用该组件的每个页面都可以提供其 UI 模板,该模板可以根据页面要求由模板化组件呈现。
模板化组件的示例包括:
- 一个表格组件,它允许用户为表格标题、行和页脚指定模板。
- 一个小部件组件,允许用户呈现具有相同外观和感觉但内容不同的不同小部件。
- 一个列表组件,允许用户指定一个模板来呈现项目符号或数字等列表项。
- 允许用户在列表、网格或卡片视图中显示数据的列表组件
当我们创建任何 Blazor 组件的参数时,我们通常将其类型指定为 string、int 或任何其他内置 .NET 数据类型。要创建模板化组件,我们创建 RenderFragment 或 RenderFragment 类型的组件参数。 RenderFragment 允许我们提供可以由模板化组件呈现的一段 UI。
[Parameter]
public RenderFragment HeaderTemplate { get; set; }
RenderFragment 更进一步,允许我们传递 T 类型的参数,该参数可用于自定义模板化组件的输出。
[Parameter]
public RenderFragment<T> RowTemplate { get; set; }
开始使用真实示例
为了详细了解模板化组件,我决定构建一个 TableWidget 模板化组件,它允许我们自定义不同格式的表头、行和页脚。在创建第一个模板化组件之前,让我们创建一个新的 Blazor 服务器应用程序并添加基本功能以呈现表格格式的一些数据。
在 Blazor Server App 中创建一个 Data 文件夹,并在 Data 文件夹中添加以下两个模型类。
Product.cs
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
Order.cs
public class Order
{
public int Id { get; set; }
public string OrderNo { get; set; }
public DateTime OrderDate { get; set; }
public string Status { get; set; }
public decimal OrderTotal { get; set; }
}
在项目中创建一个Services文件夹,在Services文件夹中添加如下IProductService和ProductService。对于本教程,我只是返回一些假数据来生成表。
IProductService.cs
public interface IProductService
{
List<Product> GetTopSellingProducts();
}
ProductService.cs
public class ProductService : IProductService
{
public List<Product> GetTopSellingProducts()
{
return new List<Product>()
{
new Product()
{
Id = 1,
Title = "Wireless Mouse",
Price = 29.99m,
Quantity = 3
},
new Product()
{
Id = 2,
Title = "HP Headphone",
Price = 79.99m,
Quantity = 4
},
new Product()
{
Id = 3,
Title = "Sony Keyboard",
Price = 119.99m,
Quantity = 5
}
};
}
}
接下来,在同一个 Services 文件夹中创建 IOrderService 和 OrderService 并添加一些假订单数据以生成一个表。
IOrderService.cs
ublic interface IOrderService
{
List<Order> GetLatestOrders();
}
OrderService.cs
public class OrderService : IOrderService
{
public List<Order> GetLatestOrders()
{
return new List<Order>()
{
new Order()
{
Id = 1,
OrderNo = "12345",
OrderDate = DateTime.Today.AddDays(-2),
Status = "Pending",
OrderTotal = 399.99m
},
new Order()
{
Id = 2,
OrderNo = "67890",
OrderDate = DateTime.Today.AddDays(-5),
Status = "Completed",
OrderTotal = 199.99m
},
new Order()
{
Id = 3,
OrderNo = "13579",
OrderDate = DateTime.Today.AddDays(-7),
Status = "Completed",
OrderTotal = 249.99m
}
};
}
}
我们需要使用依赖注入将上述服务注入到 Blazor 组件中,为此,我们需要在 Startup.cs 文件中注册上述服务。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IProductService, ProductService>();
}
接下来,在项目 Pages 文件夹中创建 Blazor 组件 Dashboard.razor 及其对应的代码隐藏文件 Dashboard.razor.cs。如果您不熟悉 Blazor 组件和代码隐藏文件,请阅读我的文章 Blazor 组件初学者指南。
Dashboard.razor.cs 文件将在组件的代码隐藏文件中注入 IOrderService 和 IProductService,然后我们将使用 GetLatestOrders 和 GetTopSellingProducts 方法来填充我们的本地订单和产品列表。
Dashboard.razor.cs
public partial class Dashboard
{
[Inject]
private IOrderService OrderService { get; set; }
[Inject]
private IProductService ProductService { get; set; }
private List<Order> Orders { get; set; }
private List<Product> Products { get; set; }
protected override void OnInitialized()
{
Orders = OrderService.GetLatestOrders();
Products = ProductService.GetTopSellingProducts();
}
}
razor 组件视图文件将简单地在 Orders 和 Products 上运行 foreach 循环,并将生成 HTML 表。
@page "/dashboard"
<h1>Dashboard</h1>
<br />
<div class="row">
<div class="col">
@if (Orders != null)
{
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Order</th>
<th scope="col">Date</th>
<th scope="col">Status</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
@foreach (var order in Orders)
{
<tr>
<td>@order.OrderNo</td>
<td>@order.OrderDate.ToShortDateString()</td>
<td>@order.Status</td>
<td>@order.OrderTotal</td>
</tr>
}
</tbody>
</table>
}
</div>
<div class="col">
@if (Products != null)
{
<h3>Top Selling Products</h3>
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Title</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
</tr>
</thead>
<tbody>
@foreach (var product in Products)
{
<tr>
<td>@product.Title</td>
<td>@product.Price</td>
<td>@product.Quantity</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
如果您将运行该项目,您将在页面上看到以下两个表格。
到目前为止,我们还没有创建任何模板化组件,但你会觉得我们很快就需要一个,因为上面显示的订单和产品表具有几乎相同的外观和感觉,我们正在上面的 foreach 循环中复制大量 HTML 以生成这两个表。创建一个模板化组件,然后重用该组件来生成上述两个表,并且仍然能够自定义由这些表呈现的标题和数据行,这是一个好主意。让我们创建我们的第一个模板化组件,称为 TableWidget 组件。
创建 Blazor 模板化组件
在 Shared 文件夹中添加一个新的 Razor 组件 TableWidget.razor 并在其中添加以下代码。
TableWidget.razor
@typeparam TItem
<br />
<h3>@Title</h3>
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
@HeaderTemplate
</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
<tr>
@RowTemplate(item)
</tr>
}
</tbody>
<tfoot>
<tr>
@FooterTemplate
</tr>
</tfoot>
</table>
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment HeaderTemplate { get; set; }
[Parameter]
public RenderFragment<TItem> RowTemplate { get; set; }
[Parameter]
public RenderFragment FooterTemplate { get; set; }
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
}
我们的 TableWidget 组件具有以下三个模板。
[Parameter]
public RenderFragment HeaderTemplate { get; set; }
[Parameter]
public RenderFragment<TItem> RowTemplate { get; set; }
[Parameter]
public RenderFragment FooterTemplate { get; set; }
HeaderTemplate 将允许用户在表的标题中呈现任何 UI 模板。此模板用于呈现 thead 元素中的表格标题单元格。
<thead class="thead-dark">
<tr>
@HeaderTemplate
</tr>
</thead>
FooterTemplate 类似于 HeaderTemplate,它允许用户在表格的页脚中呈现任何 UI 模板。此模板用于呈现 tfoot 元素中的表格页脚单元格。
<tfoot>
<tr>
@FooterTemplate
</tr>
</tfoot>
RowTemplate 属于 RanderFragment 类型,它将允许用户使用任何 .NET 类型呈现 UI 模板。该类型不是固定的,而是使用组件顶部的 @typeparam 指令声明为泛型类型。
@typeparam TItem
我们还在组件中创建了一个 TItem 对象的集合,以便我们可以迭代该集合并生成我们的表行
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
我们将在 UI 模板中传递的对象类型将使用以下 foreach 循环呈现。您很快就会看到这将如何帮助我们使用相同的 TableWidget 组件呈现 Products 和 Order 表。
<tbody>
@foreach (var item in Items)
{
<tr>
@RowTemplate(item)
</tr>
}
</tbody>
使用 Blazor 模板化组件的不同方式
现在是时候看看我们的 TableWidget 组件的运行情况了,我们可以通过不同的方式使用这个组件。用下面的 TableWidget 组件替换我们上面生成的最近订单表。
<div class="col">
@if (Orders != null)
{
<TableWidget Title="Recent Orders" Items="Orders">
<HeaderTemplate>
<th scope="col">Order</th>
<th scope="col">Date</th>
<th scope="col">Status</th>
<th scope="col">Total</th>
</HeaderTemplate>
<RowTemplate>
<td>@context.OrderNo</td>
<td>@context.OrderDate.ToShortDateString()</td>
<td>@context.Status</td>
<td>@context.OrderTotal</td>
</RowTemplate>
</TableWidget>
}
</div>
在上面的代码片段中,Items 属性是用我们从我们的服务收到的 Orders 列表初始化的。然后我们决定使用 HeaderTemplate 和 RowTemplate 来生成表格的页眉和页脚。您可能正在思考上下文的来源。上下文是一个隐式参数,可用于类型 RenderFragment 的所有组件参数。我们可以使用上下文来访问我们正在处理的对象的属性。在上面的示例中,上下文将向模板提供订单信息。
如果您将运行该项目,您将在页面上看到以下两个表格。现在使用我们的 TableWidget 组件生成最近的订单表。
让我们重用我们的 TableWidget 组件,这次生成 Top Selling Products 表。这一次,我们传递给它 Products 列表,并且我们还指定了我们自己的 Context="product" 这意味着我们现在可以使用 product 而不是隐式参数 context 来访问产品属性。
<div class="col">
@if (Products != null)
{
<TableWidget Title="Top Selling Products" Items="Products" Context="product">
<HeaderTemplate>
<th scope="col">Title</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
</HeaderTemplate>
<RowTemplate>
<td>@product.Title</td>
<td>@product.Price</td>
<td>@product.Quantity</td>
</RowTemplate>
</TableWidget>
}
</div>
您可以在模板级别指定 Context,如示例中所示,其中 Context=”product” 添加到 RowTemplate
<TableWidget Title="Top Selling Products" Items="Products">
<HeaderTemplate>
<th scope="col">Title</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
</HeaderTemplate>
<RowTemplate Context="product">
<td>@product.Title</td>
<td>@product.Price</td>
<td>@product.Quantity</td>
</RowTemplate>
</TableWidget>
如果您将运行该项目,您将看到页面上呈现了以下两个表,但这次我们知道这两个表是使用我们的 TableWidget 模板化组件呈现的。此示例清楚地表明,相同的模板化组件可用于生成不同类型的 UI,并且它可以根据我们的应用程序需求呈现不同类型的对象。
让我们用另外两个示例重用我们的 TableWidget 组件,这些示例将显示相同的最近订单和热销产品,但布局略有不同
<div class="row">
<div class="col">
@if (Orders != null)
{
<TableWidget Title="Recent Orders" Items="Orders">
<HeaderTemplate>
<th scope="col" colspan="2">Order Details</th>
<th scope="col">Status</th>
<th scope="col">Total</th>
</HeaderTemplate>
<RowTemplate Context="order">
<td colspan="2">
<b>Order No: </b>@order.OrderNo
<br />
<b>Order Date: </b>@order.OrderDate.ToShortDateString()
</td>
<td>@order.Status</td>
<td>@order.OrderTotal</td>
</RowTemplate>
</TableWidget>
}
</div>
<div class="col">
@if (Products != null)
{
<TableWidget Title="Top Selling Products" Items="Products" TItem=”Product”>
<RowTemplate Context="product">
<td>
<h2>@product.Title</h2>
<h4><b>@product.Price.ToString("C")</b></h4>
</td>
</RowTemplate>
<FooterTemplate>
<td class="text-right"><b>Last 30 Days</b></td>
</FooterTemplate>
</TableWidget>
}
</div>
</div>
使用泛型类型组件时,如果可能,将推断类型参数。但是,我们可以选择使用名称与类型参数匹配的属性显式指定类型,在上面的示例中为 TItem。
如果您将运行该项目,您将看到使用相同的 TableWidget 模板化组件在页面上呈现的所有四个表。
创建通用模板组件
我们的 TableWidget 组件很好,我们已经看到了可以重用它的不同示例,但该组件的问题在于它只生成 HTML 表格。如果我们想创建一个更通用的组件,可以重用它来生成任何类型的 UI,例如表格、卡片、项目符号等。我们可以通过从模板化组件中删除所有标记来轻松创建这样的组件。
让我们创建一个通用的 ListWidget 组件来查看一个这样的组件的运行情况。 在 Shared 文件夹中创建一个新的 ListWidget.razor 组件,并在其中添加以下代码。这一次,我们在组件中没有标记,在 foreach 循环中只有一个 ItemTemplate。这意味着我们可以使用这个 ListWidget 组件自由生成任何类型的列表。
ListWidget.razor
@typeparam TItem
@foreach (var item in Items)
{
@ItemTemplate(item)
}
@code {
[Parameter]
public RenderFragment<TItem> ItemTemplate { get; set; }
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
}
假设我们想使用这个 ListWidget 组件生成引导程序列表,所以我们可以使用以下代码片段来做到这一点。
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center active">
Latest Products
</li>
<ListWidget Items="Products" Context="product">
<ItemTemplate>
<li class="list-group-item d-flex justify-content-between align-items-center">
@product.Title
<b>@product.Price.ToString("C")</b>
<span class="badge badge-primary badge-pill">
@product.Quantity
</span>
</li>
</ItemTemplate>
</ListWidget>
</ul>
运行该项目,您将看到现在生成的相同产品列表作为引导列表组件。
现在假设您有另一个页面,其中需要使用 div 和 a 标签以不同方式显示产品列表,因此您可以再次重用相同的 ListWidget 组件,这次生成如下标记:
<div class="list-group">
<a class="list-group-item d-flex justify-content-between align-items-center active">
Latest Products
</a>
<ListWidget Items="Products" Context="product" TItem="Product">
<ItemTemplate>
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1"><b>@product.Title</b></h5>
<small class="text-muted">@product.Quantity units left</small>
</div>
<p class="mb-1">@product.Price.ToString("C")</p>
</a>
</ItemTemplate>
</ListWidget>
</div>
运行该项目,您将看到类似于以下内容的输出。
总结概括
在本教程中,我概述了 Blazor 模板化组件,我们创建了两种类型的模板化组件。接下来,我们看到了几个重用 TableWidget 和 ListWidget 组件来生成不同类型标记的示例。我不得不承认模板化组件是 Blazor 开发人员工具箱的一个很好的补充,使用这些组件我们可以创建一些惊人的可重用组件。
您的资助是我最大的动力!
金额随意,欢迎来赏!