ASP.NET Core Blazor 5:Blazor表单和数据

本章将描述 Blazor 为处理 HTML 表单提供的特性,包括对数据验证的支持。

1 准备工作

继续使用上一章项目。

创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。

csharp 复制代码
@inherits LayoutComponentBase

<div class="m-2">
    @Body
</div>

为 Blazor/Forms 文件夹添加 FormSpy.razor,这个组件用来显示表单元素和旁边正在编辑的值。

csharp 复制代码
<div class="container-fluid no-gutters">
    <div class="row">
        <div class="col">
            @ChildContent
        </div>
        <div class="col">
            <table class="table table-sm table-striped table-bordered">
                <thead>
                    <tr><th colspan="2" class="text-center">Data Summary</th></tr>
                </thead>
                <tbody>
                    <tr><th>ID</th><td>@PersonData?.PersonId</td></tr>
                    <tr><th>Firstname</th><td>@PersonData?.Firstname</td></tr>
                    <tr><th>Surname</th><td>@PersonData?.Surname</td></tr>
                    <tr><th>Dept ID</th><td>@PersonData?.DepartmentId</td></tr>
                    <tr><th>Location ID</th><td>@PersonData?.LocationId</td></tr>
                </tbody>
            </table>            
        </div>
    </div>
</div>

@code {

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public Person PersonData { get; set; }
}

Blazor/Forms 文件夹添加 Editor.razor,此组件将用于创建和编辑 Person 对象。

csharp 复制代码
@page "/forms/edit/{id:long}"
@layout EmptyLayout

<h4 class="bg-primary text-center text-white p-2">Edit</h4>

<FormSpy PersonData="PersonData">
   <h4 class="text-center">Form Placeholder</h4>
   <div class="text-center">
       <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
   </div>
</FormSpy>


@code
{

    [Inject]
    public NavigationManager NavManager { get; set; }

    [Inject]
    DataContext Context { get; set; }

    [Parameter]
    public long Id { get; set; }

    public Person PersonData { get; set; } = new Person();

    protected async override Task OnParametersSetAsync()
    {
        PersonData = await Context.People.FindAsync(Id);
    }
}

代码中的组件使用 @layout 表达式覆盖默认布局并选择 EmptyLayout。并排布局用于在占位符旁边显示 PersonTable 组件,在这里将添加表单。

最后,在 Blazor/Forms 文件夹中创建 List.razor,该组件以表的形式向用户显示 Person 对象列表。

csharp 复制代码
@page "/forms"
@page "/forms/list"
@layout EmptyLayout

<h5 class="bg-primary text-white text-center p-2">People</h5>

<table class="table table-sm table-striped table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Dept</th>
            <th>Location</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @if (People.Count() == 0)
        {
            <tr><th colspan="5" class="p-4 text-center">Loading Data...</th></tr>
        }
        else
        {
            @foreach (Person p in People)
            {
                <tr>
                    <td>@p.PersonId</td>
                    <td>@p.Surname, @p.Firstname</td>
                    <td>@p.Department.Name</td>
                    <td>@p.Location.City</td>
                    <td>
                        <NavLink class="btn btn-sm btn-warning"
                                 href="@GetEditUrl(p.PersonId)">
                            Edit
                        </NavLink>
                    </td>
                </tr>
            }
        }
    </tbody>
</table>

@code 
{
    [Inject]
    public DataContext Context { get; set; }

    public IEnumerable<Person> People { get; set; } = Enumerable.Empty<Person>();

    protected override void OnInitialized()
    {
        People = Context.People.Include(p => p.Department).Include(p => p.Location);
    }

    string GetEditUrl(long id) => $"/forms/edit/{id}";
}

请求 http://localhost:5000/forms,这将生成一个数据表。单击其中一个 Edit 按钮,将看到表单占位符和显示所选 Person 对象当前属性值的摘要。

2 使用 Blazor 表单组件

Blazor 提供的表单组件:

