Blazor Workshop项目实战:实现订单数据验证功能
【免费下载链接】blazor-workshop Blazor workshop 项目地址: https://gitcode.com/gh_mirrors/bl/blazor-workshop
在Web应用开发中,数据验证是确保数据完整性和用户体验的关键环节。Blazor Workshop项目通过一个披萨订购系统,展示了如何在Blazor应用中实现完整的客户端和服务器端数据验证。本文将深入探讨订单数据验证的实现细节,帮助你掌握Blazor验证机制的核心技术。
验证架构概览
Blazor Workshop采用双层验证架构,确保数据的完整性和安全性:
核心技术组件
| 组件 | 作用 | 位置 |
|---|---|---|
DataAnnotations | 定义验证规则 | 共享模型层 |
EditForm | 包装表单组件 | 客户端组件 |
EditContext | 跟踪编辑状态 | 客户端组件 |
InputText | 数据绑定输入控件 | 客户端组件 |
ValidationMessage | 显示验证消息 | 客户端组件 |
DataAnnotationsValidator | 执行验证逻辑 | 客户端组件 |
[ApiController] | 自动验证请求 | 服务器端控制器 |
数据模型验证规则定义
在Address.cs中定义地址模型的验证规则:
using System.ComponentModel.DataAnnotations;
namespace BlazingPizza.Shared;
public class Address
{
public int Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required, MaxLength(100)]
public string Line1 { get; set; } = string.Empty;
[MaxLength(100)]
public string Line2 { get; set; } = string.Empty;
[Required(ErrorMessage = "We need to know which city to deliver to"), MaxLength(50)]
public string City { get; set; } = string.Empty;
[Required, MaxLength(20)]
public string Region { get; set; } = string.Empty;
[Required, MaxLength(20)]
public string PostalCode { get; set; } = string.Empty;
}
验证规则详解
| 属性 | 验证规则 | 错误消息 | 业务意义 |
|---|---|---|---|
| Name | 必填,最大100字符 | 默认消息 | 收货人姓名 |
| Line1 | 必填,最大100字符 | 默认消息 | 地址第一行 |
| Line2 | 最大100字符 | 默认消息 | 地址第二行(可选) |
| City | 必填,最大50字符 | 自定义友好消息 | 配送城市 |
| Region | 必填,最大20字符 | 默认消息 | 地区/省份 |
| PostalCode | 必填,最大20字符 | 默认消息 | 邮政编码 |
客户端验证实现
地址编辑器组件
AddressEditor.razor组件负责渲染地址输入表单:
<div class="form-field">
<label>Name:</label>
<div>
<InputText @bind-Value="Address.Name" />
<ValidationMessage For="@(() => Address.Name)" />
</div>
</div>
<div class="form-field">
<label>Line 1:</label>
<div>
<InputText @bind-Value="Address.Line1" />
<ValidationMessage For="@(() => Address.Line1)" />
</div>
</div>
<!-- 其他字段类似实现 -->
@code {
[Parameter, EditorRequired]
public Address Address { get; set; } = new();
}
结账页面集成验证
Checkout.razor页面整合所有验证组件:
@page "/checkout"
@rendermode InteractiveWebAssembly
@inject OrderState OrderState
@inject IRepository Repository
@inject NavigationManager NavigationManager
<PageTitle>Blazing Pizza - Checkout</PageTitle>
<div class="main">
<EditForm EditContext="EditContext" OnValidSubmit="PlaceOrder">
<div class="checkout-cols">
<div class="checkout-order-details">
<h4>Review order</h4>
<OrderReview Order="OrderState.Order" />
</div>
<div class="checkout-delivery-address">
<h4>Deliver to...</h4>
<AddressEditor Address="OrderState.Order.DeliveryAddress" />
</div>
</div>
<button class="checkout-button btn btn-warning" type="submit">
Place order
</button>
<DataAnnotationsValidator />
<ValidationSummary />
</EditForm>
</div>
@code {
public EditContext EditContext { get; set; } = new EditContext(new Order());
override protected async Task OnAfterRenderAsync(bool first)
{
if (first)
{
EditContext = new EditContext(OrderState.Order.DeliveryAddress);
EditContext.SetFieldCssClassProvider(new BootstrapFieldClassProvider());
StateHasChanged();
}
}
async Task PlaceOrder()
{
var newOrderId = await Repository.PlaceOrder(OrderState.Order);
OrderState.ResetOrder();
NavigationManager.NavigateTo($"myorders/{newOrderId}");
}
}
自定义CSS类提供器
BootstrapFieldClassProvider.cs实现字段状态样式:
using Microsoft.AspNetCore.Components.Forms;
namespace BlazingPizza.Client;
public class BootstrapFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = editContext.IsValid(fieldIdentifier);
return isValid ? "is-valid" : "is-invalid";
}
}
服务器端验证机制
API控制器配置
OrdersController.cs通过[ApiController]属性启用自动验证:
[Route("orders")]
[ApiController]
[Authorize]
public class OrdersController : Controller
{
private readonly PizzaStoreContext _db;
public OrdersController(PizzaStoreContext db)
{
_db = db;
}
[HttpPost]
public async Task<ActionResult<int>> PlaceOrder(Order order)
{
// 如果验证失败,自动返回400 Bad Request
order.CreatedTime = DateTime.Now;
order.DeliveryLocation = new LatLong(51.5001, -0.1239);
order.UserId = PizzaApiExtensions.GetUserId(HttpContext);
// 处理订单逻辑...
_db.Orders.Attach(order);
await _db.SaveChangesAsync();
return order.OrderId;
}
}
验证流程详解
客户端验证流程
服务器端验证流程
高级验证特性
实时验证反馈
Blazor的输入组件提供实时验证反馈:
- 即时验证:字段失去焦点时立即验证
- 视觉反馈:通过CSS类显示验证状态
- 错误消息定位:在对应字段下方显示具体错误
自定义验证逻辑
除了内置的DataAnnotations,还可以:
// 自定义验证属性
public class PizzaNameAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var name = value as string;
if (name != null && name.Contains("🍕"))
{
return new ValidationResult("披萨名称不能包含表情符号");
}
return ValidationResult.Success;
}
}
最佳实践总结
客户端验证最佳实践
- 使用合适的输入组件:优先使用
InputText等内置组件 - 提供即时反馈:实时显示验证状态和消息
- 自定义错误消息:使用友好的用户语言
- 样式一致性:通过CSS类提供器统一验证样式
服务器端验证最佳实践
- 始终验证:不要信任客户端提交的数据
- 使用标准机制:利用
[ApiController]的自动验证 - 业务规则验证:在保存前验证业务逻辑一致性
- 错误处理:提供清晰的错误响应
安全考虑
- 双重验证:客户端验证改善体验,服务器端验证确保安全
- 防篡改:服务器端验证防止恶意请求绕过客户端验证
- 数据完整性:确保数据库存储的数据符合业务规则
实战技巧
处理表单提交状态
防止重复提交的增强实现:
@code {
private bool isSubmitting = false;
async Task PlaceOrder()
{
if (isSubmitting) return;
isSubmitting = true;
try
{
var newOrderId = await Repository.PlaceOrder(OrderState.Order);
OrderState.ResetOrder();
NavigationManager.NavigateTo($"myorders/{newOrderId}");
}
finally
{
isSubmitting = false;
StateHasChanged();
}
}
}
条件按钮样式
<button class="checkout-button btn @(isSubmitting ? "btn-secondary" : "btn-warning")"
type="submit" disabled="@isSubmitting">
@(isSubmitting ? "处理中..." : "Place order")
</button>
总结
Blazor Workshop的订单数据验证功能展示了现代Web应用验证的最佳实践。通过结合客户端即时反馈和服务器端强制验证,既提供了优秀的用户体验,又确保了数据的安全性和完整性。
关键要点:
- 分层验证:客户端改善体验,服务器端确保安全
- 标准组件:充分利用Blazor内置的表单和验证组件
- 实时反馈:通过EditContext实现动态验证状态管理
- 一致性:保持客户端和服务器端验证规则的一致性
掌握这些验证技术,你就能构建出既用户友好又安全可靠的Blazor应用程序。
【免费下载链接】blazor-workshop Blazor workshop 项目地址: https://gitcode.com/gh_mirrors/bl/blazor-workshop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



