ASP.NET Core 3.1中优雅实现语言代码数据库唯一性校验方案
嘿,这个场景我太熟悉了——把唯一性校验逻辑硬塞在控制器里,确实会让代码显得冗余又不优雅。下面给你几个更贴合ASP.NET Core MVC设计思想的优化方案,按需挑选就行:
方案1:自定义验证属性(最推荐,贴合原生验证体系)
把唯一性校验逻辑封装成自定义的ValidationAttribute,直接绑定到视图模型的属性上,这样控制器就能彻底摆脱校验代码,回归原本的职责。
步骤1:编写自定义异步验证属性
public class UniqueLanguageCodeAttribute : ValidationAttribute { protected override async Task<ValidationResult> IsValidAsync(object value, ValidationContext validationContext) { // 从依赖注入容器中获取仓储实例 var languageRepo = (ILanguageRepository)validationContext.GetService(typeof(ILanguageRepository)); var inputCode = value?.ToString()?.ToUpper(); // 空值校验交给[Required]特性处理,这里直接跳过 if (string.IsNullOrEmpty(inputCode)) { return ValidationResult.Success; } // 查询数据库校验唯一性 var existingLanguage = await languageRepo.All.FirstOrDefaultAsync(x => x.LangCode.ToUpper() == inputCode); if (existingLanguage != null) { return new ValidationResult($"The language with code {value} already exists"); } return ValidationResult.Success; } }
步骤2:在视图模型上绑定属性
public class LanguageViewModel { public int Sort { get; set; } [Required(ErrorMessage = "Language code is required")] [UniqueLanguageCode] // 直接加上这个自定义特性 public string Code { get; set; } [Required(ErrorMessage = "Language name is required")] public string Name { get; set; } }
步骤3:简化控制器代码
现在控制器只需要做基础的ModelState校验,剩下的验证逻辑会自动触发:
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel) { if (!ModelState.IsValid) { return View(viewModel); } var newLanguage = new Language() { Sort = viewModel.Sort, LangCode = viewModel.Code, LangName = viewModel.Name }; await _languageRepository.SaveAsync(newLanguage); return RedirectToAction("Index"); }
这个方案的优势是解耦性强,校验逻辑可以复用在其他需要验证语言代码的地方,完全贴合ASP.NET Core的原生验证流程。
方案2:使用FluentValidation(适合复杂验证场景)
如果你的项目已经在使用FluentValidation库,或者需要处理更复杂的验证规则,用它来定义唯一性校验会更灵活清晰。
步骤1:编写验证器
public class LanguageViewModelValidator : AbstractValidator<LanguageViewModel> { private readonly ILanguageRepository _languageRepo; // 通过构造函数注入仓储 public LanguageViewModelValidator(ILanguageRepository languageRepo) { _languageRepo = languageRepo; // 代码非空+唯一性校验 RuleFor(x => x.Code) .NotEmpty().WithMessage("Language code is required") .MustAsync(async (code, cancellationToken) => { var existing = await _languageRepo.All.FirstOrDefaultAsync( x => x.LangCode.ToUpper() == code.ToUpper(), cancellationToken ); return existing == null; }).WithMessage((model) => $"The language with code {model.Code} already exists"); // 名称非空校验 RuleFor(x => x.Name) .NotEmpty().WithMessage("Language name is required"); } }
步骤2:注册验证器
在Startup.cs中注册FluentValidation:
services.AddControllersWithViews() .AddFluentValidation(config => { config.RegisterValidatorsFromAssemblyContaining<LanguageViewModelValidator>(); });
步骤3:控制器代码
和方案1一样,控制器只需要做基础的ModelState校验即可,FluentValidation会自动处理所有规则。
方案3:将校验逻辑移到服务层(适合分层架构项目)
如果你的项目有专门的业务服务层,把“唯一性校验+实体创建”的逻辑封装到服务里,让控制器只负责请求响应的处理。
步骤1:编写业务服务
public class LanguageService { private readonly ILanguageRepository _languageRepo; public LanguageService(ILanguageRepository languageRepo) { _languageRepo = languageRepo; } public async Task<(bool IsSuccess, string ErrorMessage)> CreateLanguageAsync(LanguageViewModel viewModel) { // 唯一性校验 var existingLanguage = await _languageRepo.All.FirstOrDefaultAsync(x => x.LangCode.ToUpper() == viewModel.Code.ToUpper()); if (existingLanguage != null) { return (false, $"The language with code {viewModel.Code} already exists"); } // 创建并保存实体 var newLanguage = new Language { Sort = viewModel.Sort, LangCode = viewModel.Code, LangName = viewModel.Name }; await _languageRepo.SaveAsync(newLanguage); return (true, null); } }
步骤2:控制器调用服务
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel) { if (!ModelState.IsValid) { return View(viewModel); } var result = await _languageService.CreateLanguageAsync(viewModel); if (!result.IsSuccess) { ModelState.AddModelError("Code", result.ErrorMessage); return View(viewModel); } return RedirectToAction("Index"); }
这个方案适合追求清晰分层架构的项目,把业务逻辑集中在服务层,控制器更轻量化。
内容的提问来源于stack exchange,提问作者Aleksej_Shherbak
相关产品推荐
相关产品推荐

