VS-2019中Blazor PWA项目的CRUD验证及唯一性校验实现难题
看起来你遇到的核心问题是跨项目依赖限制导致SHARED层无法注入SERVER服务,以及客户端校验错误无法显示到validation-summary的问题。我来给你几个贴合你项目结构的实用解决方案:
方案1:把唯一性校验移到SERVER层,通过API返回错误到客户端ModelState
因为SHARED项目不能依赖SERVER(避免循环依赖),所以在SHARED的ValidationAttribute里做数据库校验本来就行不通——Attribute是静态实例化的,没法依赖注入服务,而且SHARED也没有DI容器支持。
正确的思路是把唯一性校验放在SERVER的API层,通过ModelState返回错误,再让客户端把这些错误同步到自己的EditContext.ModelState里,这样validation-summary就能自动显示错误了。
步骤1:SERVER层添加校验逻辑
在你的CustomerController的Create/Update方法里,先检查名称是否重复:
[HttpPost] public async Task<IActionResult> Create(Customer customer) { // 调用Repository检查名称是否重复(编辑时要排除当前ID) bool nameExists = await _customerRepository.IsNameDuplicateAsync(customer.CustomerName, customer.Id); if (nameExists) { // 添加错误到ModelState,指定关联字段和错误信息 ModelState.AddModelError(nameof(Customer.CustomerName), "该客户名称已存在,请更换"); return BadRequest(ModelState); } // 正常创建逻辑 await _customerRepository.CreateAsync(customer); return Ok(); } // 编辑方法同理 [HttpPut("{id}")] public async Task<IActionResult> Update(int id, Customer customer) { if (id != customer.Id) return BadRequest(); bool nameExists = await _customerRepository.IsNameDuplicateAsync(customer.CustomerName, customer.Id); if (nameExists) { ModelState.AddModelError(nameof(Customer.CustomerName), "该客户名称已存在,请更换"); return BadRequest(ModelState); } await _customerRepository.UpdateAsync(customer); return Ok(); }
步骤2:客户端处理API返回的错误
在你的CLIENT组件的OnSubmit方法里,调用API后如果收到BadRequest,就把错误信息添加到EditContext.ModelState:
private async Task HandleSubmit() { // 先做客户端静态校验(比如DataAnnotation的必填、长度规则) bool isClientValid = EditContext.Validate(); if (!isClientValid) return; // 调用API提交数据 var response = await Http.PostAsJsonAsync("api/customers", CurrentCustomer); if (response.IsSuccessStatusCode) { // 提交成功:跳转、重置表单等逻辑 NavigationManager.NavigateTo("/customers"); return; } // 处理错误:如果是BadRequest,说明是校验错误 if (response.StatusCode == HttpStatusCode.BadRequest) { // 读取API返回的错误字典 var errors = await response.Content.ReadFromJsonAsync<Dictionary<string, string[]>>(); if (errors != null) { // 清空之前的错误,避免叠加 foreach (var key in EditContext.ModelState.Keys) { EditContext.ModelState[key].Errors.Clear(); } // 添加API返回的错误到ModelState foreach (var errorEntry in errors) { foreach (var errorMessage in errorEntry.Value) { EditContext.ModelState.AddModelError(errorEntry.Key, errorMessage); } } // 通知UI校验状态变化,让validation-summary更新 EditContext.NotifyValidationStateChanged(); } } else { // 处理其他服务器错误(比如500) // 可以显示全局错误提示 } }
这样一来,服务器端的唯一性校验错误就会自动显示在你的validation-summary里了。
方案2:客户端即时校验(可选,优化用户体验)
如果想在用户输入时就即时提示名称重复,而不是等到提交后,你可以在CLIENT项目里直接调用API获取现有名称列表,然后添加客户端自定义校验规则:
步骤1:在CLIENT组件里加载现有名称
private List<string> _existingCustomerNames = new(); private Customer CurrentCustomer = new(); private string _originalCustomerName; // 编辑模式下保存原始名称 protected override async Task OnInitializedAsync() { // 从API获取所有已存在的客户名称 _existingCustomerNames = await Http.GetFromJsonAsync<List<string>>("api/customers/names"); // 如果是编辑模式,保存原始名称 if (CurrentCustomer.Id != 0) { _originalCustomerName = CurrentCustomer.CustomerName; } }
步骤2:添加自定义校验规则
protected override void OnInitialized() { // 给EditContext添加自定义校验逻辑 EditContext.OnValidationRequested += (sender, args) => { ValidateCustomerNameUniqueness(); }; // 监听字段变化,实时校验 EditContext.OnFieldChanged += (sender, args) => { if (args.FieldIdentifier.FieldName == nameof(Customer.CustomerName)) { ValidateCustomerNameUniqueness(); } }; } private void ValidateCustomerNameUniqueness() { // 清空之前的该字段错误 EditContext.ModelState[nameof(Customer.CustomerName)]?.Errors.Clear(); // 判断名称是否重复:新增时直接检查,编辑时排除当前原始名称 bool isDuplicate = false; if (CurrentCustomer.Id == 0) // 新增模式 { isDuplicate = _existingCustomerNames.Contains(CurrentCustomer.CustomerName, StringComparer.OrdinalIgnoreCase); } else // 编辑模式 { isDuplicate = _existingCustomerNames.Any(name => name.Equals(CurrentCustomer.CustomerName, StringComparer.OrdinalIgnoreCase) && !name.Equals(_originalCustomerName, StringComparer.OrdinalIgnoreCase)); } if (isDuplicate) { EditContext.ModelState.AddModelError(nameof(Customer.CustomerName), "该客户名称已存在"); } // 通知UI更新 EditContext.NotifyValidationStateChanged(); }
为什么SHARED层的Attribute没法注入服务?
你之前尝试在SHARED的ValidationAttribute里注入ICustomerService但始终为null,原因有两个:
- 依赖关系限制:SHARED不能依赖SERVER,否则会出现循环依赖(SERVER依赖CLIENT,CLIENT依赖SHARED,SHARED再依赖SERVER),VS会直接报错。
- Attribute的生命周期:
ValidationAttribute是静态构造的,.NET的DI容器不会自动注入到Attribute实例里,所以即使能引用服务,也没法获取到有效实例。
所以需要访问数据库的动态校验,完全不适合放在SHARED的Attribute里,要么放SERVER层做后端校验,要么放CLIENT层通过API做前端校验。
内容的提问来源于stack exchange,提问作者John D

