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

news2024/11/17 23:58:26

  本章将描述 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.coloradmin.cn/o/1891746.html

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

相关文章

UnityUGUI之三 Text

富文本 常用语法&#xff1a; 1.加粗 <b> text </b> 2.斜体 <i> text </i> 3.尺寸 <size?> text </size> 4.颜色 <color#ff0000> text </color>

qt 滚动区域简单实验

1.概要 有些时候&#xff0c;想用一个有限的区域显示更多的内容&#xff0c;且内容不固定用滚动区域控件是一个不错的选择&#xff0c;我今天就用一个图片简单的实验一下。 2.代码&#xff08;关键代码&#xff09; #include "widget.h" #include "ui_widget…

antd+vue——实现table组件跨页多选,已选择数据禁止第二次重复选择

需求场景&#xff1a;点击【新增】按钮可以在分页弹窗中跨页多选选择数据后添加到页面中&#xff0c;再次点击【新增】&#xff0c;已经选择过的数据则置灰不让重复选择。 选择后&#xff0c;置灰 点击【确定】数据添加到页面中&#xff0c;可再次点击【新增】进行添加数据 …

JS基础与Chrome介绍

导言 在Web开发中后端负责程序架构和数据管理&#xff0c;前端负责页面展示和用户交互&#xff1b;在这种前后端分离的开发方式中&#xff0c;以接口为标准来进行联调整合&#xff0c;为了保证接口在调用时数据的安全性&#xff0c;也为了防止请求参数被篡改&#xff0c;大多数…

C语言常见概念

目录 1. C语言是什么&#xff1f; 2. C语言的历史 3 编译和链接 4. VS项目和源文件、头文件介绍​编辑 5.创建项目 6.main函数​编辑 7. printf和库函数 8. 关键字介绍 9. 字符和ASCII编码 10. 字符串和\0 1. C语言是什么&#xff1f; 人和计算机交流的语言工具&…

CVD-Risk-Prevent 个性化心血管健康推荐系统:基于医学指南的规则框架与 LLM 的结合

CVD-Risk-Prevent 个性化心血管健康推荐系统&#xff1a;基于医学指南的规则框架与 LLM 的结合 提出背景推荐算法的选择选择疑问健康指标管理心血管风险因素目标设定实现目标的计划推荐的多维性 算法关键点&#xff1a;如何将心血管健康指标转换为多维推荐&#xff1f;确定风险…

热备路由HSRP与VRRP

一、什么是HSRP HSRP&#xff08;Hot Standby Router Protocol&#xff09;是Cisco的专有协议&#xff0c;用于实现网络中路由器的冗余和故障转移。通过HSRP&#xff0c;可以将多台路由器组成一个“热备份组”&#xff0c;形成一个虚拟路由器。在这个组内&#xff0c;只有一个…

理解Netty的核心概念

一、理解Netty Netty是一个用于开发高性能网络应用的框架。为了更容易理解它&#xff0c;下面一些描述&#xff0c;不一定准确&#xff0c;但一定容易理解。 从Netty的Channel开始&#xff0c;把Netty所有的核心概念都串起来。 Channel 简单理解为一个连接。 有一个特殊的C…

Datadog Dash 2024 新功能解析

Datadog 2024 年的 Dash 刚刚落下帷幕&#xff0c;作为正在与 Datadog 开始竞争的观测云&#xff0c;我们认真仔细的分析了 Datadog 的每一个新功能&#xff0c;发现一些很有意思的事情&#xff0c;今天就给大家做一次全面的分析。&#xff08;所有 Datadog 的 Dash 的最新功能…

《UDS协议从入门到精通》系列——图解0x86:事件响应

《UDS协议从入门到精通》系列——图解0x86&#xff1a;事件响应 一、简介1.1 什么是事件响应&#xff1f;跟其他服务有何不同&#xff1f;1.2 到底如何理解事件响应机制&#xff1f;1.3 使用事件响应机制有哪些注意点&#xff1f; 二、数据包格式三、通信示例 Tip&#x1f4cc;…

