Blazor 模板化组件开发指南
翻译自 Waqas Anwar 2021年4月15日的文章 《A Developer’s Guide To Blazor Templated Components》 [1]

在我之前的一篇文章 Blazor 组件入门指南中,我介绍了组件参数,并向您展示了如何将数据作为参数传递给 Blazor 组件以定制化其功能。在这篇文章中,我将更进一步向您展示,如何将一个或多个 UI 模板作为参数传递给一个称之为模板化组件的不同类型的 Blazor 组件。
Blazor 模板化组件概述
Blazor 模板化组件是一种接受将一个或多个 UI 模板作为参数的组件。这有助于组件的可重用性,因为您只需要创建一次模板化组件,然后使用该组件的每个页面都可以提供其 UI 模板,模板化组件可以根据页面需求渲染此 UI 模板。

本文中的模板化组件示例包括:
- 一个允许用户指定表格表头、行和页脚模板的表格组件。
- 一个允许用户呈现具有相同外观和体验而具有不同内容的小部件组件。
- 一个允许用户指定一个模板来呈现项目符号或编号等列表项的列表组件。
- 一个允许用户以列表、网格或卡片视图来显示数据的列表组件。
当我们创建 Blazor 组件的一个参数时,我们通常将其类型指定为 string、int 或者其他内置 .NET 数据类型。为了创建一个模板化组件,我们需要创建类型为 RenderFragment 或 RenderFragment<T> 的组件参数。RenderFragment 允许我们提供一个可以由模板化组件渲染的 UI 内容片段(作为一个委托实现,将其内容写入到 RenderTreeBuilder)。
[Parameter]
public RenderFragment HeaderTemplate { get; set; }
RenderFragment<T> 更进一步,允许我们传入参数的类型 T,可以用它来自定义模板化组件的输出。
[Parameter]
public RenderFragment<T> RowTemplate { get; set; }
从一个实例开始
为了详细了解模板化组件,我决定构建一个 TableWidget 模板化组件,它允许我们自定义不同格式的表头、行和页脚。在创建第一个模板化组件之前,我们先来创建一个新的 Blazor Server 应用程序并添加其基本功能,以表格格式呈现一些数据。
在 Blazor Server 应用程序中创建一个 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
public 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 文件中注册上述服务。如果您想了解关于依赖注入的更多知识,可以阅读我的文章 A Step by Step Guide to ASP.NET Core Dependency Injection[3]。
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 方法来填充我们的本地 Orders 和 Products 列表。
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<TItem>,它允许用户使用任意的 .NET 类型渲染 UI 模板。该类型不是固定的,而是使用组件顶部的 @typeparam 指令声明为一个泛型类型。
@typeparam TItem
我们还在组件中创建了一个 TItem 对象的集合,以便我们可以迭代该集合生成表格的行。
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
我们将要传入 UI 模板中的 TItem 类型的对象会使用以下 foreach 循环进行渲染。您很快就会看到这将如何帮助我们使用相同的 TableWidget 组件同时渲染产品和订单表格。
<tbody>
@foreach (var item in Items)
{
<tr>
@RowTemplate(item)
</tr>
}
</tbody>
使用 Blazor 模板化组件的不同方式
现在是时候来实践一下我们的 TableWidget 组件了,我们可以通过不同的方式使用这个组件。用下面的 TableWidget 组件替换我们前面生成的 Recent Orders 表格。
<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 来生成表格的表头和数据行。您可能在想 context 是从哪里来的?context 是一个隐式参数,所有类型为 RenderFragment<T> 的组件参数都可以使用。我们可以使用 context 访问我们正在处理对象的属性。在上面的示例中,context 将向模板提供订单信息。
如果此时您运行项目,会在页面上看到以下两个表格。现在,最近的订单(Recent Orders)表格是使用我们的 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 组件,它们将显示同样的最近订单(Recent Orders)和热销产品(Top Selling Products),但布局略有改变。
<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 组件,并在其中添加以下代码。这次在组件中没有 HTML 标签,在 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 组件生成 bootstrap 列表,那么我们可以使用下面的代码段来实现这一操作。
<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>
运行该项目,您将看到以 bootstrap 列表方式生成的相同产品的列表。

