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

一文看懂Blazor 和 ASP.NET Core MVC 异同 阅读详情

  本章将描述 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防伪令牌实战:从CSRF防御到Blazor SSR配置 跨站请求伪造(CSRF)是一种常见的Web安全威胁,攻击者利用用户已登录的会话,诱导其执行非预期的操作。其防御核心在于验证请求的合法性,确保操作来自可信来源。防伪令牌技术通过生成一次性随机令牌,结合会话凭证进行双重验证,有效阻断恶意请求。在ASP.NET Core中,该机制通过中间件和属性验证实现,为表单提交和状态修改操作提供基础防护。随着.NET版本更新,自动CSRF保护功能引入,基于浏览器安全头进行智能拦截,但防伪令牌仍是敏感操作的核心保障。在Blazor SSR场景中,最新版本要求显式启用中间件验证, 阅读详情

相关推荐

Blazor vs ASP.NET Core MVC:如何根据项目需求选择最佳框架

本文深入比较了Blazor和ASP.NET Core MVC两大框架的核心差异与应用场景,帮助开发者根据项目需求做出最佳选择。Blazor适合需要丰富交互的SPA应用和.NET技术栈团队,而ASP.NET Core MVC则在内容型网站和SEO优化方面表现更佳。文章还提供了混合架构实践和迁移策略,助力开发者高效决策。

weixin_33734785的博客 397

Blazor —— 母版页的定义和使用(Layout)

母版页在项目中那是必然要用的功能,那你咋知道怎么定义和使用吗?

不忘初心 4534

ASP.NET Core Blazor 编程系列:实现用户登录

ASP.NET Core Blazor 编程系列:实现用户登录在本篇文章中,我们将探讨如何使用 ASP.NET Core Blazor 构建一个用户登录功能。登录是许多 Web 应用程序中常见的功能之一,它允许用户通过提供有效的凭据来访问应用程序的特定部分或功能。首先,我们需要创建一个新的 Blazor 应用程序。一旦项目生成完成,我们可以开始实现用户登录功能。

YOLOv222的博客 1247

Blazor 自定义可重用基础组件之 TextBox(InputText)

这是一个带标签和验证信息的输入框,用起来很方便。为避免命名冲突,这里将InputText改名为TextBox,其实内部类型就是string。可能有人会说用反射获取标签,我认为上面这个代码非常明显地显示了这是哪个属性输入框,用反射反而查找起来不容易。注意,组件的总长不能大于上一级容器长度,否则标签会分行或断行。

luwq168的专栏 898

闲置服务器之代理服务器篇

这台服务器是在阿里云免费白嫖的,天工开物计划,阿里给的300块无门槛优惠券,买了一台新加坡的境外服务器,288,刚刚抵扣完,因为是境外服务器,能够访问一些国内不能访问的网站,所以本文章仅从技术的角度来搭建代理服务器,使用时,请自觉遵守国家相关规定,如有任何违法行为,与本人无任何关系!!!。本文使用tinyproxy来搭建代理服务器。

m0_50281408的博客 2103

Windows安装Pytorch3d

pytorch和pytorch3d版本对应关系如下:https://github.com/facebookresearch/pytorch3d/releasescuda和NVIDIA CUB版本对应关系如下:https://github.com/NVIDIA/cub/releases?page=1本人配置如下:GPU:1050TiCUDA:11.1python:3.8。

Snakewood的博客 4285

全面的ASP.NET Core Blazor简介和快速入门