名称 描述
EditForm 此组件将呈现连接起来进行数据验证的表单元素
InputText 此组件呈现一个绑定到 C# 字符串属性的输入元素
InputCheckbox 此组件呈现一个输入元素,它的类型属性是 checkbox,并且绑定到 C# bool 属性
InputDate 此组件呈现一个输入元素,该元素的类型属性为date,并绑定到C# DateTime 或DateTimeOffset属性
InputNumber 此组件呈现一个输入元素,其类型属性为 number,并绑定到 C# int、long、float、double 或 decimal 值
InputTextArea 此组件呈现一个绑定到 C#字符串属性的 textarea 组件

EditFomm 组件必须用于任何其他组件才能工作。Blazor/Forms 文件夹的 Editorrazor 文件中使用表单组件,添加一个 EditForm 和两个 ImputText 组件,来表示 Person 类定义的两个属性。

csharp 复制代码
<FormSpy PersonData="PersonData">
    <EditForm Model="PersonData">
        <div class="form-group">
            <label>Person ID</label>
            <InputNumber class="form-control"
            @bind-Value="PersonData.PersonId" disabled />
        </div>
        <div class="form-group">
            <label>Firstname</label>
            <InputText class="form-control" @bind-Value="PersonData.Firstname" />
        </div>
        <div class="form-group">
            <label>Surname</label>
            <InputText class="form-control" @bind-Value="PersonData.Surname" />
        </div>
        <div class="form-group">
            <label>Dept ID</label>
            <InputNumber class="form-control"
            @bind-Value="PersonData.DepartmentId" />
        </div>
        <div class="text-center">
            <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
        </div>
    </EditForm>
</FormSpy>

EditForm 组件呈现一个表单元素,Model 属性用于向 EditForm 提供表单用于编辑和验证的对象。

名称以 Input 开头的组件用于显示单个模型属性的 input 或 textarea 元素。这些组件定义了一个名为 Value 的自定义绑定,该绑定使用@bind-Value 属性与模型属性关联。属性级组件必须与它们呈现给用户的属性类型相匹配。

重启并请求 http://localhost:5000/forms/edit/2,将看到显示的三个输入元素。编推值并通过按 Tab 键移动焦点,将在更新窗口的右侧看到汇总数据。

2.1 创建自定义表单组件

Blazor 仅为 input 和 textarea 元素提供内置组件。不过创建一个集成到 Blazor 表单特性的自定义组件是一个简单的过程。在 Blazor/Forms 文件夹中添加一个名为 CustomSelect.razor 的Razor 组件。

csharp 复制代码
@typeparam TValue
@inherits InputBase<TValue>

<select class="form-control @CssClass" value="@CurrentValueAsString"
@onchange="@(ev => CurrentValueAsString = ev.Value as string)">
    @ChildContent
    @foreach (KeyValuePair<string, TValue> kvp in Values)
    {
        <option value="@kvp.Value">@kvp.Key</option>
    }
</select>

@code
{
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public IDictionary<string, TValue> Values { get; set; }

    [Parameter]
    public Func<string, TValue> Parser { get; set; }

    protected override bool TryParseValueFromString(
        string value, out TValue result, out string validationErrorMessage)
    {
        try
        {
            result = Parser(value);
            validationErrorMessage = null;
            return true;
        }
        catch
        {
            result = default(TValue);
            validationErrorMessage = "The value is not valid";
            return false;
        }
    }
}

表单组件的基类是 InputBase ,其中通用类型参数是组件表示的模型属性类型。基类负责大部分工作,并提供 CurrentValueAsString 属性,该属性用于在用户选择新值时在事件处理程序中提供当前值: value="@CurrentValueAsString" @onchange="@(ev => CurrentValueAsString = ev.Value as string)"。在准备数据验证的过程中,该组件包括 CssClass 属性的值,在select 元素的 class 属性中 @CssClass

