如何基于EF构建Blazor通用可复用表单字段组件?
通用单属性编辑组件实现咨询
需求场景
我有一个包含多表多列的数据库应用,不同页面需要编辑实体的不同字段:比如客户只能修改自身名称,而信用额度仅能在管理后台修改。如果为每个字段单独创建绑定数据表的表单组件,后续修改表单样式或添加输入功能时,维护成本会很高。
因此我想做一个通用的单属性表单行组件,可以在不同页面中按需引入需要编辑的字段,比如在admin/customers/{CustomerId:int}页面中希望这样使用:
<h2>编辑客户</h2> <FormField Entity="currentCustomer" Property="@(() => nameof(currentCustomer.Name))" /> <FormField Entity="currentCustomer" Property="@(() => nameof(currentCustomer.CreditLimit))" />
这个组件需要具备以下能力:
- 自带
EditContext处理表单状态 - 支持数据验证
- 完成数据库更新操作
父页面负责加载实体并传递给该组件。
我知道可以用@typeparam TEntity实现泛型组件,但不清楚如何通过泛型来读取和更新实体的指定属性。
现有单属性组件示例
下面是我针对Employee实体的BankAccount字段写的组件代码,我希望把它改成适用于所有数据库列的通用版本:
@if (editContext == null) { <Loading /> } else { <EditForm EditContext="editContext"> <FormRow> <LeftColumn> <FormFieldLabel For="@(() => editModel.Field)" /> </LeftColumn> <MiddleColumn> <div class="input-group"> <InputText @bind-Value="editModel.Field" class="form-control" /> @if (updating) { <div class="input-group-text text-primary border-primary"> <div class="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div> </div> } @if (error) { <div class="input-group-text text-danger border-danger"><Svg Name="exclamationmark" /></div> } @if (success) { <div class="input-group-text text-success border-success"><Svg Name="checkmark" /></div> } <span class="btn btn-outline-primary" @onclick="Reset"><Svg Name="arrowundo" /></span> </div> </MiddleColumn> <RightColumn> <ValidationMessage For="@(() => editModel.Field)" class="text-danger" /> </RightColumn> </FormRow> @if (exception != null) { <FormRow> <MiddleColumn> <button type="submit" class="btn btn-sm btn-outline-success">重试</button> <button type="button" class="btn btn-sm btn-outline-primary" @onclick="Reset">取消</button> <button type="button" class="btn btn-sm btn-outline-danger" @onclick="@(() => showException = true)">显示异常</button> @if (showException) { <p><br />@exception.Message</p> } </MiddleColumn> </FormRow> } <DataAnnotationsValidator /> </EditForm> } @code { [Parameter] public required Employee Entity { get; set; } [Parameter] public required ApplicationDbContext DbContext { get; set; } private EditContext? editContext; private ValidationMessageStore? messageStore; private EditModel editModel = new(); private int updateResult = 0; private bool updating = false; private bool error = false; private bool success = false; private bool showException = false; private Exception? exception; private string? initialValue; protected override void OnInitialized() { editModel.Field = Entity.BankAccount; initialValue = Entity.BankAccount; editContext = new(editModel); editContext.OnFieldChanged += HandleFieldChanged; messageStore = new(editContext); } async void HandleFieldChanged(object? sender, FieldChangedEventArgs e) { updating = false; error = false; success = false; messageStore!.Clear(() => editModel.Field!); showException = false; exception = null; if (editContext!.Validate()) { updating = true; Entity.BankAccount = editModel.Field; try { DbContext.Entry(Entity).State = EntityState.Modified; updateResult = await DbContext.SaveChangesAsync(); } catch (Exception ex) { updating = false; error = true; messageStore!.Add(() => editModel.Field!, "保存时发生错误。"); exception = ex; } } else { error = true; } if (updateResult > 0) { updating = false; success = true; } StateHasChanged(); } void Reset() { updating = false; error = false; success = true; editModel.Field = initialValue; messageStore!.Clear(() => editModel.Field!); showException = false; exception = null; editContext!.NotifyFieldChanged(editContext.Field(nameof(editModel.Field))); } private class EditModel { [MaxLength(20), Display(Name = "银行账户")] public string? Field { get; set; } } }
父页面加载组件方式
@if (employee == null) { <Loading /> } else { <BankAccount Entity="employee" DbContext="context" /> } @code { [Parameter] public int EmployeeId { get; set; } private ApplicationDbContext? context { get; set; } private Employee? employee; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { context = DbFactory.CreateDbContext(); employee = await context.Employees.FindAsync(EmployeeId); if (employee == null) { // 处理找不到员工的逻辑 } StateHasChanged(); } } public void Dispose() { context?.Dispose(); } }
我不确定当前的思路是否正确,希望能得到实现通用组件的具体方法,或者更优的方案建议。
内容的提问来源于stack exchange,提问作者Simen Arntsen
相关产品推荐
相关产品推荐

