当前位置: 首页 > 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、…...

图书馆数据仓库

目录 1.数据仓库的数据来源为业务数据库&#xff08;mysql&#xff09; 初始化脚本 init_book_result.sql 2.通过sqoop将mysql中的业务数据导入到大数据平台&#xff08;hive&#xff09; 导入mysql数据到hive中 3.通过hive进行数据计算和数据分析 形成数据报表 4.再通过sq…...

基于uniapp(vue3)H5附件上传组件,可限制文件大小

代码&#xff1a; <template><view class"upload-file"><text>最多上传5份附件&#xff0c;需小于50M</text><view class"" click"selectFile">上传</view></view><view class"list" v…...

Phoenix Omid Timestamp Oracle 组件实现原理

Omid Timestamp Oracle 组件实现原理 作用 生成全局单调递增的时间戳&#xff0c;支持获取操作和崩溃恢复。 功能 1.生成全局单调递增的时间戳(支持崩溃恢复)apinext返回下一个时间戳getLast返回最后一个分配的时间戳(当前时间戳)实现方式TimestampOracleImpl单调递增的时间…...

Lex Fridman Podcast with Andrej Karpathy

我不太喜欢Lex Fridman的声音&#xff0c;总觉得那让人昏昏欲睡&#xff0c; 但无奈他采访的人都太大牌了&#xff0c;只能去听。但是听着听着&#xff0c;就会觉得有深度的采访这些人&#xff0c;似乎也只有他这种由研究员背景的人能干&#xff0c; 另&#xff0c;他提的问题确…...

力扣1895.最大的幻方

