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

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

1 准备工作

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

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

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

<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 对象。

@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 对象列表。

@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 类定义的两个属性。

<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 组件。

@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 属性选择值。

@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-errorsValidationSummary 组件生成一个 ul 元素,该元素被分配给这个类,并且是验证消息摘要的顶级容器
validation-messageValidationSummary 组件使用为每个验证消息分配给这个类的 il 元素来填充它的 ul 元素。ValidationMessage 组件为这个类的属性级消息呈现一个分配给它的 div 元素

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

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

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

.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 文件中应用验证组件。

@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 文件中应用验证属性。

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 事件。

@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 连接来显示多个组件。这将导致多个组件共享单个依赖注入范围,一个组件所做的更改将影响其他组件,即使这些更改没有写入数据库。image.png1.丢弃未保存的数据更改
  如果在组件之间共享上下文,那么可采用这种方法,并确保组件在销毁时放弃任何更改。 在 Blazor/Forms 文件夹的 Editor.razor 文件中丢弃未保存的数据更改。

@implements IDisposable
......
public void Dispose() => Context.Entry(PersonData).State = EntityState.Detached;

2.创建新的依赖注入范围
  若想保留其余部分使用的模型,就必须创建新的依赖注入范围。并让每个组件接收子级的 EF Core 上下文对象。这是通过使用 @ inherits 表达是将组件的基类设置为 OwningComponentBase 来完成的。
  OwningComponentBase 类定义了组件继承的 ScopedServices 属性,提供了一个可用于获取服务的 IServiceProvider 对象,该服务在一个特定于组件的生命周期的作用域中创建,该范围不会与其他任何组件共享。
  在 Blazor/Forms 文件夹的 Editor.razor 文件中使用新的范围。

@inherits OwningComponentBase
@using Microsoft.Extensions.DependencyInjection
......
DataContext Context => ScopedServices.GetService<DataContext>();

  注释掉了 Inject 属性,并通过获得 DataContext 服务来设置 Context 属性的值。Micosof.Extensions.DependencyIniection 名称空间包含扩展方法,这样 IServiceProvider 对象更容易获取服务。
  OwningComponentBase 类定义了一个额外的便利属性,来访问范围类型 T 的服务,如果组件只需要范围内单一的服务,该属性就可以很有用。

@inherits OwningComponentBase<DataContext>
......
DataContext Context => Service;

  有作用域的服务可通过名为 Service 的属性使用。在这个例子中,指定 DataContext 作为基类的类型参数。
  无论使用哪个基类,结果都是Editor组件有自己的依赖注入作用域和自己的DataContext对象。List 组件没有修改,因此它将接收请求范围的 DataContext 对象。image.png

3.2 理解重复查询问题

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

......
@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 文件中控制查询。

<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 组件添加了一个排序操作,该操作是使用和不使用新查询实现的。

<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 中删除了前面部分中不再需要的一些特性,并派加了允许用户导航到其他函数的按钮。

<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 组件。此组件显示的所有输入元素都被禁用,这意味着不需要处理事件或处理用户输入。

@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 组件处理。代码清单删除了前面示例中不再需要的特性,并添加了对创建和编辑对象的支持,包括持久化数据。

@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 按钮,将允许处理数据库中的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40262.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型

论文全称&#xff1a;PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题&#xff1a;如何在检索增强式问答&#xff08;ReQA&#xff09;任务中&#xff0c;利用大型语言模型&#xf…

【C语言入门】初识C语言:掌握编程的基石

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言入门 &#x1f4d2;1. 选择…

单片机关键任务优先级的实现学习

与总体产品联调时&#xff0c;需要各个单机系统严格按照总体要求&#xff0c;进行数据输出&#xff0c;时间的偏差将出现系统异常&#xff0c;控制失败等不稳定情况产生&#xff0c;甚至影响到产品安全。 因此必须确保某些关键任务的优先执行。单片机任务优先级一般有两种方式…

[小试牛刀-习题练]《计算机组成原理》之指令系统

一、选择题 0.【指令-课本习题】某计算机按字节编址&#xff0c;指令字长固定且只有两种指令格式&#xff0c;其中三地址指令29条&#xff0c;二地址指令107条&#xff0c;每个地址字段为6位&#xff0c;则指令字长至少应该是&#xff08;A&#xff09; A.24位 B. 26位 C. 28位…

ctfshow web sql注入 web242--web249

