You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 07:52:29