VBA通过Range对象实现Excel的数据写入

前言 本节会介绍通过VBA中的Range对象&#xff0c;来实现Excel表格中的单元格写入、区域范围写入&#xff0c;当然也可以写入不同类型的数据&#xff0c;如数值、文本、公式&#xff0c;以及实现公式下拉自动填充的功能。 一、单元格输入数据 1.通过Value方法实现输入不同类型…

去中心化社会的崛起:探索区块链对社会结构的影响

随着区块链技术的发展和应用&#xff0c;我们正逐步迈向一个去中心化的社会结构。本文将深入探讨区块链技术如何影响社会结构&#xff0c;从经济、政治到文化等多个方面进行探索和分析&#xff0c;揭示其可能带来的革命性变革。 1. 区块链技术的基本原理回顾 1.1 分布式账本与…

从 ClickHouse 到 Apache Doris:快成物流的数智化货运应用实践

导读&#xff1a;随着快成物流的大宗商品产业链的不断发展&#xff0c;货运轨迹规划和实时数据分析的需求日益迫切&#xff0c;为了保障数据报表更新、用户画像圈选与物流轨迹实时更新等大数据核心系统性能&#xff0c;快成物流引入 Apache Doris 实时数仓升级了大数据算法平台…

vue2使用use注册自定义指令实现输入控制与快捷复制

使用场景 在一些form表单填写内容的时候&#xff0c;要限制输入的内容必须是数值、浮点型&#xff0c;本来el-input-number就可以实现&#xff0c;但是它本身带那个数值控制操作&#xff0c;等一系列感觉不舒服的地方。如果只是使用el-input该多好&#xff0c;只要监听一下输入…

docker安装ElasticSearchKibana

本文参考以下两篇文章 ✅ElasticSearch&Kibana 部署 云效 Thoughts 企业级知识库 (aliyun.com) docker安装ElasticSearch&Kibana - 飞书 安装elasticsearch 使用docker下载es&#xff1a; docker pull elasticsearch:8.13.0 挂载配置 创建挂在文件目录 mkdir…

AIGC在软件开发中的应用

目录 1. AIGC技术概述1.1 定义与背景1.2 发展历程 2. AIGC在软件开发中的应用2.1 代码生成2.2 错误检测与修复2.3 自动化测试 3. AIGC对开发者职业前景的影响3.1 助力与赋能开发者代码示例&#xff1a;自动化测试 3.2 技能需求转变与职业转型压力代码示例&#xff1a;AIGC辅助的…

云原生技术架构详解

云原生技术最全详解(图文全面总结) 容器技术 容器技术&#xff1a;是将应用程序、及其所有依赖项&#xff0c;打包到一个独立的、可移植的容器中。 如下图所示: 容器技术的实现&#xff0c;最典型的就是以Docker为代表的。 如下图所示&#xff1a; 主要解决&#xff1a; 1、…

#LinuxC高级 笔记一

linux命令 什么是嵌入式&#xff1f; 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软件硬件可裁剪&#xff0c;适用于对功能、可靠性、成本、体积、功耗有严格要求的专用计算机系统 计算机系统组成&#xff1f; 硬件、软件 操作系统&#xff1f; ios windows harmo…

Docker的架构原理

例子可以想象成一个买手机的场景 clien可以想象 你个人 docker deamon &#xff1a;店员 images&#xff1a; 样机 regisitry&#xff1a; 手机仓库 container: 使用的手机 首先我要在店员买一个手机&#xff0c;店员发现是样机&#xff0c;但是仓库有&#xff0c;&…

Zabbix 配置端口监控

Zabbix 端口监控简介 在Zabbix中配置端口监控&#xff0c;可以帮助你实时监控服务器或网络设备上的特定端口是否开放和可访问。Zabbix提供了多种方式来监控端口&#xff0c;主要包括简单的端口可用性检查和更复杂的服务监控。 在Zabbix中进行端口监控时&#xff0c;不一定需要…