必须实现抽象的 TryParseValueFromString 方法,以便基类能够在 HTML 元素使用的字符串值和 C#模型属性的相应值之间进行映射。这里不想将自定义 select 元素实现为任何特定的 C# 数据类型,因此使用@typeparam 表达式来定义通用类型参数。Values 属性用于接收将显示给用户的字典映射字符串值和用作 C# 值的 TValue 值。该方法接收两个out 参数,这些参数用于设置解析值以及解析器验证错误消息,如果存在问题,该错误消息将显示给用户。由于正在使用泛型类型,因此 Parser 属性接收一个函数,调用该函数,以将字符串值解析为 TValue 值。

在 BlazorFomms 文件央的 Edior.razor 文件中使用自定义表单元素,因此用户可为 Person 类定义的 Departmentld 和 Locationld 属性选择值。

csharp 复制代码
@page "/forms/edit/{id:long}"
@layout EmptyLayout

<h4 class="bg-primary text-center text-white p-2">Edit</h4>

<FormSpy PersonData="PersonData">
    <EditForm Model="PersonData">
        <div class="form-group">
            <label>Firstname</label>
            <InputText class="form-control" @bind-Value="PersonData.Firstname" />
        </div>
        <div class="form-group">
            <label>Surname</label>
            <InputText class="form-control" @bind-Value="PersonData.Surname" />
        </div>
        <div class="form-group">
            <label>Dept ID</label>
            <CustomSelect TValue="long" Values="Departments" Parser="@(str=>long.Parse(str))"
            @bind-Value="PersonData.DepartmentId">
                <option selected disabled value="0">choose a Department</option>
            </CustomSelect>
        </div>
        <div class="form-group">
            <label>Location ID</label>
            <CustomSelect TValue="long" Values="Departments" Parser="@(str=>long.Parse(str))"
            @bind-Value="PersonData.LocationId">
                <option selected disabled value="0">choose a Location</option>
            </CustomSelect>
        </div>
        <div class="text-center">
            <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
        </div>
    </EditForm>
</FormSpy>


@code
{

    [Inject]
    public NavigationManager NavManager { get; set; }

    [Inject]
    DataContext Context { get; set; }

    [Parameter]
    public long Id { get; set; }

    public Person PersonData { get; set; } = new Person();

    public IDictionary<string, long> Departments { get; set; }
        = new Dictionary<string, long>();
    public IDictionary<string, long> Locations { get; set; }
        = new Dictionary<string, long>();

    protected async override Task OnParametersSetAsync()
    {
        PersonData = await Context.People.FindAsync(Id);
        Departments = await Context.Departments
            .ToDictionaryAsync(d => d.Name, d => d.Departmentid);
        Locations = await Context.Locations
            .ToDictionaryAsync(l => $"{l.City}, {l.State}", l => l.LocationId);
    }
}

使用 Entity Framework Core ToDictionaryAsync 方法从 Department 和 Location 数据创建值和标签的集合,并使用它们配置 CustomSelect 组件。重启请求 http://localhost:5000/forms/edit/2,当选择一个新值时,CustomSelect 组件将更新 CurrentValueAsString 属性,TryParseValueFromString 方法被调用,其结果用于更新 Value 绑定。

2.2 验证表单数据

blazor 提供了使用标准属性执行验证的组件。

名称 描述
DataAnnotationsValidator 此组件将应用于模型类的验证属性集成到 Blazor 表单特性中
ValidationMessage 此组件显示单个属性的验证错误消息
ValidationSummary 此组件显示整个模型对象的验证错误消息

验证组件生成分配给类的元素,可以用 CSS 样式化这些元素。

名称 描述
validation-errors ValidationSummary 组件生成一个 ul 元素,该元素被分配给这个类,并且是验证消息摘要的顶级容器
validation-message ValidationSummary 组件使用为每个验证消息分配给这个类的 il 元素来填充它的 ul 元素。ValidationMessage 组件为这个类的属性级消息呈现一个分配给它的 div 元素

Blazor Input* 组件将它们生成的 HTML 元素添加到下表描述的类中,以指示验证状态。这包括 ImnputBase 类,从这个类派生了 CustomSelect 组件,它也是代码中 CssClass 属性的用途。

