ASP.NET Web Forms 的优势之一在于其能够将可重用的用户界面 (UI) 代码片段封装到可重用的 UI 控件中。 可以使用 .ascx 文件在标记中定义自定义用户控件。 还可以在代码中生成具有全面设计器支持的复杂服务器控件。
Blazor 还支持通过组件进行 UI 封装。 组件具有以下特点:
是自包含的 UI 块。
维护自己的状态和呈现逻辑。
可以定义 UI 事件处理程序、绑定到输入数据以及管理其自己的生命周期。
通常使用 Razor 语法在 .razor 文件中定义。
Razor 简介
Razor 是基于 HTML 和 C# 的轻量级标记模板化语言。 借助 Razor,可以在标记和 C# 代码之间无缝转换,以定义组件呈现逻辑。 编译 .razor 文件时,将在 .NET 类中以结构化方式捕获呈现逻辑。 编译类的名称从 .razor 文件名中获取。 命名空间从项目的默认命名空间和文件夹路径中获取,也可以使用
@namespace
指令(在下面的 Razor 指令中详细介绍)显式指定命名空间。
组件的呈现逻辑使用常规 HTML 标记创作,并使用 C# 添加动态逻辑。
@
字符用于转换为 C#。 Razor 通常很聪明,可猜出你何时切换回 HTML。 例如,以下组件使用当前时间呈现
<p>
标记:
<p>@DateTime.Now</p>
若要显式指定 C# 表达式的开头和结尾,请使用括号:
<p>@(DateTime.Now)</p>
使用 Razor 还可以在呈现逻辑中轻松地使用 C# 控制流。 例如,可以有条件地呈现某些 HTML,如下所示:
@if (value % 2 == 0)
<p>The value was even.</p>
或者,可以使用常规 C# foreach
循环生成项目列表,如下所示:
@foreach (var item in items)
<li>@item.Text</li>
像 ASP.NET Web Forms 中的指令一样,Razor 指令可控制 Razor 组件编译的许多方面。 示例包括组件的以下项:
实现的接口
导入的命名空间
Razor 指令以 @
字符开头,通常用于文件开头的新行的开头。 例如,@namespace
指令定义组件的命名空间:
@namespace MyComponentNamespace
下表总结 Blazor 中使用的各种 Razor 指令及其 ASP.NET Web Forms 等效项(如果存在)。
Web Forms 等效项
Razor 组件还广泛使用元素上的指令属性来控制组件编译的各个方面(事件处理、数据绑定、组件和元素引用等)。 指令属性均遵循常用泛型语法,其中括号中的值是可选的:
@directive(-suffix(:name))(="value")
下表总结 Blazor 中使用的 Razor 指令的各种属性。
若要将成员添加到 Razor 组件类,请使用 @code
指令。 此方法类似于在 ASP.NET Web Forms 用户控件或页中使用 <script runat="server">...</script>
块。
@code {
int count = 0;
void IncrementCount()
count++;
因为 Razor 基于 C#,所以必须从 C# 项目 (.csproj) 中进行编译。 无法从 Visual Basic 项目 (.vbproj) 编译 .razor 文件。 仍然可以从 Blazor 项目引用 Visual Basic 项目。 反之亦然。
有关完整的 Razor 语法参考,请参阅 ASP.NET Core 的 Razor 语法参考。
除常规 HTML 外,组件还可以将其他组件用作其呈现逻辑的一部分。 用于在 Razor 中使用组件的语法类似于在 ASP.NET Web Forms 应用中使用用户控件。 使用与组件的类型名称匹配的元素标记指定组件。 例如,可以添加 Counter
组件,如下所示:
<Counter />
与 ASP.NET Web Forms 不同,Blazor 中的组件:
不使用元素前缀(例如,asp:
)。
不要求在页上或在 web.config 中进行注册。
将 Razor 组件视为 .NET 类型,因为这正是其本质。 如果引用了包含组件的程序集,则该组件可供使用。 若要将组件的命名空间纳入范围,请应用 @using
指令:
@using MyComponentLib
<Counter />
如默认的 Blazor 项目所示,通常将 @using
指令放入 _Imports.razor 文件中,以便将它们导入同一目录中和子目录中的所有 .razor 文件。
如果组件的命名空间不在范围内,则可以使用其完整类型名称来指定组件,就像在 C# 中一样:
<MyComponentLib.Counter />
修改组件的页面标题
在生成 SPA 样式的应用时,通常可以在不重新加载整个页面的情况下重新加载页面的某些部分。 尽管如此,根据当前加载的组件,更改页面标题可能会很有用。 这可以通过在组件的 Razor 页面中包含 <PageTitle>
标记来实现:
@page "/"
<PageTitle>Home</PageTitle>
此元素的内容可以是动态的,例如显示消息的当前计数:
<PageTitle>@MessageCount messages</PageTitle>
请注意,如果特定页面上的多个组件包含 <PageTitle>
标记,则只会显示最后一个(因为每个组件都将覆盖前一个)。
在 ASP.NET Web Forms 中,可以使用公共属性将参数和数据传递到控件。 这些属性可以使用特性在标记中进行设置,也可以直接在代码中设置。 Razor 组件以类似的方式工作,尽管组件属性还必须使用 [Parameter]
特性进行标记才能被视为组件参数。
以下 Counter
组件定义名为 IncrementAmount
的组件参数,该参数可用于指定每次单击按钮时 Counter
应该递增的数量。
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
int currentCount = 0;
[Parameter]
public int IncrementAmount { get; set; } = 1;
void IncrementCount()
currentCount+=IncrementAmount;
若要在 Blazor 中指定组件参数,请像在 ASP.NET Web Forms 中一样使用特性:
<Counter IncrementAmount="10" />
查询字符串参数
Razor 组件也可以利用它们所呈现的页面的查询字符串的值作为参数源。 若要启用此参数,请将 [SupplyParameterFromQuery]
属性添加到参数中。 例如,下面的参数定义将以 ?IncBy=2
格式从请求中获取其值:
[Parameter]
[SupplyParameterFromQuery(Name = "IncBy")]
public int IncrementAmount { get; set; } = 1;
如果未在 [SupplyParameterFromQuery]
属性中提供自定义 Name
,则默认情况下它将匹配属性名称(在这种情况下为 IncrementAmount
)。
组件和错误边界
默认情况下,Blazor 应用将检测未处理的异常,并在页面底部显示错误消息,无其他详细信息。 若要限制受未处理错误影响的应用的某些部分,例如将影响限制在单个组件,可以将 <ErrorBoundary>
标记包装在组件声明周围。
例如,若要防止从 Counter
组件引发的可能异常,请在 <ErrorBoundary>
中声明它,并且可以选择指定在出现异常时显示的消息:
<ErrorBoundary>
<ChildContent>
<Counter />
</ChildContent>
<ErrorContent>
Oops! The counter isn't working right now; please try again later.
</ErrorContent>
</ErrorBoundary>
如果不需要指定自定义错误内容,则只需直接包装组件:
<ErrorBoundary>
<Counter />
</ErrorBoundary>
如果包装组件中发生未处理的异常,将显示指示“出现错误”的默认消息。
事件处理程序
ASP.NET Web Forms 和 Blazor 均提供用于处理 UI 事件的基于事件的编程模型。 此类事件的示例包括按钮单击和文本输入。 在 ASP.NET Web Forms 中,可以使用 HTML 服务器控件处理 DOM 公开的 UI 事件,也可以处理 Web 服务器控件公开的事件。 这些事件通过表单回发请求在服务器上显示。 考虑以下 Web Forms 按钮单击示例:
Counter.ascx
<asp:Button ID="ClickMeButton" runat="server" Text="Click me!" OnClick="ClickMeButton_Click" />
Counter.ascx.cs
public partial class Counter : System.Web.UI.UserControl
protected void ClickMeButton_Click(object sender, EventArgs e)
Console.WriteLine("The button was clicked!");
在 Blazor 中,可以直接使用 @on{event}
形式的指令属性为 DOM UI 事件注册处理程序。 {event}
占位符表示事件的名称。 例如,可侦听按钮单击,如下所示:
<button @onclick="OnClick">Click me!</button>
@code {
void OnClick()
Console.WriteLine("The button was clicked!");
事件处理程序可以接受可选的事件特定参数,以提供有关事件的详细信息。 例如,鼠标事件可以接受 MouseEventArgs
参数,但这不是必需的。
<button @onclick="OnClick">Click me!</button>
@code {
void OnClick(MouseEventArgs e)
Console.WriteLine($"Mouse clicked at {e.ScreenX}, {e.ScreenY}.");
可以使用 lambda 表达式来代替引用事件处理程序的方法组。 借助 lambda 表达式,可以覆盖其他范围内的值。
@foreach (var buttonLabel in buttonLabels)
<button @onclick="() => Console.WriteLine($"The {buttonLabel} button was clicked!")">@buttonLabel</button>
事件处理程序可以同步或异步执行。 例如,以下 OnClick
事件处理程序异步执行:
<button @onclick="OnClick">Click me!</button>
@code {
async Task OnClick()
var result = await Http.GetAsync("api/values");
处理事件后,将呈现组件以解释任何组件状态更改。 使用异步事件处理程序时,组件将在处理程序执行完成后立即呈现。 异步 Task
完成后,将再次呈现组件。 在异步 Task
仍在执行时,此异步执行模式提供呈现部分相应 UI 的机会。
<button @onclick="ShowMessage">Get message</button>
@if (showMessage)
@if (message == null)
<p><em>Loading...</em></p>
<p>The message is: @message</p>
@code
bool showMessage = false;
string message;
public async Task ShowMessage()
showMessage = true;
message = await MessageService.GetMessageAsync();
组件还可以通过定义类型为 EventCallback<TValue>
的组件参数来定义自己的事件。 事件回叫支持 DOM UI 事件处理程序的所有变体:可选参数、同步或异步、方法组或 lambda 表达式。
<button class="btn btn-primary" @onclick="OnClick">Click me!</button>
@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
Blazor 提供将 UI 组件中的数据绑定到组件状态的简单机制。 此方法与 ASP.NET Web Forms 中用于将数据源中的数据绑定到 UI 控件的功能不同。 我们将在处理数据部分中介绍如何处理来自不同数据源的数据。
若要创建从 UI 组件到组件状态的双向数据绑定,请使用 @bind
指令属性。 在下面的示例中,复选框的值绑定到 isChecked
字段。
<input type="checkbox" @bind="isChecked" />
@code {
bool isChecked;
呈现组件时,复选框的值将设置为 isChecked
字段的值。 当用户切换复选框时,将触发 onchange
事件,且 isChecked
字段将设置为新值。 在这种情况下,@bind
语法等效于以下标记:
<input value="@isChecked" @onchange="(UIChangeEventArgs e) => isChecked = e.Value" />
若要更改用于绑定的事件,请使用 @bind:event
属性。
<input @bind="text" @bind:event="oninput" />
<p>@text</p>
@code {
string text;
组件还可以支持将数据绑定到其参数。 若要进行数据绑定,请定义与可绑定参数同名的事件回叫参数。 名称会添加“Changed”后缀。
PasswordBox.razor
Password: <input
value="@Password"
@oninput="OnPasswordChanged"
type="@(showPassword ? "text" : "password")" />
<label><input type="checkbox" @bind="showPassword" />Show password</label>
@code {
private bool showPassword;
[Parameter]
public string Password { get; set; }
[Parameter]
public EventCallback<string> PasswordChanged { get; set; }
private Task OnPasswordChanged(ChangeEventArgs e)
Password = e.Value.ToString();
return PasswordChanged.InvokeAsync(Password);
若要将数据绑定链接到基础 UI 元素,请设置值并直接在 UI 元素上处理事件,而不要使用 @bind
属性。
若要绑定到组件参数,请使用 @bind-{Parameter}
属性指定要绑定到的参数。
<PasswordBox @bind-Password="password" />
@code {
string password;
如果组件的状态已在常规 UI 事件或事件回叫之外更改,则组件必须手动发出需要再次呈现的信号。 若要发出组件状态已更改的信号,请在组件上调用 StateHasChanged
方法。
在下面的示例中,组件显示来自 AppState
服务的消息(可由应用的其他部分更新)。 组件向 AppState.OnChange
事件注册其 StateHasChanged
方法,以便每次消息更新时呈现该组件。
public class AppState
public string Message { get; }
// Lets components receive change notifications
public event Action OnChange;
public void UpdateMessage(string message)
Message = message;
NotifyStateChanged();
private void NotifyStateChanged() => OnChange?.Invoke();
@inject AppState AppState
<p>App message: @AppState.Message</p>
@code {
protected override void OnInitialized()
AppState.OnChange += StateHasChanged
组件生命周期
ASP.NET Web Forms 框架为模块、页和控件提供定义完善的生命周期方法。 例如,以下控件为 Init
、Load
和 UnLoad
生命周期事件实现事件处理程序:
Counter.ascx.cs
public partial class Counter : System.Web.UI.UserControl
protected void Page_Init(object sender, EventArgs e) { ... }
protected void Page_Load(object sender, EventArgs e) { ... }
protected void Page_UnLoad(object sender, EventArgs e) { ... }
Razor 组件也具有定义完善的生命周期。 组件的生命周期可用于初始化组件状态及实现高级组件行为。
Blazor 的所有组件生命周期方法都具有同步和异步版本。 组件呈现是同步的。 不能在组件呈现期间运行异步逻辑。 所有异步逻辑都必须作为 async
生命周期方法的一部分执行。
OnInitialized
OnInitialized
和 OnInitializedAsync
方法用于初始化组件。 组件通常在首次呈现后初始化。 组件初始化后,可能会在最终释放前呈现多次。 OnInitialized
方法类似于 ASP.NET Web Forms 页和控件中的 Page_Load
事件。
protected override void OnInitialized() { ... }
protected override async Task OnInitializedAsync() { await ... }
当组件已从其父级接收参数并将值分配给属性时,将调用 OnParametersSet
和 OnParametersSetAsync
方法。 这些方法在组件初始化后以及每次呈现组件时执行。
protected override void OnParametersSet() { ... }
protected override async Task OnParametersSetAsync() { await ... }
OnAfterRender
OnAfterRender
和 OnAfterRenderAsync
方法在组件完成呈现后调用。 此时,将填充元素和组件引用(在下文中详细介绍这些概念)。 此时已启用与浏览器的交互性功能。 与 DOM 和 JavaScript 执行的交互可以安全地进行。
protected override void OnAfterRender(bool firstRender)
if (firstRender)
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender)
await ...
在服务器上进行预呈现时,不调用 OnAfterRender
和 OnAfterRenderAsync
。
firstRender
参数在首次呈现组件时为 true
;否则,其值为 false
。
IDisposable
从 UI 中移除 Razor 组件时,该组件可以实现 IDisposable
来释放资源。 Razor 组件可以通过使用 @implements
指令来实现 IDispose
:
@using System
@implements IDisposable
@code {
public void Dispose()
捕获组件引用
在 ASP.NET Web Forms 中,通常通过引用控件实例 ID 直接在代码中操作控件实例。 在 Blazor 中,也可以捕获和操作对组件的引用,尽管这种做法不太常见。
若要在 Blazor 中捕获组件引用,请使用 @ref
指令属性。 属性的值应该匹配与引用的组件具有相同类型的可设置字段的名称。
<MyLoginDialog @ref="loginDialog" ... />
@code {
MyLoginDialog loginDialog = default!;
void OnSomething()
loginDialog.Show();
呈现父组件时,将使用子组件实例填充该字段。 然后,可以在组件实例上调用方法,或以其他方式操作组件实例。
不建议直接使用组件引用来操作组件状态。 这样做会阻止组件在正确的时间自动呈现。
捕获元素引用
Razor 组件可以捕获对元素的引用。 与 ASP.NET Web Forms 中的 HTML 服务器控件不同,不能在 Blazor 中直接使用元素引用来操作 DOM。 Blazor 使用其 DOM 比较算法处理大部分 DOM 交互。 Blazor 中捕获的元素引用是不透明的。 但是,它们用于在 JavaScript 互操作调用中传递特定元素引用。 有关 JavaScript 互操作的详细信息,请参阅 ASP.NET Core Blazor JavaScript 互操作。
模板化组件
在 ASP.NET Web Forms 中,可以创建模板化控件。 模板化控件使开发人员可以指定用于呈现容器控件的一部分 HTML。 生成模板化服务器控件的机制很复杂,但它们提供了功能强大的方案,可用于以用户可自定义的方式呈现数据。 模板化控件的示例包括 Repeater
和 DataList
。
还可以通过定义类型为 RenderFragment
或 RenderFragment<T>
的组件参数来模板化 Razor 组件。 RenderFragment
表示可随后由组件呈现的 Razor 标记块。 RenderFragment<T>
是 Razor 标记块,其接受可在呈现片段时指定的参数。
Razor 组件可以将其子内容作为 RenderFragment
进行捕获,并将该内容作为组件呈现的一部分呈现。 若要捕获子内容,请定义类型为 RenderFragment
的组件参数并将其命名为 ChildContent
。
ChildContentComponent.razor
<h1>Component with child content</h1>
<div>@ChildContent</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
然后,父组件可以使用常规 Razor 语法提供子内容。
<ChildContentComponent>
<ChildContent>
<p>The time is @DateTime.Now</p>
</ChildContent>
</ChildContentComponent>
模板化 Razor 组件还可以定义类型为 RenderFragment
或 RenderFragment<T>
的多个组件参数。 RenderFragment<T>
的参数可以在调用时指定。 若要为组件指定泛型类型参数,请使用 @typeparam
Razor 指令。
SimpleListView.razor
@typeparam TItem
@Heading
@foreach (var item in Items)
<li>@ItemTemplate(item)</li>
@code {
[Parameter]
public RenderFragment Heading { get; set; }
[Parameter]
public RenderFragment<TItem> ItemTemplate { get; set; }
[Parameter]
public IEnumerable<TItem> Items { get; set; }
在使用模板化组件时,可以使用与参数名称匹配的子元素来指定模板参数。 作为元素传递的类型为 RenderFragment<T>
的组件参数有一个名为 context
的隐式参数。 可以使用子元素上的 Context
属性来更改此实现参数的名称。 可以使用与类型参数名称匹配的属性来指定任何泛型类型参数。 如果可能,将推断类型参数:
<SimpleListView Items="messages" TItem="string">
<Heading>
<h1>My list</h1>
</Heading>
<ItemTemplate Context="message">
<p>The message is: @message</p>
</ItemTemplate>
</SimpleListView>
此组件的输出如下所示:
<h1>My list</h1>
<li><p>The message is: message1</p></li>
<li><p>The message is: message2</p></li>
Razor 组件通常是在单个 .razor 文件中创作的。 但也可以使用代码隐藏文件来分隔代码和标记。 若要使用组件文件,请添加与组件文件的文件名匹配,但添加了 .cs 扩展名的 C# 文件 (Counter.razor.cs)。 使用 C# 文件定义组件的基类。 可以根据需要命名基类,但通常将基类命名为与组件类相同的名称,并添加 Base
扩展名 (CounterBase
)。 基于组件的类也必须从 ComponentBase
中派生。 然后,在 Razor 组件文件中,添加 @inherits
指令以指定组件的基类 (@inherits CounterBase
)。
Counter.razor
@inherits CounterBase
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button @onclick="IncrementCount">Click me</button>
Counter.razor.cs
public class CounterBase : ComponentBase
protected int currentCount = 0;
protected void IncrementCount()
currentCount++;
组件成员在基类中的可见性必须为 protected
或 public
才能对组件类可见。
前面的内容并不是对 Razor 组件的所有方面的详尽介绍。 有关如何创建和使用 ASP.NET Core Razor 组件的详细信息,请参阅 Blazor 文档。
上一页下一页