web242 into outfile 的使用 SELECT ... INTO OUTFILE file_name[CHARACTER SET charset_name][export_options]export_options:[{FIELDS | COLUMNS}[TERMINATED BY string]//分隔符[[OPTIONALLY] ENCLOSED BY char][ESCAPED BY char]][LINES[STARTING BY string][TERMINATED…

Android系统层屏蔽弹出停止运行对话框

项目场景&#xff1a; 车载项目&#xff0c;ATC8257-Android9.0系统平台&#xff0c;福田汽车P3系列项目 项目使用高德公版地图前提是无法获得任何高德定制服务&#xff0c;每次刷完机去切换语言系统会弹出"高德地图已停止运行"弹窗&#xff0c;严重影响用户使用体…

【第三版 系统集成项目管理工程师】第6章 数据工程

持续更新。。。。。。。。。。。。。。。 【第三版】第六章 数据工程 6.1数据采集和预处理6.1.1 数据采集 P2346.1.2 数据预处理6.1.3 数据预处理方法1.缺失数据的预处理-P2352.异常数据的预处理-P2363.不一致数据的预处理-P2364.重复数据的预处理-P2365.格式不符数据的预处理…

UE5 03-物体碰撞检测

在你需要碰撞的物体上添加一个碰撞检测组件 碰撞预设 设置为NoCollision,这样移动过程中就不会有物理碰撞阻挡效果,只负责检测是否碰撞,比较难解释,如果学过Unity的话,可以把它理解成 Collision 为 Trigger

My sql 安装,环境搭建

以下以MySQL 8.0.36为例。 一、下载软件 1.下载地址官网&#xff1a;https://www.mysql.com 2. 打开官网&#xff0c;点击DOWNLOADS 然后&#xff0c;点击 MySQL Community(GPL) Downloads 3. 点击 MySQL Community Server 4.点击Archives选择合适版本 5.选择后下载第二个…

密码学复习

目录 基础 欧拉函数 欧拉函数φ(n)定义 计算方法的技巧 当a=a_1*a_2*……*a_n时 欧拉定理 剩余系 一些超简单密码 维吉尼亚 密钥fox 凯撒(直接偏移) 凯特巴氏(颠倒字母表) 摩斯密码(字母对应电荷线) 希尔(hill)密码 一些攻击 RSA 求uf+vg=1 快速幂模m^…

Python | Leetcode Python题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:def robRange(start: int, end: int) -> int:first nums[start]second max(nums[start], nums[start 1])for i in range(start 2, end 1):first, second second, max(fi…

Bootstrap 图片

Bootstrap 图片 Bootstrap 是一个流行的前端框架,它提供了一套丰富的工具和组件,用于快速开发响应式和移动优先的网页。在本文中,我们将探讨如何使用 Bootstrap 来处理和展示图片,包括图片的响应式设计、图片样式和图片布局。 响应式图片 Bootstrap 通过其栅格系统提供了…

人工智能在物流领域的应用,智慧物流大有可为!

物流是复合型服务产业&#xff0c;作为经济的重要组成部分&#xff0c;受到人工智能技术的深刻影响。物流行业的人工智能应用也将助推人工智能技术的发展&#xff0c;人工智能技术应用于物流行业&#xff0c;应用领域包括以下方向&#xff1a; 第一、车货匹配系统 使用人工智…

AI智能音箱用2×15W立体声功放芯片NTP8918

智能音箱是近年来非常受欢迎的智能家居产品之一&#xff0c;它集成了人工智能技术和音频技术&#xff0c;能够为用户提供语音助手、音乐播放、智能家居控制等多种功能。其中&#xff0c;音频输出是智能音箱的核心功能之一&#xff0c;而功放芯片则是实现音频放大的关键组成部分…

00 如何根据规律在变化中求发展?

你好&#xff0c;我是周大壮。目前&#xff0c;我已在搜索推荐等算法技术领域从事研发近 10 年&#xff0c;做过诸多流量分发领域的算法技术工作。 如今任公司同城的算法架构师、技术委员会人工智能分会委员、公司本地服务事业群算法策略部负责人&#xff0c;我主要负责公司集…

计算机网络之局域网

目录 1.局域网的基本概念 2.LAN的特性 3.局域网特点 4.拓扑结构 5.传输媒体的选择 6.传输媒体 7.传输技术 8.传输技术距离问题 9.LAN的逻辑结构 10.局域网工作原理 上篇文章内容&#xff1a;OSI七层体系结构 1.局域网的基本概念 局域网 是将分散在有限地 理范围内&…

51单片机定时器/计数器

欢迎入群共同学习交流 时间记录&#xff1a;2024/7/3 一、电路原理图 51单片机具有两个定时器T0、T1 二、知识点介绍 1、寄存器介绍 &#xff08;1&#xff09;TMOD方式寄存器 T0为例介绍&#xff1a; 工作方式选择位M1、M0 常用方式为方式1、方式2&#xff0c;方式2低…

【按键精灵】#1找图、找色、移动和点击

关键字&#xff1a; 找图、找色、移动和点击 找图&#xff1a; 抓抓截图&#xff0c;添加到附件 FindPic 0,0,1024,768,"Attachment:\Xmind.bmp",0.9,intX,intYIf intX > 0 And intY > 0 Then TracePrint "找到了" //找到打印Else TracePri…

Python | Leetcode Python题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution:def shortestPalindrome(self, s: str) -> str:n len(s)fail [-1] * nfor i in range(1, n):j fail[i - 1]while j ! -1 and s[j 1] ! s[i]:j fail[j]if s[j 1] s[i]:fail[i] j 1best -1for i in range(n - 1,…

宠物商城系统7000字文档20页ppt java项目javaweb项目ssm项目jsp项目java课程设计java毕业设计

文章目录 宠物商城系统一、项目演示二、项目介绍三、7000字项目文档四、20页ppt五、部分功能截图六、部分代码展示七、底部获取项目源码带7000字文档和20页ppt&#xff08;9.9&#xffe5;带走&#xff09; 宠物商城系统 一、项目演示 宠物商店 二、项目介绍 语言: Java 数据…