名称 描述
modifed 一旦用户编辑了值,元素就会添加到这个类中
valid 如果包含的值通过验证,则将元素添加到该类中
invalid 如果元素包含的值验证失败,则将元素添加到该类中

将一个名为 blazorValidation.css 的 CSS 样式表添加到 wwwroot 文件夹中。

csharp 复制代码
.validation-errors {
    background-color: rgb(220, 53, 69);
    color: white;
    padding: 8px;
    text-align: center;
    font-size: 16px;
    font-weight: 500;
}

div.validation-message {
    color: rgb(220, 53, 69);
    font-weight: 500
}

.modified.valid {
    border: solid 3px rgb(40, 167, 69);
}

.modified.invalid {
    border: solid 3px rgb(220, 53, 69);
}

这些样式将错误消息格式化为红色,并对单个表单元素应用红色或绿色边框。导入 CSS 样式表并在Editor.razor 文件中应用验证组件。

csharp 复制代码
@page "/forms/edit/{id:long}"
@layout EmptyLayout
<link href="~/blazorvalidation.css" rel="stylesheet" />
<h4 class="bg-primary text-center text-white p-2">Edit</h4>

<FormSpy PersonData="PersonData">
    <EditForm Model="PersonData">
        <DataAnnotationsValidator />
        <ValidationSummary />
        <div class="form-group">
            <label>Firstname</label>
            <ValidationMessage For="@(()=>PersonData.Firstname)" />
            <InputText class="form-control" @bind-Value="PersonData.Firstname" />
        </div>
        <div class="form-group">
            <label>Surname</label>
            <ValidationMessage For="@(()=>PersonData.Surname)" />
            <InputText class="form-control" @bind-Value="PersonData.Surname" />
        </div>
        <div class="form-group">
            <label>Dept ID</label>
            <ValidationMessage For="@(()=>PersonData.DepartmentId)" />
            <CustomSelect TValue="long" Values="Departments" Parser="@(str=>long.Parse(str))"
            @bind-Value="PersonData.DepartmentId">
                <option selected disabled value="0">choose a Department</option>
            </CustomSelect>
        </div>
        <div class="form-group">
            <label>Location ID</label>
            <ValidationMessage For="@(()=>PersonData.LocationId)" />
            <CustomSelect TValue="long" Values="Locations" Parser="@(str=>long.Parse(str))"
            @bind-Value="PersonData.LocationId">
                <option selected disabled value="0">choose a Location</option>
            </CustomSelect>
        </div>
        <div class="text-center">
            <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
        </div>
    </EditForm>
</FormSpy>

DataAnnotationsValidator 和 ValidationSummary 组件在应用时没有任何配置属性。ValidationMessag 属性使用 For 属性配置,该属性接收一个的数,该函数返回组件所表示的属性。

启用数据验证的最后一步是将属性应用到模型类,在 Models 文件夹的 Person.cs 文件中应用验证属性。

csharp 复制代码
public class Person
    {
        public long PersonId { get; set; }

        [Required(ErrorMessage = "A firstname is required")]
        [MinLength(3, ErrorMessage = "Firstnames must be 3 or more characters")]
        public string Firstname { get; set; }

        [Required(ErrorMessage = "A surname is required")]
        [MinLength(3, ErrorMessage = "Surnames must be 3 or more characters")]
        public string Surname { get; set; }

        [Required]
        [Range(1, long.MaxValue, ErrorMessage = "A department must be selected")]
        public long DepartmentId { get; set; }

        [Required]
        [Range(1, long.MaxValue, ErrorMessage = "A location must be selected")]
        public long LocationId { get; set; }

        public Department Department { get; set; }
        public Location Location { get; set; }
    }

