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

ASP.NET Web API 6 自定义ValidationAttribute校验用户名唯一报错

错误原因
  • 构造函数参数不匹配:你定义的AllValidationAttribute构造函数要求必须传入TalkBackDbContext类型的参数,但在属性上标注特性时仅给ErrorMessage属性赋值,没有传入必填的构造参数,编译器找不到匹配的构造函数,直接触发编译错误。
  • 特性依赖注入方式错误:Attribute属于编译期生成的元数据,由CLR直接实例化,不受DI容器管理,无法通过构造函数直接注入DbContext这类运行时服务,原写法本身不符合C#特性的运行规则。
  • 校验逻辑位置错误:你把用户名查重逻辑写在了FormatErrorMessage方法中,这个方法仅负责错误文本的格式化输出,不承担校验逻辑的执行职责,且方法传入的name参数是属性的显示名称,不是用户输入的属性值,就算逻辑能执行也无法得到正确结果,真正的校验逻辑需要重写IsValid方法实现。
正确实现步骤

1. 重写自定义校验特性

移除带DbContext参数的构造函数,重写IsValid方法,通过校验上下文的服务容器获取DbContext实例,同时兼容新增、编辑两种场景的查重逻辑:

public class UniqueUserNameAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        // 从校验上下文获取DI容器中的数据库上下文
        var dbContext = validationContext.GetService<TalkBackDbContext>();
        if (dbContext == null)
        {
            throw new InvalidOperationException("数据库上下文未注册,无法执行用户名唯一性校验");
        }

        var inputUserName = value?.ToString()?.Trim();
        // 空值校验交给Required特性处理,此处直接跳过
        if (string.IsNullOrWhiteSpace(inputUserName))
        {
            return ValidationResult.Success;
        }

        // 编辑场景下需要排除当前实体本身,避免把自己判定为重复
        if (validationContext.ObjectInstance is User currentUser)
        {
            bool userExists = dbContext.Users.Any(u => 
                u.UserName == inputUserName && u.Id != currentUser.Id);
            return userExists 
                ? new ValidationResult(ErrorMessage ?? "用户名已存在,请更换后重试") 
                : ValidationResult.Success;
        }

        // 新增场景直接查重
        bool newUserExists = dbContext.Users.Any(u => u.UserName == inputUserName);
        return newUserExists 
            ? new ValidationResult(ErrorMessage ?? "用户名已存在,请更换后重试") 
            : ValidationResult.Success;
    }
}

2. 修改实体类的特性标注

替换原来的AllValidation特性,新特性提供无参构造函数,可以正常配置ErrorMessage属性:

[Table("User")]
public partial class User
{
    [Key]
    public Guid Id { get; set; } = Guid.NewGuid();
    [Column("userName")]
    [StringLength(200)]
    [UniqueUserName(ErrorMessage = "用户名已被占用")]
    [Required(ErrorMessage = "Username is a required field")]
    public string UserName { get; set; } = null!;
    
    [StringLength(50, MinimumLength = 3, ErrorMessage = "The password should be between 3 characters to 50")]
    [Required(ErrorMessage = "Password is a required field")]
    public string Password { get; set; } = null!;
    
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy/MM/dd}")]
    public DateTime? LastLogin { get; set; }
    public string? Token { get; set; }
}

3. 补充数据库层面的唯一约束

模型层的校验无法覆盖高并发的竞态场景,必须在数据库层面给UserName字段添加唯一索引,从根本上避免重复数据写入:

public class TalkBackDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 给用户名添加唯一索引
        modelBuilder.Entity<User>()
            .HasIndex(u => u.UserName)
            .IsUnique()
            .HasDatabaseName("IX_User_UserName");
    }
}

编写完成后执行EF Core迁移命令更新数据库即可。

注意:ASP.NET Core 及以上版本默认支持在ValidationContext中获取DI服务,无需额外配置即可直接使用GetService方法拿取DbContext实例。

内容的提问来源于stack exchange,提问作者Tommy Udi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 02:01:12