当前位置: 首页 > news >正文

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

相关文章:

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

本章将描述 Blazor 为处理 HTML 表单提供的特性&#xff0c;包括对数据验证的支持。 1 准备工作 继续使用上一章项目。   创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。 inherits LayoutComponentBase<div …...

C++ 仿QT信号槽二

// 实现原理 // 每个signal映射到bitset位&#xff0c;全集 // 每个slot做为signal的bitset子集 // signal全集触发&#xff0c;标志位有效 // flip将触发事件队列前置 // slot检测智能指针全集触发的标志位&#xff0c;主动运行子集绑定的函数 // 下一帧对bitset全集进行触发清…...

联合概率密度函数

目录 1. 什么是概率密度由联合概率密度求概率参考链接 1. 什么是概率密度 概率密度到底在表达什么&#xff1f; 外卖在20-40分钟内送达的概率 随机变量落在[20,40]之间的概率。下图中&#xff0c;对总面积做规范化处理&#xff0c;令总面积1&#xff0c; f ( x ) f(x) f(x)则成…...

【Java10】成员变量与局部变量

Java中的变量只有两种&#xff1a;成员变量和局部变量。 和C不同&#xff0c;没有全局变量了。 成员变量&#xff0c;field&#xff0c;我习惯称之为**”属性“**&#xff08;但这些年&#xff0c;因为attribute更适合被叫做属性&#xff0c;所以渐渐不这么叫了&#xff09;。 …...

Spring Session与分布式会话管理详解

随着微服务架构的普及&#xff0c;分布式系统中的会话管理变得尤为重要。传统的单点会话管理已经不能满足现代应用的需求。本文将深入探讨Spring Session及其在分布式会话管理中的应用。 什么是Spring Session&#xff1f; Spring Session是一个用于管理HttpSession的Spring框…...

从0开始学习pyspark--Spark DataFrame数据的选取与访问[第5节]

在PySpark中&#xff0c;选择和访问数据是处理Spark DataFrame的基本操作。以下是一些常用的方法来选择和访问DataFrame中的数据。 选择列&#xff08;Selecting Columns&#xff09;: select: 用于选择DataFrame中的特定列。selectExpr: 用于通过SQL表达式选择列。 df.select…...

Fastjson首字母大小写问题

1、问题 使用Fastjson转json之后发现首字母小写。实体类如下&#xff1a; Data public class DataIdentity {private String BYDBSM;private String SNWRSSJSJ;private Integer CJFS 20; } 测试代码如下&#xff1a; public static void main(String[] args) {DataIdentit…...

GuLi商城-商品服务-API-品牌管理-效果优化与快速显示开关

<template><div class"mod-config"><el-form :inline"true" :model"dataForm" keyup.enter.native"getDataList()"><el-form-item><el-input v-model"dataForm.key" placeholder"参数名&qu…...

如何成为C#编程高手?

成为C#编程高手需要时间、实践和持续的学习。以下是一些建议&#xff0c;可以帮助你提升C#编程技能&#xff1a; 深入理解基础知识&#xff1a; 确保你对C#的基本语法、数据类型、控制结构、面向对象编程&#xff08;OOP&#xff09;原则有深刻的理解。学习如何使用Visual Stud…...

SpringBoot学习06-[SpringBoot与AOP、SpringBoot自定义starter]

SpringBoot自定义starter SpringBoot与AOPSpringBoot集成Mybatis-整合druid在不使用启动器的情况下&#xff0c;手动写配置类进行整合使用启动器的情况下,进行整合 SpringBoot启动原理源码解析创建SpringApplication初始化SpringApplication总结 启动 SpringBoot自定义Starter定…...

Maven - 在没有网络的情况下强制使用本地jar包

文章目录 问题解决思路解决办法删除 _remote.repositories 文件代码手动操作步骤验证 问题 非互联网环境&#xff0c;无法从中央仓库or镜像里拉取jar包。 服务器上搭建了一套Nexus私服。 Nexus私服故障&#xff0c;无法连接。 工程里新增了一个Jar的依赖&#xff0c; 本地仓…...

JAVA--JSON转换工具类

JSON转换工具类 import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackso…...

每日复盘-20240705

今日关注&#xff1a; 20240705 六日涨幅最大: ------1--------300391--------- 长药控股 五日涨幅最大: ------1--------300391--------- 长药控股 四日涨幅最大: ------1--------300391--------- 长药控股 三日涨幅最大: ------1--------300391--------- 长药控股 二日涨幅最…...

MySQL 一些用来做比较的函数

目录 IF&#xff1a;根据不同条件返回不同的值 CASE&#xff1a;多条件判断&#xff0c;类似于Switch函数 IFNULL&#xff1a;用于检查一个值是否为NULL&#xff0c;如果是&#xff0c;则用指定值代替 NULLIF&#xff1a;比较两个值&#xff0c;如果相等则返回NULL&#xff…...

一个使用率超高的大数据实验室是如何练成的?

厦门大学嘉庚学院“大数据应用实训中心”&#xff08;以下简称“实训中心”&#xff09;自2022年建成以来&#xff0c;已经成为支撑“大数据专业”复合型人才培养的重要支撑&#xff0c;目前实训中心在专业课程实验教学、项目实训、数据分析类双创比赛、毕业设计等方面都有深入…...

Chiasmodon:一款针对域名安全的公开资源情报OSINT工具

关于Chiasmodon Chiasmodon是一款针对域名安全的公开资源情报OSINT工具&#xff0c;该工具可以帮助广大研究人员从各种来源收集目标域名的相关信息&#xff0c;并根据域名、Google Play应用程序、电子邮件地址、IP地址、组织和URL等信息进行有针对性的数据收集。 该工具可以提…...

如何在Java中实现PDF生成

如何在Java中实现PDF生成 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在软件开发和企业应用中&#xff0c;生成PDF文档是一项常见的需求。Java作为一种强大…...

Redis 的缓存淘汰策略

Redis 作为一个高性能的内存数据库&#xff0c;提供了多种缓存淘汰策略&#xff08;也称为过期策略或驱逐策略&#xff09;&#xff0c;用于管理内存使用。当 Redis 达到其内存限制时&#xff0c;系统会根据配置的策略删除一些数据&#xff0c;以释放内存空间。以下是 Redis 支…...

音乐播放器

目录 一、设计目标二、实现流程1. 数据库操作2. 后端功能实现3. 前端UI界面实现4. 程序入口 三、项目收获 一、设计目标 1. 模拟网易云音乐&#xff0c;实现本地音乐盒。 2. 功能分析&#xff1a; 登录功能窗口显示加载本地音乐建立播放列表播放音乐删除播放列表音乐 3.设计思…...

三星组件新的HBM开发团队加速HBM研发

为应对人工智能(AI)市场扩张带来的对高性能存储解决方案需求的增长&#xff0c;三星电子在其设备解决方案(DS)部门内部成立了全新的“HBM开发团队”&#xff0c;旨在提升其在高带宽存储器(HBM)领域的竞争力。根据Business Korea的最新报告&#xff0c;该团队将专注于推进HBM3、…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

FOPLP vs CoWoS

以下是 FOPLP&#xff08;Fan-out panel-level packaging 扇出型面板级封装&#xff09;与 CoWoS&#xff08;Chip on Wafer on Substrate&#xff09;两种先进封装技术的详细对比分析&#xff0c;涵盖技术原理、性能、成本、应用场景及市场趋势等维度&#xff1a; 一、技术原…...