要查看验证组件的效果,重启请求 http://localhost:5000/forms/edit/2。除 Firstname 字段并通过按 Tab 键或单击另一个字段移动焦点。当焦点更改时,将执行验证,并显示错误消息。Editor 组件同时显示摘要消息和每个属性消息,因此相同的错误消息会显示两次从 Surname 字段中删除除前两个字符以外的所有字符,当更改焦点时将显示第二条验证消息。也有对其他属性的验证支持,但是 select 元素不允许用户选择无效的有效值。如果更改了一个值,select 元素将用绿色边框装饰,以指示有效的选择,但是在演示如何使用表单维件创建新的数据对象之前,看不到无效的响应。

2.3 处理表单事件

EditForm 组件定义了允许应用程序响应用户操作的事件。

名称 描述
OnValidSubmit 当提交表单且表单数据通过验证时触发此事件
OnInvalidSubmit 当提交表单且表单数据验证失败时触发此事件
OnSubmit 此事件在表单提交和验证执行之前触发

这些事件通过提交按钮来触发。向 Editor 组件中添加一个 submit 按钮来处理 EditForm 事件。

csharp 复制代码
@page "/forms/edit/{id:long}"
@layout EmptyLayout
<link href="~/blazorvalidation.css" rel="stylesheet" />
<h4 class="bg-primary text-center text-white p-2">Edit</h4>
<h6 class="bg-info text-center text-white p-2">@FormSubmitMessage</h6>

<FormSpy PersonData="PersonData">
    <EditForm Model="PersonData" OnValidSubmit="HandleValidSubmit"
              OnInvalidSubmit="HandleInvalidSubmit">
        <DataAnnotationsValidator />
        ......
        <div class="text-center">
            <button type="submit" class="btn btn-primary">Submit</button>
            <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
        </div>
    </EditForm>
</FormSpy>


@code
{
    ......
    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";
    public void HandleValidSubmit() => FormSubmitMessage = "Valid Data Submitted";
    public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";
}

重启请求 http://localhost:5000/forms/edit/2,清除 Firstname 字段,然后单击 Submit 按钮。除了验证错误之外,还将看到一条消息,指示提交的表单使用了无效数据。在字段中输入一个名称,再次单击 Submit,消息会更改。

3 使用 EF Core 与 Blazor

Blazor 模型改变了 EF Core 的行为方式,如果习惯于编写常规的 ASE.NET Core 应用程序,那么这可能会导致意想不到的结果。接下来将解释这些问题以及如何避免可能出现的问题。

3.1 理解 EF Core 上下文范围问题

第一个问题是,更改 Firstname 之后未提交,点击返回主列表后,主列表数据已经改变了。

在 Blazor 应用程序中,路由系统响应 URL 更改,而不发送新的 HTTP 请求,这意味着只使用 Blazor 维护的到服务器的持久 HTTP 连接来显示多个组件。这将导致多个组件共享单个依赖注入范围,一个组件所做的更改将影响其他组件,即使这些更改没有写入数据库。1.丢弃未保存的数据更改

如果在组件之间共享上下文,那么可采用这种方法,并确保组件在销毁时放弃任何更改。 在 Blazor/Forms 文件夹的 Editor.razor 文件中丢弃未保存的数据更改。

csharp 复制代码
@implements IDisposable
......
public void Dispose() => Context.Entry(PersonData).State = EntityState.Detached;

2.创建新的依赖注入范围

若想保留其余部分使用的模型,就必须创建新的依赖注入范围。并让每个组件接收子级的 EF Core 上下文对象。这是通过使用 @ inherits 表达是将组件的基类设置为 OwningComponentBase 来完成的。

OwningComponentBase 类定义了组件继承的 ScopedServices 属性,提供了一个可用于获取服务的 IServiceProvider 对象,该服务在一个特定于组件的生命周期的作用域中创建,该范围不会与其他任何组件共享。

在 Blazor/Forms 文件夹的 Editor.razor 文件中使用新的范围。

csharp 复制代码
@inherits OwningComponentBase
@using Microsoft.Extensions.DependencyInjection
......
DataContext Context => ScopedServices.GetService<DataContext>();