因为咱们的MongoDB入门到实战教程Web端准备使用Blazor来作为前端展示UI,本篇文章主要是介绍Blazor是一个怎样的Web UI框架,其优势和特点在哪?并带你快速入门上手ASP.NET Core Blazor(当然这个前提是你要有一定的C#编程基础的情况,假如你完全没有接触过C#的话建议你先从基本语法学起)。Blazor Server 应用程序在服务器上运行,可享受完整的 .NET Core 运行时支持。所有处理都在服务器上完成,UI/DOM 更改通过 SignalR 连接回传给客户端。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 2526

ICC2 实战秘籍:create_workspace 命令全解析

必选项:工作区名称,这可是创建工作区的 “身份证”,必不可少。技术数据选项匹配:优先考虑用 -technology 直接加载技术文件,这是获取最新、最准确技术数据的首选路径;要是已经有合适的技术库了,那 -use_technology_lib 引用现有技术库也能快速上手。精度设置优先规则:技术文件自动配置排在第一位,能自动适配就别手动操心;要是自动配置不理想,那就显式指定 scale_factor;实在没要求,就用默认配置,但得清楚它的精度限制。

weixin_50820814的博客 1123

使用Canvas扩展绘制动态ASP.NET Core Blazor条形图

目录 介绍 背景 使用代码 步骤1:创建ASP.NET Core Blazor服务器应用程序 运行测试应用程序 步骤2:安装软件包 步骤3:建立模型类别 创建服务类 步骤4:将服务添加到Startup.cs 步骤5:使用客户项目 添加Razor组件 Import部分 HTML设计和数据绑定部分 函数(Function)部分 导航菜单 生成并运行应用程序 兴趣点 ...

寒冰屋的专栏 2575

芒果YOLOv5改进55:损失函数篇之KLD:即插即用 芒果首发原创新颖|全网唯一更新KLD改进博客,通过 Kullback-Leibler 散度学习用于目标检测的高精度边界框,提升YOLOv5小目标

YOLOv5改进KLD损失函数:即插即用 原创新颖|芒果专栏独家首发

包括YOLOv5、YOLOv7、YOLOv8等模型改进 661

桌面实施远程光电体积描记法:使用面部视频测量心率的C++详细指南

在现代医疗和健康监测领域,心率是一个非常重要的生理参数,它可以为我们提供关于身体健康状况的重要信息。但是,随着技术的进步,现在我们有了一种新的方法,称为远程光电体积描记法(rPPG),它可以无需与皮肤接触就测量心率。然后,我们从面部颜色的信号序列中提取绿色通道的值,因为绿色通道对于心率的变化最为敏感。在上述代码中,我们使用了OpenCV库,它是一个开源的计算机视觉库,非常适合这种应用。这种变化是由于血液流动引起的,当心脏跳动时,血液流经面部,导致肤色的轻微变化。要实现rPPG,首先需要检测并跟踪人脸。

qq_38334677的博客 608

使用Canvas扩展绘制动态ASP.NET Core Blazor气泡图

下载源234.8 KB 介绍 在今天的文章中,我们将看到如何使用HTML5 Canvas和ASP.NET Core Blazor Web应用程序绘制自己的气泡图。 背景 Visual Studio 2019 16.3或更高版本 .NET Core 3.0 SDK 使用代码 步骤1:创建ASP.NET Core Blazor服务器应用程序 安装上面列出的所有必备组件后,单击桌...

寒冰屋的专栏 3179

让 ABAP 开发更聪明的搭档:SAP Joule for Developers 与 ABAP AI Capabilities 全面实战

摘要:SAP已将生成式AI集成到ABAP开发工具中,推出Joule for Developers功能。该功能直接嵌入Eclipse开发环境,提供代码解释、生成、测试构造和遗留代码迁移等辅助功能。Joule支持ABAP Cloud项目开发,能围绕当前开发对象提供上下文相关的建议,帮助开发者高效完成应用开发与迁移任务。启用该功能需升级ADT并获取相应许可,典型应用场景包括RAP应用构建和S/4HANA迁移中的代码重构。官方资源提供了多个实践案例,帮助开发者快速上手这一AI辅助开发功能。(149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 488

ASP.NET Core 11 Blazor SSR 客户端表单验证实战:DataAnnotationsValidator 免往返校验

本文围绕 ASP.NET Core 11 中新增的 Blazor SSR 客户端表单验证能力展开,结合仓库 [projects/net11/ssr-client-side-validation](https://link.gitcode.com/i/c1568582edd5a06469b7a75f32a33426) 示例完整讲解其工作原理、启用方式与实战写法。读完本文,你将掌握如何在静态 SSR

gitblog_00033的博客 344

23、变压器与电抗器知识详解

本文详细介绍了电力变压器的基本原理、等效电路、测试方法及多种运行方式,涵盖双绕组与自耦变压器的建模与应用。深入探讨了变压器极性判断、并联运行条件、电压调节技术,并扩展至非线性、磁滞、EMTP和高频模型,用于瞬态分析。同时阐述了地磁感应电流(GIC)的影响及对偶建模方法。最后系统介绍了各类电抗器的应用场景与特性,包括限流、无功补偿、滤波及特殊用途电抗器,全面覆盖电力系统中变压器与电抗器的核心知识。

eee77的博客 150

利用NLP技术分析爬取的文本数据:Python爬虫与自然语言处理实战

网页爬虫(Web Crawler)是一种自动化脚本,通过模拟人类浏览网页的方式,定期抓取网页内容并进行分析和存储。对于大规模的数据抓取任务,爬虫技术提供了极大的便利。常见的Python爬虫库有requestsSelenium等。在本篇博客中,我们从爬取网页文本数据开始,通过清洗和预处理,最终利用自然语言处理技术(包括情感分析、文本分类等)对数据进行了深度分析。通过该过程,我们展示了如何使用Python爬虫抓取文本数据,并利用NLP技术进行分析和建模。

2201_76125261的博客 627

BBDown 哔哩哔哩视频下载教程:贴链接存本地,高清无水印

赶期末,网课要反复拉进度条看,网页播放器却总在下一节之间乱跳。BBDown 是一个命令行式哔哩哔哩下载器,把视频链接贴进终端,它直接解析并保存成能离线播放的 mp4 到本地。 ## 手动另存为做不到什么 浏览器右键"另存为"拿到的只是一段一段的 ts 分片,而且画质档位远少于播放器里能选的。BBDown 的做法不同: - 默认多线程下载,画质最高到 8K、HDR 真彩、杜比视界 - 贴番剧、

gitblog_00371的博客 426

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

ASP.NET Core 5.0 的新增功能

本文重点介绍 ASP.NET Core 5.0 中最重要的更改,并提供相关文档的链接。 ASP.NET Core MVC 和 :::no-loc(Razor)::: 改进 通过模型绑定将日期/时间绑定到 UTC 模型绑定现在支持将 UTC 时间字符串绑定到DateTime。如果请求包含 UTC 时间字符串,则模型绑定会将其绑定到 UTCDateTime。例如,以下时间字符串会绑定到 UTCDateTime:https://example.com/mycontroller/myaction?t...

https://github.com/conanl5566 1735

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

使用 ASP.NET Core 进行现代全栈 Web 开发: 5 Blazor 入门

在 Blazor 框架中,WebAssembly 作为在浏览器中运行.NET 代码的基础, 让开发者能够充分利用 C#和.NET 的强大功能进行客户端网页开发。凭借与.NET 生态系统的无缝集成以及对开发者体验的高度重视,Blazor 有望彻底改变 Web 应用程序的构建和部署方式,使创建现代化交互式用户体验变得前所未有的简单。通过利用.NET 生态系统的强大功能和熟悉度,Blazor 使开发者能够轻松构建现代化、交互式的 Web 应用程序,同时促进整个 开发技术栈中的代码复用、一致性和可维护性。

1061

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

用于存储过程的ASP.NET Core Blazor和EF Core原始SQL查询

下载源405 KB 介绍 在本文中,我们将详细介绍如何使用Entity Framework Core Raw SQL在我们的ASP.NET Core Blazor应用程序中执行存储过程。 原始SQL查询最常用于所有关系数据库,因为它可以返回常规实体类型或无键实体类型。就像我们使用具有大量关系表和许多页面的大型数据库一样,我们需要通过从许多表中进行联接来绑定和显示表详细信息,例如,如果要显示销售报告,在这种情况下,我们需要加入多个表,例如公司主数据,工厂主数据、客户主数据、物料主数据、生产结果和...

寒冰屋的专栏 1582

告别组件孤岛:ASP.NET Core Blazor父子通信完全指南

你是否还在为Blazor组件间数据传递头疼?子组件如何实时响应父组件状态变化?父组件怎样捕获子组件的用户操作?本文将通过3种实战模式+5个代码示例,彻底解决Blazor组件通信难题,让你的UI交互逻辑清晰如流水。 ## 组件通信的三种核心模式 Blazor组件间的数据流动如同建筑中的管线系统,合理的设计能让整个应用"水电通畅"。根据项目复杂度不同,我们需要掌握以下三种基础通信模式: ###

gitblog_00351的博客 515

HAVIT Blazor:Bootstrap 5 组件库的强大助力

**HAVIT Blazor** 是一个为 ASP.NET Blazor 开发者提供的免费 Bootstrap 5.3 组件库。它支持 .NET 6 及以上版本,适用于 Blazor WebAssembly 和 Blazor Server 两种托管模式。HAVIT Blazor 不仅提供了丰富的 UI 组件,还包含一个可选的企业级项目模板,帮助开发者快速搭建具有分层架构、EF Core、gRPC

gitblog_00333的博客 1041

解决ASP.NET Core Blazor中EditForm模型初始化的5个关键方案

在Blazor开发中,EditForm(编辑表单)是处理用户输入的核心组件,但模型初始化问题常常导致表单验证异常或数据绑定失败。本文将从实际项目代码出发,系统分析常见问题并提供可落地的解决方案。 ## 问题现象与影响范围 当EditForm模型未正确初始化时,会出现三类典型错误: - 运行时异常:"EditForm requires either a Model parameter, or a...

gitblog_00118的博客 412

ASP.NET Core Blazor 工具链全解析:从环境搭建到高效开发实战

在现代Web开发中,一套高效的工具链是提升开发效率和项目质量的关键。工具链(Tooling)通常指支持项目全生命周期的一系列工具、配置和集成环境,其核心原理在于通过自动化、智能化和标准化来减少人工操作,降低错误率。对于使用C#和.NET技术栈的前端开发而言,ASP.NET Core Blazor框架的工具链价值尤为突出,它无缝集成了项目创建、智能编码、实时调试、热重载和优化构建等环节。特别是在开发Blazor WebAssembly或Blazor Server应用时,借助热重载(Hot Reload)功能,

weixin_34224941的博客 403

告别重复编码:ASP.NET Core Blazor组件复用的5个实战技巧

在ASP.NET Core Blazor开发中,高效的组件复用是提升开发效率、减少代码冗余的关键。本文将分享5个实用技巧,帮助开发者轻松实现Blazor组件的复用,让你的项目更加模块化和可维护。 ## 1. 利用布局组件实现页面结构复用 Blazor的布局组件是实现页面结构复用的强大工具。通过创建可继承的布局组件,你可以轻松地在多个页面间共享一致的页面结构。 ```razor @inheri

gitblog_01171的博客 529
上一篇: ASP.NET Core Blazor 4:高级Blazor特性
下一篇: IOC容器和依赖注入
似水明俊德
博客等级 码龄8年 247粉丝 276原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值