现在,假设您有另一个页面,其中需要使用 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 开发者工具箱中的一个很好的补充,我们可以使用这些组件创建一些令人惊叹的可重用组件。
相关阅读:
- Blazor Server 和 WebAssembly 应用程序入门指南
- Blazor 组件入门指南
- Blazor 数据绑定开发指南
- Blazor 事件处理开发指南
- Blazor 组件之间使用 EventCallback 进行通信
- Blazor 路由及导航开发指南
- Blazor 模板化组件开发指南
https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-templated-components A Developer’s Guide To Blazor Templated Components ︎
https://github.com/ezzylearning/BlazorTemplatedComponentDemo 下载源码 ︎
https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection A Step by Step Guide to ASP.NET Core Dependency Injection ︎
Blazor 模板化组件开发指南的更多相关文章
- Blazor 路由及导航开发指南
翻译自 Waqas Anwar 2021年4月2日的文章 <A Developer's Guide To Blazor Routing and Navigation> [1] 检查传入的请 ...
- [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发
通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...
- javashop组件开发指南
javashop组件开发指南 1. 概念解释 组件:可以理解为是插件,功能点的一个集合. 插件:是指具体的某个功能. 插件桩:是负责调用插件. 事件:是要决定什么时候执行插件 一个组件是由多 ...
- Blazor 组件库开发指南
翻译自 Waqas Anwar 2021年5月21日的文章 <A Developer's Guide To Blazor Component Libraries> [1] Blazor 的 ...
- WPF组件开发之组件的基类
之前在网上看到很多关于组件开发的资料,但真正可以用到框架内的却很少.今天贴出自己做的组件,并适合大部分框架的代码. 组件开发需要先做出组件的基类,然后由其他的各类组件去继承这个基类,下面是组件基类的代 ...
- Android移动应用界面的模板化设计
Android没有像苹果开发那样功能强大的界面开发工具,本身 ADT插件提供的界面编辑能力有限,没办法刻画所有的界面情况:Android的界面xml代码可以进行人工修改,而Iphone的全部在图形界面 ...
- 《进击吧!Blazor!》第一章 5.组件开发
<进击吧!Blazor!>是本人与张善友老师合作的Blazor零基础入门系列视频,此系列能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力. 视频地址:https://s ...
- FreeMarker模板开发指南知识点梳理
freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么? FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生 ...
- 面向UI编程:ui.js 1.0 粗糙版本发布,分布式开发+容器化+组件化+配置化框架,从无到有的艰难创造
时隔第一次被UI思路激励,到现在1.0的粗糙版本发布,掐指一算整整半年了.半年之间,有些细节不断推翻重做,再推翻再重做.时隔今日,终于能先出来个东西了,这个版本很粗糙,主体功能大概能实现了,但是还是有 ...
随机推荐
- 9.11、mysql增量备份和增量恢复介绍
1.增量备份: 增量数据是从上次全量备份之后,更新的新数据,对于mysql来说,binlog日志就是mysql的增量数据: (1)按天进行备份: 周一00点全量备份 周二00点全量备份 ...... ...
- 22.17、heartbeat和drbd整合
1.要确保master-db和slave-db的drbd服务和heartbeat服务都已经停止了: 2.heartbeate设置: 修改master-db和slave-db的'/etc/ha.d/ha ...
- ubuntu 替换某一内核模块
流程 方法一 以下配置仅执行一次,并以 linux kernel 3.13.0 为例 $ cd ~ $ apt-get source linux-source-3.13.0 $ cd linux-3. ...
- 【重学Java】可变参数
可变参数 可变参数[应用] 可变参数介绍 可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了 方法的参数类型已经确定,个数不确定,我们可以使用可变参数 可变参数定义格式 修饰符 ...
- Java核心基础第1篇-走进Java世界
一.Java简介 1.1 Java概述 Java从一开始就以友好的语法.面向对象.内存管理和最棒的跨平台可移植性来吸引程序员. 写一次就可以在所有地方执行( write-once/run-anywhe ...
- 2012年第三届蓝桥杯C/C++程序设计本科B组省赛 取球博弈
2012年第三届蓝桥杯C/C++程序设计本科B组省赛 取球博弈 题目描述 **取球博弈 今盒子里有n个小球,A.B两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并 ...
- Flink进入大厂面试准备,收藏这一篇就够了
1. Flink 的容错机制(checkpoint) Checkpoint机制是Flink可靠性的基石,可以保证Flink集群在某个算子因为某些原因(如 异常退出)出现故障时,能够将整个应用流图的状态 ...
- Thymeleaf模板引擎语法
th:text 用于显示值 th:object 接收后台传来的对象 th:action 提交表单 th:value 绑定值 th:field 绑定 ...
- 5分钟搞定一个网页特效----v客学院技术分享
这是我们v客学院基础班的童鞋都做完的一个超简单炫酷的特效demo,今天我来带大家用最快的速度和最简单的方法制作一个音乐抖动条,大家有兴趣不妨一起来试试~~~~~~~ 做这个demo之前我们得有一些ht ...
- [Vue warn]: “TypeError: Cannot read property ‘slideTo‘ of undefined“
问题: 使用Vue插件swiper,报如下bug: 解决: 报错原因: vue-awesome-swiper下载版本问题 解决: 如果写成下面这样报错: 则加上$ 反之,删除$ 问题解决