注释掉了 Inject 属性,并通过获得 DataContext 服务来设置 Context 属性的值。Micosof.Extensions.DependencyIniection 名称空间包含扩展方法,这样 IServiceProvider 对象更容易获取服务。

OwningComponentBase 类定义了一个额外的便利属性,来访问范围类型 T 的服务,如果组件只需要范围内单一的服务,该属性就可以很有用。

csharp 复制代码
@inherits OwningComponentBase<DataContext>
......
DataContext Context => Service;

有作用域的服务可通过名为 Service 的属性使用。在这个例子中,指定 DataContext 作为基类的类型参数。

无论使用哪个基类,结果都是Editor组件有自己的依赖注入作用域和自己的DataContext对象。List 组件没有修改,因此它将接收请求范围的 DataContext 对象。

3.2 理解重复查询问题

Blazor 尽可能高效地响应状态变化,但仍然必须呈现组件的内容,以确定应该发送到浏览器的变化,它会导致发送到数据库的查询数量急剧增加。在 Blazor/Foms 文件夹的 List.razor文件中添加一个按钮计数器来反映这个问题。

csharp 复制代码
......
@layout EmptyLayout
...... 
<button class="btn btn-primary" @onclick="@(() => Counter++)">Increment</button>
<span class="h5">Counter:@Counter</span>

@code
{
    ......
    public int Counter { get; set; } = 0;
}

请求 http://localhost:5000/forms。单击按钮并观察 ASP.NET Core 服务器的输出。每次单击该按钮时,都会调用事件处理程序,并向数据库发送一个新的数据库查询。

每次呈现组件时,EFCore 都向数据库发送两个相同的请求,即使在没有执行数据操作的地方单击了 Increment 按钮,也是如此。当使用 EF Core 时,就会出现这个问题,而 Blazor 则加重了这个问题。

管理组件中的查询

Blazor 和 EF Core 之间的交互对所有项目来说都不是问题,但是如果是的话,那么最好的方法是査询一次数据库,并且只对用户希望发生更新的操作再次进行査询。有些应用程产可能需要为用户提供显式选项来重新加载数据,特别是对于用户希望看到更新的应用程序。

在 Blazor/Forms 文件夹的 List.razor 文件中控制查询。

csharp 复制代码
<button class="btn btn-danger" @onclick="UpdateData">Update</button>
......
protected async override Task OnInitializedAsync()
{
    await UpdateData();
}
private async Task UpdateData() =>
    People = await Context.People.Include(p => p.Department)
        .Include(p => p.Location).ToListAsync<Person>();

UpdateData 方法执行相同的査询,但应用 ToListAsync 方法,该方法强制对 EFCore 查询进行评估。结果分配给 People 属性,可以重复读取,而不触发其他查询。为了让用户控制数据,添加了一个按钮,当单击 UpdateData 方法时,该按钮会调用该方法。重启请求 http:/localhost:5000/forms,然后单击 Increment 按钮。监视服务器的输出,将看到只有在组件初始化时才进行査询。要显式触发查询,请单击Update 按钮。

一些操作可能需要一个新的查询,这很容易执行。为了便于演示,向 List 组件添加了一个排序操作,该操作是使用和不使用新查询实现的。

csharp 复制代码
<button class="btn btn-danger" @onclick="(()=>UpdateData())">Update</button>
<button class="btn btn-info" @onclick="SortWithQuery">Sort (With Query)</button>
<button class="btn btn-info" @onclick="SortWithoutQuery">Sort (No Query)</button>
......
protected async override Task OnInitializedAsync()
{
    await UpdateData();
}
private IQueryable<Person> Query =>
    Context.People.Include(p => p.Department).Include(p => p.Location);
private async Task UpdateData(IQueryable<Person> query = null) =>
    People = await (query ?? Query).ToListAsync<Person>();
public async Task SortWithQuery()
{
    await UpdateData(Query.OrderBy(p => p.Surname));
}
public async Task SortWithoutQuery()
{
    People = People.OrderBy(p => p.Firstname).ToList<Person>();
}

