ASP.NET MVC中Fluent Validation重复校验失效及空引用异常解决
ASP.NET MVC Fluent Validation重复校验异常问题
问题详情
- ASP.NET MVC项目中,Fluent Validation的必填字段校验规则均正常触发,但重复组合校验规则失效。需求是:当
ProjectID、DocumentType和IsThisCurrent三者的组合已存在于数据库时,抛出重复错误(例如以下数据中第1行和第3行的组合重复):
Project ID DocumentType IsThisCurrent 1 Protocol Yes 1 Application Yes 1 Protocol Yes 1 Protocol No
- 初始代码中校验时加载的是空列表
var existingValues = new List<DocTrailList>();,无法读取数据库已有数据;尝试添加private ResearchContext _context;并修改为var existingValues = _context.DocTrailList!.ToList();后,触发**System.NullReferenceException: 'Object reference not set to an instance of an object'**异常。
相关代码
Create控制器代码
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("DetailID,ProjectID,DocumentType,Version,Dated,DateReceived,IsThisCurrent")] DocTrailList docTrailList) { DocTrailListsValidator doctraillistsvalidator = new DocTrailListsValidator(); ValidationResult result = doctraillistsvalidator.Validate(docTrailList); if (result.IsValid) { _context.Add(docTrailList); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } else { foreach (var failure in result.Errors) { ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage); } } ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID", docTrailList.ProjectID); var dtname = _context.DocType!.ToList(); ViewBag.dtname = dtname; var current = _context.YesNoList!.ToList(); ViewBag.current = current; return View(docTrailList); }
DocTrailListValidator代码
using FluentValidation; using Research.Data; namespace Research.Models { public class DocTrailListsValidator : AbstractValidator<DocTrailList> { private ResearchContext _context; public DocTrailListsValidator() { RuleFor(x => x.DocumentType).NotNull().WithMessage("A required field"); RuleFor(x => x.Version).NotNull().WithMessage("A required field"); RuleFor(x => x.Dated).NotNull().WithMessage("A required field"); RuleFor(x => x.DateReceived).NotNull().WithMessage("A required field"); RuleFor(x => x.Version).NotNull().WithMessage("A required field"); RuleFor(x => x.IsThisCurrent).NotNull().WithMessage("A required field"); RuleFor(x => x.ProjectID) .Must((root, projectID, context) => !IsDuplicate(root)) .WithMessage("Duplicate entry found."); } private bool IsDuplicate(DocTrailList c) { var existingValues = _context.DocTrailList!.ToList(); return existingValues.Any(x => x.ProjectID == c.ProjectID && x.DocumentType == c.DocumentType && x.IsThisCurrent == c.IsThisCurrent); } } }
Create视图校验代码
<div class="col-md-10"> @Html.ValidationMessageFor(model => model.ProjectID, null, new { @class = "text-danger" }) </div>
DocTrailList模型代码
using System.ComponentModel.DataAnnotations; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Research.Models { public class DocTrailList { [Key] public int DetailID { get; set; } public int? ProjectID { get; set; } [Display(Name = "Document Type")] public string? DocumentType { get; set; } public string? Version { get; set; } public string? Dated { get; set; } [Display(Name = "Date Received")] [DataType(DataType.Date)] public DateTime? DateReceived { get; set; } [Display(Name = "Is This Current?")] public string? IsThisCurrent { get; set; } [ForeignKey("ProjectID")] public PIF? PIF { get; set; } } }
解决方案
1. 修复校验器的上下文初始化问题
异常原因是校验器中的_context字段未被赋值,需要通过构造函数传入ResearchContext实例:
using FluentValidation; using Research.Data; namespace Research.Models { public class DocTrailListsValidator : AbstractValidator<DocTrailList> { // 改为readonly字段,确保不可修改 private readonly ResearchContext _context; // 构造函数注入上下文 public DocTrailListsValidator(ResearchContext context) { _context = context; // 保留原有必填校验规则(去重重复的Version规则) RuleFor(x => x.DocumentType).NotNull().WithMessage("必填字段"); RuleFor(x => x.Version).NotNull().WithMessage("必填字段"); RuleFor(x => x.Dated).NotNull().WithMessage("必填字段"); RuleFor(x => x.DateReceived).NotNull().WithMessage("必填字段"); RuleFor(x => x.IsThisCurrent).NotNull().WithMessage("必填字段"); // 重复组合校验规则 RuleFor(x => x.ProjectID) .Must((model, projectId) => !IsDuplicate(model)) .WithMessage("已存在相同的项目ID、文档类型和当前状态组合"); } private bool IsDuplicate(DocTrailList model) { // 直接查询数据库,避免加载全表,提升性能 return _context.DocTrailList.Any(x => x.ProjectID == model.ProjectID && x.DocumentType == model.DocumentType && x.IsThisCurrent == model.IsThisCurrent); } } }
2. 控制器中传入上下文实例
修改控制器中校验器的初始化代码,将控制器已有的_context传入校验器:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("DetailID,ProjectID,DocumentType,Version,Dated,DateReceived,IsThisCurrent")] DocTrailList docTrailList) { // 传入控制器的上下文实例 DocTrailListsValidator doctraillistsvalidator = new DocTrailListsValidator(_context); ValidationResult result = doctraillistsvalidator.Validate(docTrailList); if (result.IsValid) { _context.Add(docTrailList); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } else { foreach (var failure in result.Errors) { ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage); } } ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID", docTrailList.ProjectID); ViewBag.dtname = _context.DocType!.ToList(); ViewBag.current = _context.YesNoList!.ToList(); return View(docTrailList); }
3. 性能优化(可选)
原代码中_context.DocTrailList!.ToList()会加载全表数据到内存,再进行判断,数据量大时性能低下。改为直接使用Any()方法,EF Core会生成对应的SQL查询,只检查是否存在匹配项,效率更高。
4. 依赖注入注册校验器(推荐)
如果项目使用依赖注入框架,建议在Program.cs中注册校验器:
builder.Services.AddScoped<DocTrailListsValidator>();
然后控制器通过构造函数注入校验器,无需手动实例化:
private readonly ResearchContext _context; private readonly DocTrailListsValidator _validator; // 构造函数注入上下文和校验器 public YourControllerName(ResearchContext context, DocTrailListsValidator validator) { _context = context; _validator = validator; } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind(...)] DocTrailList docTrailList) { ValidationResult result = _validator.Validate(docTrailList); // 后续逻辑不变 }
内容的提问来源于stack exchange,提问作者Sally Parkes
相关产品推荐
相关产品推荐