力扣1895.最大的幻方 求前缀和暴力枚举幻方边长 求行列前缀和 class Solution {public:int largestMagicSquare(vector<vector<int>>& grid) {int n grid.size() , m grid[0].size();vector<vector<int>> rowsum(n,vector<int>(m));for…...

【C++】 解决 C++ 语言报错:Segmentation Fault

文章目录 引言 段错误&#xff08;Segmentation Fault&#xff09;是 C 编程中常见且令人头疼的错误之一。段错误通常发生在程序试图访问未被允许的内存区域时&#xff0c;导致程序崩溃。本文将深入探讨段错误的产生原因、检测方法及其预防和解决方案&#xff0c;帮助开发者在…...

【linuxC语言】手撕Http协议之程序框架

文章目录 前言提示基本框架主函数一个小问题代码概况多线程版本单线程版本总结前言 在现代网络编程中,HTTP协议无疑是最常用的协议之一。它是互联网上应用最为广泛的一种网络协议。理解HTTP协议并能够手动实现它,对于深入理解网络编程和提高编程技能都有着重要的意义。本文将…...

溶解氧(DO)理论指南(1)

转载自梅特勒官网资料&#xff0c;仅用于学习交流&#xff0c;侵权则删&#xff01; 溶解氧理论指南 1 溶解氧(DO)原理1.1 溶解氧和分压1.2 氧气在水中的溶解度1.3 溶解氧对生物的重要性1.4 溶解氧对工业的重要性 1 溶解氧(DO)原理 氧是宇宙中第三大常见元素&#xff0c;也是…...

Mysql中常用函数的使用示例

场景 基础知识回顾&#xff1a;mysql中常用函数的使用示例。 注&#xff1a; 博客&#xff1a;霸道流氓气质-CSDN博客 实现 数学函数 -- ABS(x)返回x的绝对值 SELECT ABS(-1),ABS(2); -- PI()返回圆周率 SELECT PI(); -- SQRT(x)返回非负数x的二次方根 SELECT SQRT(4); -…...

开源205W桌面充电器,140W+65W升降压PD3.1快充模块(2C+1A口),IP6557+IP6538

开源一个基于IP6557和IP6538芯片的205W升降压快充模块&#xff08;140W65W&#xff09;&#xff0c;其中一路C口支持PD3.1协议&#xff0c;最高输出28V5A&#xff0c;另一路是A口C口&#xff0c;最高输出65W&#xff08;20V3.25A&#xff09;&#xff0c;可搭配一个24V10A的开关…...

Java中的内存数据库与缓存技术

Java中的内存数据库与缓存技术 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. 内存数据库的概念与优势 1.1 什么是内存数据库&#xff1f; 内存数据库是…...

GUKE万能工具箱(附带源码)

GUKE万能工具箱&#xff08;附带源码&#xff09; 效果图部分源码领取完整源码下期更新 效果图 部分源码 <!DOCTYPE html> <html><head><meta charset"utf-8" name"viewport" content"widthdevice-width, initial-scale1"…...

FFmpeg开发笔记(四十二)使用ZLMediaKit开启SRT视频直播服务

《FFmpeg开发实战&#xff1a;从零基础到短视频上线》一书在第10章介绍了轻量级流媒体服务器MediaMTX&#xff0c;通过该工具可以测试RTSP/RTMP等流媒体协议的推拉流。不过MediaMTX的功能实在是太简单了&#xff0c;无法应用于真实直播的生产环境&#xff0c;真正能用于生产环境…...

spring-boot-starter-data-redis是否支持reactive响应式编程

开源项目SDK&#xff1a;https://github.com/mingyang66/spring-parent 个人文档&#xff1a;https://mingyang66.github.io/raccoon-docs/#/ spring-boot-starter-data-redis&#xff1a; 使用传统的基于阻塞的I/O编程模型&#xff0c;这意味着当你调用Redis操作时&#xff0…...

Java后端每日面试题(day3)

目录 Spring中Bean的作用域有哪些&#xff1f;Spring中Bean的生命周期Bean 是线程安全的吗&#xff1f;了解Spring Boot中的日志组件吗&#xff1f; Spring中Bean的作用域有哪些&#xff1f; Bean的作用域&#xff1a; singleton&#xff1a;单例&#xff0c;Spring中的bean默…...

[单master节点k8s部署]18.监控系统构建(三)Grafana安装

Grafana是一个跨平台的开源的度量分析和可视化工具。支持多种数据源&#xff0c;比如OpenTSDB&#xff0c;Prometheus&#xff0c;ElasticResearch&#xff0c;Cloudwatch等。 Grafana安装 通过yaml配置grafana的pod和service&#xff0c;grafana工作在kube-system的命名空间…...

【JavaScript脚本宇宙】优化你的Web色彩:精选JavaScript颜色工具对比

万能色彩助手&#xff1a;详解最受欢迎的JavaScript颜色库 前言 在现代Web开发中&#xff0c;颜色处理和转换是一个不可忽视的环节。无论是网站设计、数据可视化还是用户界面开发&#xff0c;都离不开对颜色的精确控制和转换。为了满足这一需求&#xff0c;众多JavaScript库应…...

用html+css设计一个列表清单小卡片

目录 简介: 效果图: 源代码: 可能的问题: 简介: 这个HTML代码片段是一个简单的列表清单设计。它包含一个卡片元素(class为"card"),内部包含一个无序列表(ul),列表项(li)前面有一个特殊的符号(△)。整个卡片元素设计成300px宽,150px高,具有圆角边…...

day11_homework_need2submit

Homework 编写—个将ts或mp4中视频文件解码到yuv的程序 yuv数据可以使用如下命令播放: ffplay -i output yuv-pix_fmt yuv420p-s 1024x436 要求: ffmpeg解析到avpacket并打印出pts和dts字段完成解码到avframe并打印任意字段完成yuv数据保存 // teminal orders on bash cd ex…...

昇思MindSpore学习总结九——FCN语义分割

1、语义分割 图像语义分割&#xff08;semantic segmentation&#xff09;是图像处理和机器视觉技术中关于图像理解的重要一环&#xff0c;AI领域中一个重要分支&#xff0c;常被应用于人脸识别、物体检测、医学影像、卫星图像分析、自动驾驶感知等领域。 语义分割的目的是对图…...

js数据库多级分类按树形结构打印

可以使用 JavaScript 来按层级打印 categories 数组。首先&#xff0c;需要将这个数组转换成一个树形结构&#xff0c;然后再进行递归或者迭代来打印每个层级的内容。 以下是一个示例代码&#xff0c;用来实现这个功能&#xff1a; const categories [{ id: 2, name: "…...

centos下编译安装redis最新稳定版

一、目标 编译安装最新版的redis 二、安装步骤 1、redis官方下载页面 Downloads - Redis 2、下载最新版的redis源码包 注&#xff1a;此时的最新稳定版是 redis 7.2.5 wget https://download.redis.io/redis-stable.tar.gz 3、安装编译环境 yum install -y gcc gcc-c …...

如何让自动化测试更加灵活简洁?

简化的架构对于自动化测试和主代码一样重要。冗余和不灵活性可能会导致一些问题&#xff1a;比如 UI 中的任何更改都需要更新多个文件&#xff0c;测试可能在功能上相互重复&#xff0c;并且支持新功能可能会变成一项耗时且有挑战性的工作来适应现有测试。 页面对象模式如何理…...

linux 下载依赖慢和访问github代码慢

1 pip install 下载依赖慢&#xff0c;添加清华镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 2 git 出现错误 Could not resolve host: github.com 原来是因为github.com没有被主机给解析&#xff0c; 第一步 先 ping 看一下主机地址 …...

奥比中光astra_pro相机使用记录

一、信息获取 1、官网 用于了解产品信息 http://www.orbbec.com.cn/sys/37.html 2、开发者社区 咨询问题下载开发部https://developer.orbbec.com.cn/ 二 、windowvs19 1、相机型号 orbbec_astro_pro 根据对应的型号找到需要的包工具 踩坑1&#xff0c;因为这个相机型号…...

【MindSpore学习打卡】应用实践-计算机视觉-深入解析 Vision Transformer(ViT):从原理到实践

在近年来的深度学习领域&#xff0c;Transformer模型凭借其在自然语言处理&#xff08;NLP&#xff09;中的卓越表现&#xff0c;迅速成为研究热点。尤其是基于自注意力&#xff08;Self-Attention&#xff09;机制的模型&#xff0c;更是推动了NLP的飞速发展。然而&#xff0c…...

Debezium系列之:支持在一个数据库connector采集中过滤某些表的删除事件

Debezium系列之:支持在一个数据库connector采集中过滤某些表的删除事件 一、需求二、相关技术三、参数设置四、消费数据一、需求 在一个数据库的connector中采集了多张表,部分表存在数据归档的业务场景,会定期从表中删除历史数据,希望能过滤掉存在数据归档这些表的删除事件…...

SQL Server端口配置指南:最佳实践与技巧

1. 引言 SQL Server通常使用默认端口1433进行通信。为了提高安全性和性能&#xff0c;正确配置SQL Server的端口非常重要。本指南将帮助您了解如何配置和优化SQL Server的端口设置&#xff0c;以满足不同环境和需求。 2. 端口配置基础 2.1 默认端口 SQL Server的默认端口是…...

FastGPT 报错:undefined 该令牌无权使用模型:gpt-3.5-turbo (request id: xxx)

目录 一、FastGPT 报错 二、解决方法 一、FastGPT 报错 进行对话时 FastGPT 报错如下所示。 [Error] 2024-07-01 09:25:23 sse error: undefined 该令牌无权使用模型:gpt-3.5-turbo (request id: xxxxx) {message: 403 该令牌无权使用模型:gpt-3.5-turbo (request id: x…...

springboot系列八: springboot静态资源访问,Rest风格请求处理, 接收参数相关注解

文章目录 WEB开发-静态资源访问官方文档基本介绍快速入门注意事项和细节 Rest风格请求处理基本介绍应用实例注意事项和细节思考题 接收参数相关注解基本介绍应用实例PathVariableRequestHeaderRequestParamCookieValueRequestBodyRequestAttributeSessionAttribute ⬅️ 上一篇…...