1 准备工作
继续使用上一章项目。
本章展示如何组合Razor组件来创建更复杂的特性。展示如何创建组件之间的父子关系,如何利用属性配置组件,以及如何创建自定义事件,以在发生重要更改时发出信号。还展示了组件如何从父组件接收内容,以及如何使用模板组件一致地生成内容,模板组件可以用一个或多个泛型类型参数定义。在本章结束时,演示了 Blazor应用程序如何对连接和应用程序错误做出反应。
2 结合组件
Blazor 组件可以组合起来创建更复杂的功能。Blazor 文件夹中添加 SelectFilter.razor:
csharp
<div class="form-group">
<label for="select-@Title">@Title</label>
<select name="select-@Title" class="form-control" @bind="SelectedValue">
<option disabled selected>Select @Title</option>
@foreach (string val in Values)
{
<option value="@val" selected="@(val == SelectedValue)">
@val
</option>
}
</select>
</div>
@code
{
public IEnumerable<string> Values { get; set; } = Enumerable.Empty<string>();
public string SelectedValue { get; set; }
public string Title { get; set; } = "Placeholder";
}
该组件呈现现一个 selcect 元素,该元素允许用户选择城市。在 Blazor 文件夹的 PeopleList.razor 中应用此组件替换现有的 select:
csharp
<SelectFilter />
当组件添加到控制器视图或 Razor Pages 所呈现的内容中时,就会使用组件元素,当一个组件添加到另一个组件所呈现的内容中时,该组件的名称将用作一个元素。本例向 PeopleList 组件所呈现的内容添加了 SelectFilter 组件,使用 SelectFilter 元素来实现,大小写必须匹配。
组合组件时,效果是一个组件将其部分布局的职责委托给另一个组件。本例各组成部分构成父/子关系:PcopleList 组件是父组件,SelectFilter 组件是子组件。
请求http:/localhost:5000/controllers
查看效果。
2.1 利用属性配置组件
使用 SelectList 组件的目标是创建一个可在整个应用程序中使用的通用特性,配置每次使用它时显示的值。Razor 组件是用属性配置的,这些属性添加到应用它们的 HTML 元素中。分配给 HTML 元素属性的值被分配给组件的 C# 属性。Parameter 属性应用于组件允许配置的 C# 属性, 在 Blazor 文件夹的 SelectFilter.razor 文件中声明可配置属性如下:
csharp
@code
{
[Parameter]
public IEnumerable<string> Values { get; set; } = Enumerable.Empty<string>();
public string SelectedValue { get; set; }
[Parameter]
public string Title { get; set; } = "Placeholder";
}
组件可以选择它们允许配置的属性。在本例中,Parameter属性已应用于 SelectFilter 组件定义的两个属性。在 Blazor 文件夹的 PeopleList.razor ,修改了组件用于应用 SelectFilter 组件以添加配置属性的元素,如下:
csharp
<SelectFilter values="@Cities" title="city" />
对于每个应该配置的属性,将一个同名属性添加到父元素的 HTML 元素中。属性值可以是固定的值,比如分配给 title 属性的 City 字符串,也可以是 Razor 表达式,如 @Cities (它将 City 属性中的对象序列分配给 values 属性)。
1.设置和接收批量配置设置
如果存在许多配置设置,可以指定一个属性来接收没有被其他属性匹配的任何属性值,然后可将其作为一个集合应用。如下在 Blazor 文件夹的 SelectFiter.razor 文件中接收未匹配属性:
csharp
<select name="select-@Title" class="form-control" @bind="SelectedValue"
@attributes="Attrs">
......
csharp
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> Attrs { get; set; }
将 Parameter 属性的 CaptureUnmatchedValues 参数设置为 true,将标识一个属性作为未以其他方式匹配的属性的集合。属性的类型必须是 Dictionary<string,object>,这表示属性名称和值,可以使用@attribute 表达式应用于元素。它允许一次性应用一组属性,上面更改的效果意味着 SelectFilter 组件接收 Values 和 Title 属性值,其他任何属性都分配给 Attrs 属性并传递给 select 元素。
在 Blazor 文件夹的 PeopleList.razor 文件中添加元素属性:
csharp
<SelectFilter values="@Cities" title="city" autofocus="true" name="city"
required ="true"/>
请求 http://localhost:5000/controllers
。传递给 select 元素的属性不影响显示,右击选择元素,并从弹出菜单中选择检查,就将看到属性添加到 PeopleList 组件的 SelectFilter 元素中,SelectFilter 元素已添加到 SelectFilter 组件呈现的元素中。
2.在控制器视图或 Razor Pages 中配置组件
当使用组件元素应用组件时,属性还用于配置组件。在 Blazor 文件夹的 PeopleList.razor,向 PeopleList 组件添加了属性,该属性指定应该显示来自数据库的多少项,以及将传递给 SelectFilter 组件的字符串值,如下:
csharp
<SelectFilter values="@Cities" title="@SelectTitle" />
@code
{
[Inject]
public DataContext Context { get; set; }
public IEnumerable<Person> People => Context.People
.Include(p => p.Department).Include(p => p.Location)
.Take(ItemCount);
public IEnumerable<string> Cities => Context.Locations.Select(l => l.City);
public string SelectedCity { get; set; }
public string GetClass(string city) =>
SelectedCity == city ? "bg-info text-white" : "";
[Parameter]
public int ItemCount { get; set; } = 4;
[Parameter]
public string SelectTitle { get; set; }
}
C#属性的值是通过将名称以param-开头、后跟属性名称的属性添加到组件元素来提供的,在 Views/Home 文件夹的 Index.cshtm 文件中添加配置属性如下:
csharp
<component type="typeof(MyAdvanced.Blazor.PeopleList)" render-mode="Server"
param-itemcount="5" param-selecttitle="@("Location")" />
param-itemcount 属性为 ItemCount 属性提供一个值,param-selecttitle 属性为 SelectTitle 属性提供一个值。
在使用组件元素时,可以解析为数值或 bool值的属性值。其他值假定为 Razor 表达式而不是文字值,因为它们没有以@作为前缀,那么,为将 SelectTitle 属性的值指定为文字字符串,就需要一个 Razor 表达式。
2.2 创建自定义事件和绑定
SelectFilter 组件从父组件接收数据值,但它无法指示用户何时进行选择。为此,需要创建一个自定义事件,父组件可为其注册一个处理程序方法,就像它为常规 HTML 元素中的事件所做的那样。向 SelectFilter 组件添加了一个自定义事件:
csharp
<select name="select-@Title" class="form-control" @onchange="HandleSelect"
value="@SelectedValue">
csharp
[Parameter]
public EventCallback<string> CustomEvent { get; set; }
public async Task HandleSelect(ChangeEventArgs e)
{
SelectedValue = e.Value as string;
await CustomEvent.InvokeAsync(SelectedValue);
}
自定义事件是通过添加一个类型为 EventCalback 的属性来定义的。泛型类型参数是父类的事件处理程序接收的类型,在本例中为sting。前面更改了 select 元素,所以当 select 元素触发其 onchange 事件时,@onchange 属性会注册 HandleSelect 方法。通过调用 EventCallback .InvokeAsync 方法,HandleSelect 方法会更新 SelectedValue 属性。
InvokeAsync方法的参数使用从 ChangeEventArgs 对象接收到的值(从select元素接收到的值)触发事件。更改了 PeopleList 组件,以便它接收由 SelectList 组件发出的定制事件:
csharp
<SelectFilter values="@Cities" title="@SelectTitle" CustomEvent="@HandleCustom"/>
csharp
public void HandleCustom(string newValue)
{
SelectedCity = newValue;
}
要设置事件处理程序,需要向使用 EventCallback 属性名称应用子组件的元素添加一个属性。属性的值是一个 Razor 表达式,它选择接收类型为 T 的参数的方法。
请求 http://localhost:5000/controllers
,并从城市列表中选择一个值。自定义事件完成父组件和子组件之间的关系。父元素通过其属性配置子元素,以指定呈现给用户的标题和数据值列表。子组件使用自定义事件来告诉父组件,当用户选择一个值时,允许父对象在其HTML 表中突出显示相应的行。 创建自定义绑定
父组件可以在子组件上创建绑定,前提是它定义了一对属性,其中一个属性被分配一个数据值,另一个属性是自定义事件。属性的名称很重要:事件属性的名称必须与数据属性的名称加 Changed 相同。
在 Blazor 文件夹的 SelectFiter.razor 文件中准备自定义绑定,更新了 SelectFiter 组件,以便它显示绑定所需的属性,如下:
csharp
[Parameter]
public string SelectedValue { get; set; }
......
[Parameter]
public EventCallback<string> SelectedValueChanged { get; set; }
public async Task HandleSelect(ChangeEventArgs e)
{
SelectedValue = e.Value as string;
await SelectedValueChanged.InvokeAsync(SelectedValue);
}
注意,Parameter 属性必须同时应用于 SelectedValue 和 SelectedValueChanged 属性。如果省略其中任何一个属性,数据绑定将无法按预期工作。
父组件通过@bind- 属性绑定到子组件,其中 对应于子组件定义的属性。在本例中,子组件的属性名是 SelectedValue,父组件可以使用@bind-SelectedValue 创建绑定。在 Blazor 文件夹 PeopleList.razor 文件中使用自定义绑定:
csharp
<SelectFilter values="@Cities" title="@SelectTitle" @bind-SelectedValue = "SelectedCity"/>
<button class="btn btn-primary" @onclick="@(()=> SelectedCity = "San Jose")">
Change
</button>
请求 http://localhost:5000/controllers
,并从城市列表中选择 New York,自定义绑定将使select 元素中选择的值通过表中的高亮显示反映出来。单击 Change 按钮以在另一个方向测试绑定,城市的更改将高亮显示。
3 在组件中显示子内容
显示子内容的组件充当父元素提供的元素的包装器。要查看子内容是如何管理的,请给 Blazor文件夹添加名为 ThemeWrapper.razor 的 Razor 组件,如下:
csharp
<div class="p-2 bg-@Theme border text-white">
<h5 class="text-center">@Title</h5>
@ChildContent
</div>
@code
{
[Parameter]
public string Theme { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
为接收子内容,组件定义了一个名为 ChildContent 的属性。@ChildContent 表达式在组件的 html 输出中包含子内容。div 元素使用引导主题颜色进行样式化,并显示标题。
子内容是通过在应用组件时在开始和结束标记之间添加HTML元素来定义的。在 Blazor 文件夹的 PeopleList.razor 文件中定义子内容:
csharp
<ThemeWrapper Theme="info" Title="Location Selector">
<SelectFilter values="@Cities" title="@SelectTitle"
@bind-SelectedValue="SelectedCity" />
<button class="btn btn-primary" @onclick="@(()=> SelectedCity = "San Jose")">
Change
</button>
</ThemeWrapper>
配置子内容不需要附加属性,子内容将被自动处理并分配给ChidContcent属性。请求 http://localhost:5000/controllers
,将看到选择主题的配置属性和用于生成响应的标题文本。
3.1 创建模板组件
模板组件为子内容的表示带来了更多的结构,允许显示内容的多个部分。模板组件可以很好地整合应用程序中使用的特性,以防止代码和内容的重复。
要了解其工作原理,请给 Blazor 文件夹添加一个名为 TableTemplate.razor 的 Razor 组件:
csharp
<table class="table table-sm table-bordered table-striped">
@if (Header != null)
{
<thead>@Header</thead>
}
<tbody>@Body</tbody>
</table>
@code
{
[Parameter]
public RenderFragment Header { get; set; }
[Parameter]
public RenderFragment Body { get; set; }
}
组件为它支持的每个子内容区域定义一个 RenderFragment 属性。TableTemplate 组件定义了两个Renderfragment 属性,命名为 Header 和 Body,它们代表了表格的内容部分,子内容的每个区域都是使用 Razor 表达式 @Header 和 @Body 呈现的,可以通过检查属性值是否为空来检查是否为特定部分提供了内容,该组件为 Header 部分执行此操作。
当使用模板组件时,每个区域的内容都包含在一个 HTML 元素中,该元素的标记与对应的Renderfragment 属性的名称相匹配。 在 Blazor 文件夹的 PeopleList.razor 文件中应用模板组件:
csharp
<TableTemplate>
<Header>
<tr><th>ID</th><th>Name</th><th>Dept</th><th>Location</th></tr>
</Header>
<Body>
@foreach (Person p in People)
{
<tr class="@GetClass(p.Location.City)">
<td>@p.PersonId</td>
<td>@p.Surname, @p.Firstname</td>
<td>@p.Department.Name</td>
<td>@p.Location.City, @p.Location.State</td>
</tr>
}
</Body>
</TableTemplate>
子内容构成与模版组件的属性、标题和主体对应的部分,这使得 TableTemplate 组件负责表结构,而 PeopleList 组件负责提供详情。请求 http://localhost:5000/controllers
,将看到模版组件爱你生成的输出。
3.2 在模板组件中使用泛型类型参数
模板组件可通过使用泛型类型参数来感知数据,泛型类型参数允许父组件提供一系列数据对象和用于表示这些对象的模板。模板组件负责为每个数据对象生成内容,因此可以提供更有用的功能。
下面将向模板组件添加对选择显示多少表行和选择表行的支持。第一步是向组件添加一个泛型类型参数,并使用它呈现表主体的内容。在 Blazor 文件夹的 TableTemplate.razor 文件中添加泛型类型参数:
csharp
@typeparam RowType
<table class="table table-sm table-bordered table-striped">
@if (Header != null)
{
<thead>@Header</thead>
}
<tbody>
@foreach(RowType item in RowData)
{
<tr>@RowTemplate(item)</tr>
}
</tbody>
</table>
@code
{
[Parameter]
public RenderFragment Header { get; set; }
[Parameter]
public RenderFragment<RowType> RowTemplate { get; set; }
[Parameter]
public IEnumerable<RowType> RowData { get; set; }
}
泛型类型参数是使用 @typeparam 属性指定的,本例为参数指定了名称 RowType,因为引用组件将为其生成表行的数据类型。
组件处理的数据是通过添加一个属性来接收的,该属性的类型是泛型类型的对象序列。将属性命名为 RowData,其类型为 IEnumerable<RowType>。组件为每个对象显示的内容是使用RenderFragment<T> 属性接收的。将这个属性命名为 RowTemplate,它的类型是RenderFragment<RowType>,反映了为泛型类型参数选择的名称。
当组件通过 RenderFragment<T>属性接收到一个内容区段时,可通过调用该区段作为方法并使用该对象作为参数,来为单个对象呈现它@RowTemplate(item)
。
这段代码枚举 RowData 序列中的 RowType 对象,并为每个对象呈现通过 RowTemplate 属性接收到的内容部分。1.使用通用模板组件
前面简化了 PeopleList 组件,使其仅使用模板组件生成 Person 对象表,并且删除了以前的特性。在 Blazor 文件夹的 PeopleList.razor 文件中使用通用模板组件:
csharp
<TableTemplate RowType="Person" RowData="People">
<Header>
<tr><th>ID</th><th>Name</th><th>Dept</th><th>Location</th></tr>
</Header>
<RowTemplate Context="p">
<td>@p.PersonId</td>
<td>@p.Surname, @p.Firstname</td>
<td>@p.Department.Name</td>
<td>@p.Location.City, @p.Location.State</td>
</RowTemplate>
</TableTemplate>
@code
{
[Inject]
public DataContext Context { get; set; }
public IEnumerable<Person> People => Context.People
.Include(p => p.Department)
.Include(p => p.Location);
}
RowIype 属性用于指定泛型类型参数的值。RowData 属性指定模板组件要处理的数据。RowTempaie 元素表示为每个数据对象生成的元素。当为 Renderragment 属性定义一个内容区段时,conrext属性用于为当前处理的对象分配一个名称。
在本例中,使用 Context 属性们名称 p 分配给当前对象,然后在用于填充内容部分元素的 Razor 表达式中引用该对象。
总体效果是将模板组件配置为显示 Person 对象。该组件为每个 Person 生成一个表行,其中包含 td 元素,其内容是使用当前 Person 对象的属性设置的。
删除了用 Parameter 属性装饰的属性,所以需要从应用 PepleList 组件的元素中删除相应的属性,在 Views/Home 文件夹的 Index.cshtml 文件中删除属性:
csharp
<component type="typeof(MyAdvanced.Blazor.PeopleList)" render-mode="Server"/>
要查看通用模板组件,请求 http://localhost:5000/controllers
。PeopleList 组件提供的数据和内容部分已被 TableTemplate 组件用于生成列表。2.向通用模板组件添加特性
在 Blazor 文件夹的 TableTemplate.razor 文件中添加特性:
csharp
@typeparam RowType
<div class="container-fluid">
<div class="row">
<div class="col">
<SelectFilter Title="@("Sort")" Values="@SortDirectionChoices"
@bind-SelectedValue="SortDirectionSelection" />
</div>
<div class="col">
<SelectFilter Title="@("Highlight")" Values="@HighlightChoices()"
@bind-SelectedValue="HighlightSelection" />
</div>
</div>
</div>
<table class="table table-sm table-bordered table-striped">
@if (Header != null)
{
<thead>@Header</thead>
}
<tbody>
@foreach (RowType item in SortedData())
{
<tr class="@IsHighlighted(item)">@RowTemplate(item)</tr>
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment Header { get; set; }
[Parameter]
public RenderFragment<RowType> RowTemplate { get; set; }
[Parameter]
public IEnumerable<RowType> RowData { get; set; }
[Parameter]
public Func<RowType, string> Highlight { get; set; }
public IEnumerable<string> HighlightChoices() =>
RowData.Select(item => Highlight(item)).Distinct();
public string HighlightSelection { get; set; }
public string IsHighlighted(RowType item) =>
Highlight(item) == HighlightSelection ? "bg-dark text-white" : "";
[Parameter]
public Func<RowType, string> SortDirection { get; set; }
public string[] SortDirectionChoices =
new string[] { "Ascending", "Descending" };
public string SortDirectionSelection { get; set; } = "Ascending";
public IEnumerable<RowType> SortedData() =>
SortDirectionSelection == "Ascending"
? RowData.OrderBy(SortDirection)
: RowData.OrderByDescending(SortDirection);
}
这些更改为用户提供了两个选择元素,这两个元素是使用本章前面创建的 SelectFilter 组件显示的。这些新元素允许用户按升序和降序对数据排序,并选择用于突出显示表行的值。父组件提供了额外参数,为模板组件函数提供功能,来选择用于排序和突出显示的属性,在 Blazor 文件夹的 PeopleList.razor 文件中配置模板组件特性:
csharp
<TableTemplate RowType="Person" RowData="People"
Highlight="@(p=>p.Location.City)" SortDirection="@(p=>p.Surname)">
......
</TableTemplate>
Highlight 属性为模板组件提供一个函数,该函数选择用于突出显示表行的属性,SortDirection 属性提供一个函数,该函数选择用于排序的属性。要查看效果,请求 http://localhost:5000/controllers
。响应将包含新的 select 元素,这些元素可用于重改排序顺定或选择要过滤的城市。3.重用通用模板组件
添加到模板组件的特性都依赖于泛型类型参数,允许组件修改它呈现的内容,而不绑定到特定的类。结果是一个组件,可用于在需要表的地方显示、排序和突出显示任何数据类型。给 Blazor文件夹添加一个名为 DepartmmentList.razor 的 Razor 组件:
csharp
<TableTemplate RowType="Department" RowData="Departments"
Highlight="@(d => d.Name)" SortDirection="@(d => d.Name)">
<Header>
<tr><th>ID</th><th>Name</th><th>People</th><th>Locations</th></tr>
</Header>
<RowTemplate Context="d">
<td>@d.Departmentid</td>
<td>@d.Name</td>
<td>@(String.Join(", ", d.People.Select(p => p.Surname)))</td>
<td>
@(String.Join(", ",
d.People.Select(p => p.Location.City).Distinct()))
</td>
</RowTemplate>
</TableTemplate>
@code
{
[Inject]
public DataContext Context { get; set; }
public IEnumerable<Department> Departments =>
Context.Departments.Include(d => d.People).ThenInclude(p => p.Location);
}
TableTemplate 组件用于向用户展示数据库中的 Department 对象列表,以及相关的 Person 和Location 对象的详细信息,这些信息通过 Entity Framework Core Include 和 Thenlnclude 方法进行查询。更改在 Pages 文件夹的 Blazor.cshtml 文件中组件:
csharp
@page "/pages/blazor"
<h4 class="bg-primary text-white text-center p-2">Departmments</h4>
<component type="typeof(MyAdvanced.Blazor.DepartmmentList)" render-mode="Server" />
请求 http://localhost:5000/pages/blazor
。使用模板化组件显示响应。
3.3 级联参数
Blazor 通过支持级联参数,组件提供的数据值可直接用于它的任何后代,而不由中间组件传递。级联参数是使用 CascadingValue 组件定义的,该组件用于包装部分内容。在 Blazor 文件夹的 DepartmentList.razor 文件中创建级联参数:
csharp
<CascadingValue Name="BgTheme" Value="Theme" IsFixed="false">
......
</CascadingValue>
<SelectFilter Title="@("Theme")" Values="Themes" @bind-SelectedValue="Theme" />
@code
{
[Inject]
public DataContext Context { get; set; }
public IEnumerable<Department> Departments => Context.Departments
.Include(d => d.People).ThenInclude(p => p.Location);
public string Theme { get; set; } = "info";
public string[] Themes = new string[] { "primary", "info", "success" };
}
CascadingValue 元素为它所包含的组件及其后代提供一个可用的值。Name 属性指定参数的名称,Value 属性指定值,isFixed 属性用于指定值是否会更改。代码清单中使用了 CascadingValue 元素来创建名为BgTheme 的级联参数,它的值由 SelectFilter 组件的一个实例设置该组件向用户提供了一组引导 CSS 主题名。
在 Blazor 文件夹的 SelectFilter.razor 文件中使用 CascadingParameter 属性的组件直接接收级联参数:
csharp
<div class="form-group p-2 bg-@Theme @TextColor()">
......
csharp
......
[CascadingParameter(Name = "BgTheme")]
public string Theme { get; set; }
public string TextColor() => Theme == null ? "" : "text-white";
CascadingParameter 属性的 Name 参数用于指定级联参数的名称。代码清单 DepartmentList.razor 中定义的 BgTheme 参数由代码清单 SelectFilter.razor 中的Theme属性接收,并用于设置组件的背景。请求 http://localhost:5000/pages/blazor
。
本例中使用了三个 SelectFilter 组件实例,但其中只有两个在 Cascadingvalue 元素所包含的层次结构中。另一个实例在 Cascadingvalue 元素之外定义,不接收级联值。
4 处理错误
4.1 处理连接错误
Blazor 依赖于它在浏览器和 ASP.NETCore 服务器之间的持久 HTTP连接。当连接中断时,应用程序无法正常工作,并显示一个模态错误消息,阻止用户与组件交互。Blazor 允许通过定义具有特定 id 的元素来定制连接错误,如代码清单所示。在 Pages 文件夹的 Blazor.cshtml 文件中定义连接错误元素:
csharp
<link href="~/lib/connectionerrors.css" rel="stylesheet" />
<div id="components-reconnect-modal"
class="h4 bg-dark text-white text-center my-2 p-2 components-reconnect-hide">
Blazor Connection Lost
<div class="reconnect">
Trying to reconnect...
</div>
<div class="failed">
Reconnection Failed.
<button class="btn btn-light" onclick="window.Blazor.reconnect()">
Reconnect
</button>
</div>
<div class="rejected">
Reconnection Rejected.
<button class="btn btn-light" onclick="location.reload()">
Reload
</button>
</div>
</div>
自定义错误元素的id 属性必须是 componentes-reconnect-modal。当出现连接错误时,Blazor将查找此元素并将其添加到表中描述的四个类中的一个。
名称 | 描述 |
---|---|
components-reconnect-show | 当连接已经丢失且 Blazor 正在尝试重连接时,将元素添加到这个类中。错误消息应该显示 给用户,并且应该防止与 Blazor 内容的交互 |
components-reconnect-hide | 如果重新建立连接,则将元素添加到该类中。错误消息应该隐藏,并且应该允许交互 |
components-reconnect-failed | 如果 Blazor 重连接失败,则将该元素添加到该类中。用户可以看到一个按钮,该按钮调用 window.Blazor.reconnect()来再次尝试重新连接 |
components-reconnect-rejected | 如果 Blazor 能够到达服务器,但用户的连接状态已经丢失,则将元素添加到这个类中,这 通常发生在服务器重新启动时。用户可以看到一个按钮,该按钮调用 location.reload()来重 新加载应用程序并重试 |
元素最初没有添加到这些类中的任何一个,因此将其显式添加到 components-reconnect-hide 类中,以便在出现问题之前隐藏它。
希望针对重连接期间可能出现的每个条件向用户显示特定的消息。为此,添加了为每个条件显示消息的元素。为了管理它们的可见性,将一个名为connectionError.css 的 CSS 样式表添加到wwwroot 文件夹中,并用它定义如代码清单所示的样式。
wwwroot 文件夹中 connectionError.css 文件的内容:
csharp
#components-reconnect-modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: hidden;
opacity: 0.9;
}
.components-reconnect-hide {
display: none;
}
.components-reconnect-show {
display: block;
}
.components-reconnect-show > .reconnect {
display: block;
}
.components-reconnect-show > .failed,
.components-reconnect-show > .rejected {
display: none;
}
.components-reconnect-failed > .failed {
display: block;
}
.components-reconnect-failed > .reconnect,
.components-reconnect-failed > .rejected {
display: none;
}
.components-reconnect-rejected > .rejected {
display: block;
}
.components-reconnect-rejected > .reconnect,
.components-reconnect-rejected > .failed {
display: none;
}
这些样式将 componentes-reconnect-modal 元素显示为一个模态项,其可见性由 componentesrecomnecthide 和 componentes-reconnect-show 类决定。
要查看效果,请求http://localhost:5000/pages/blazor
。等到显示组件,就停止服务器。当 Blazor 尝试重新连接时,将看到一个初始错误消息。几秒钟后,将看到指示重连接失败的消息。
4.2 处理未捕获的应用程序错误
Blazor 不能很好地响应未捕获的应用程序错误,这些错误几乎总是被视为终端。要查看默认的错误行为,请将代码清单中所示的元素添加到 DepartmentList 组件。
在 Blazor 文件夹的 DepartmentList.razor 文件中添加元素:
csharp
<button class="btn btn-danger" @onclick="@(()=> throw new Exception())">Error</button>
重启 ASP.NET Core,请求 http://localhost:5000/pages/blazor
,然后单击 Error 按钮。在浏览器中并无明显变化,但当单击按钮时在服务器上抛出的异常被证明是致命的;用户仍然可以使用 select 元素选择值,因为这些是由浏览器显示的,但响应选择的事件处理程序不再有效,应用程序本质上是停止了。
当出现未处理的应用程序错误时,Blazor会查找 id 为 Blazor-error-ui 的元素,并设置其 CSS显示属性以阻止。代码清单将具有此 id 的元素添加到 Blazorcshtml 文件,该文件样式化为显示有用的消息。
csharp
<div id="blazor-error-ui"
class="text-center bg-danger h6 text-white p-2 fixed-top w-100"
style="display:none">
An error has occurred. This application will not respond until reloaded.
<button class="btn btn-sm btn-primary" onclick="location.reload()">
Reload
</button>
</div>
单击 Error 按钮,当显示元素时,用户将看到一个警告和一个重新加载浏览器的按钮。