EF Core 査询表示为 IQueryable 对象,允许该査询在发送到数据库服务器之前与附加的 LINQ 方法组合。示例中的新操作都使用 LINQ OrderBy 方法,但其中一个将其应用于 IQueryable ,然后对其进行评估,以使用 ToListAsync 方法发送查询。另一个操作将 OrderBy 方法应用于现有结果数据,对其进行排序,而不发送新的查询。要查看这两个操作,请重新请求 http://localhost:5000/forms,并单击 Sort 按钮。当单击 Sort (With Query)按钮时,将看到一条日志消息,指示查询已发送到数据库。

4 执行增删改查操作

4.1 创建 List 组件

List 组件包含需要的基本功能。在 List.razor 中删除了前面部分中不再需要的一些特性,并派加了允许用户导航到其他函数的按钮。

csharp 复制代码
<td class="text-center">
    <NavLink class="btn btn-sm btn-info"
             href="@GetDetailsUrl(p.PersonId)">
        Details
    </NavLink>
    <NavLink class="btn btn-sm btn-warning"
             href="@GetEditUrl(p.PersonId)">
        Edit
    </NavLink>
    <button class="btn btn-sm btn-danger"
    @onclick="@(() => HandleDelete(p))">
        Delete
    </button>
</td>
......
string GetEditUrl(long id) => $"/forms/edit/{id}";
string GetDetailsUrl(long id) => $"/forms/details/{id}";
public async Task HandleDelete(Person p)
{
    Context.Remove(p);
    await Context.SaveChangesAsync();
    await UpdateData();
}

对象的创建、査看和编辑操作导航到其他 URL,但是删除操作由 List 组件执行,注意在保存更改后重新加载数据,以将更改反映给用户。

4.2 创建 Details 组件

为 Blazor/Forms 文件夹添加一个名为 Details.razor 的 Blazor 组件。此组件显示的所有输入元素都被禁用,这意味着不需要处理事件或处理用户输入。

csharp 复制代码
@page "/forms/details/{id:long}"
@layout EmptyLayout
@inherits OwningComponentBase<DataContext>

<h4 class="bg-info text-center text-white p-2">Details</h4>

<div class="form-group">
    <label>ID</label>
    <input class="form-control" value="@PersonData.PersonId" disabled />
</div>
<div class="form-group">
    <label>Firstname</label>
    <input class="form-control" value="@PersonData.Firstname" disabled />
</div>
<div class="form-group">
    <label>Surname</label>
    <input class="form-control" value="@PersonData.Surname" disabled />
</div>
<div class="form-group">
    <label>Department</label>
    <input class="form-control" value="@PersonData.Department?.Name" disabled />
</div>
<div class="form-group">
    <label>Location</label>
    <input class="form-control"
           value="@($"{PersonData.Location?.City}, {PersonData.Location?.State}")"
           disabled />
</div>
<div class="text-center">
    <NavLink class="btn btn-info" href="@EditUrl">Edit</NavLink>
    <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
</div>

@code
{
    [Inject]
    public NavigationManager NavManager { get; set; }

    DataContext Context => Service;

    [Parameter]
    public long Id { get; set; }

    public Person PersonData { get; set; } = new Person();

    protected async override Task OnParametersSetAsync()
    {
        PersonData = await Context.People.Include(p => p.Department)
            .Include(p => p.Location).FirstOrDefaultAsync(p => p.PersonId == Id);
    }

    public string EditUrl => $"/forms/edit/{Id}";
}

4.3 创建 Editor 组件

其余特性将由 Editor 组件处理。代码清单删除了前面示例中不再需要的特性,并添加了对创建和编辑对象的支持,包括持久化数据。

csharp 复制代码
@page "/forms/edit/{id:long}"
@page "/forms/create"
@layout EmptyLayout
@inherits OwningComponentBase<DataContext>

<link href="/blazorValidation.css" rel="stylesheet" />

<h4 class="bg-@Theme text-center text-white p-2">@Mode</h4>

<EditForm Model="PersonData" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    @if (Mode == "Edit")
    {
        <div class="form-group">
            <label>ID</label>
            <InputNumber class="form-control"
            @bind-Value="PersonData.PersonId" readonly />
        </div>
    }
    <div class="form-group">
        <label>Firstname</label>
        <ValidationMessage For="@(() => PersonData.Firstname)" />
        <InputText class="form-control" @bind-Value="PersonData.Firstname" />
    </div>
    <div class="form-group">
        <label>Surname</label>
        <ValidationMessage For="@(() => PersonData.Surname)" />
        <InputText class="form-control" @bind-Value="PersonData.Surname" />
    </div>
    <div class="form-group">
        <label>Deptartment</label>
        <ValidationMessage For="@(() => PersonData.DepartmentId)" />
        <CustomSelect TValue="long" Values="Departments"
                      Parser="@(str => long.Parse(str))"
        @bind-Value="PersonData.DepartmentId">
            <option selected disabled value="0">Choose a Department</option>
        </CustomSelect>
    </div>
    <div class="form-group">
        <label>Location</label>
        <ValidationMessage For="@(() => PersonData.LocationId)" />
        <CustomSelect TValue="long" Values="Locations"
                      Parser="@(str => long.Parse(str))"
        @bind-Value="PersonData.LocationId">
            <option selected disabled value="0">Choose a Location</option>
        </CustomSelect>
    </div>
    <div class="text-center">
        <button type="submit" class="btn btn-@Theme">Save</button>
        <NavLink class="btn btn-secondary" href="/forms">Back</NavLink>
    </div>
</EditForm>

@code
{

    [Inject]
    public NavigationManager NavManager { get; set; }

    DataContext Context => Service;

    [Parameter]
    public long Id { get; set; }

    public Person PersonData { get; set; } = new Person();

    public IDictionary<string, long> Departments { get; set; }
        = new Dictionary<string, long>();
    public IDictionary<string, long> Locations { get; set; }
        = new Dictionary<string, long>();

    protected async override Task OnParametersSetAsync()
    {
        if (Mode == "Edit")
        {
            PersonData = await Context.People.FindAsync(Id);
        }
        Departments = await Context.Departments
            .ToDictionaryAsync(d => d.Name, d => d.Departmentid);
        Locations = await Context.Locations
            .ToDictionaryAsync(l => $"{l.City}, {l.State}", l => l.LocationId);
    }

    public string Theme => Id == 0 ? "primary" : "warning";
    public string Mode => Id == 0 ? "Create" : "Edit";

    public async Task HandleValidSubmit()
    {
        if (Mode == "Create")
        {
            Context.Add(PersonData);
        }
        await Context.SaveChangesAsync();
        NavManager.NavigateTo("/forms");
    }
}

添加了对新 URL 的支持,并使用引导 CSS 主题来区分创建新对象和编辑现有对象。删除了验证摘要,以便只显示属性级别的验证消息,并添加了通过 EF Core 存储数据的支持。与使用控制器或 Razor Pages 创建的表单应用程序不同,本例不必处理模型绑定,因为 Blazor。直接处理 EF Core 从初始数据库査询生成的对象。重启并请求 http://localhost:5000/forms。将看到 Person 对象列表,单击 Create、Details、Edi和 Delete 按钮,将允许处理数据库中的数据。

相关推荐
hweiyu0016 分钟前
Maven 私库
java·maven
Boilermaker199223 分钟前
【Java EE】SpringIoC
前端·数据库·spring
Super Rookie24 分钟前
Spring Boot 企业项目技术选型
java·spring boot·后端
写不出来就跑路29 分钟前
Spring Security架构与实战全解析
java·spring·架构
中微子34 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
ZeroNews内网穿透1 小时前
服装零售企业跨区域运营难题破解方案
java·大数据·运维·服务器·数据库·tcp/ip·零售
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript