IdentityUser 的 IUserValidator

IUserValidator for IdentityUser

我有身份用户管理器的自定义用户电子邮件验证器。

它是根据附加的 属性 IsDeleted(新用户可以使用已删除用户的电子邮件添加)创建的,用于验证用户电子邮件。

这是它的样子。

public class UserEmailValidator<TUser> : IUserValidator<TUser>
    where TUser : UserAuth
{
    private readonly IUnitOfWork _unitOfWork;

    public UserEmailValidator(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user)
    {
        var errors = new List<IdentityError>();

        var existingAccount = await _unitOfWork.AuthUsers.Get()
            .FirstOrDefaultAsync(u => u.NormalizedEmail == user.Email.ToUpper() && !u.IsDeleted);
        if (existingAccount != null)
            errors.Add(new IdentityError() { Code = GlobalData.Translations.IdentityKeys.DuplicateEmail });

        return errors.Any()
            ? IdentityResult.Failed(errors.ToArray())
            : IdentityResult.Success;
    }
}

当我使用方法 var res1 = await userManager.CreateAsync(newUser) 时,它按预期工作。 res1 的值为成功。

但实际上当用户分配给角色时也会调用 ValidateAsycn 方法

var res2 = await userManager.AddToRoleAsync(newUser, "admin") 

并且 res2 的结果是失败的,因为有重复的电子邮件。 有没有办法阐明要验证的操作(用户创建、添加到角色或任何其他类型的操作),以便为所有情况提供正确的验证?

这是来自 Startup.cs

的设置
var userBuilder = services.AddIdentity<UserAuth, Role>(options =>
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
    options.Lockout.MaxFailedAccessAttempts = 10;
    options.Lockout.AllowedForNewUsers = true;

    options.ClaimsIdentity.UserIdClaimType = GlobalData.CustomClaimNames.UserId;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserValidator<UserEmailValidator<UserAuth>>()
.AddDefaultTokenProviders()
.AddEmailAndPasswordConfimationTotpTokenProvider();

更新:

await _userManager.UpdateAsync(user);
await _userManager.RemovePasswordAsync(user);

也调用相同的 ValidateAsync 方法

Is there a way to clarify what the action is going to be validated...?

没有调用 ValidateUser 的上下文,除了 User 本身,这意味着没有真正的方法可以确切地知道 为什么 它正在调用。

UserManager 包含一个 protected 方法 (ValidateAsync),每当调用 UserManager.CreateAsyncUserManager.UpdateUserAsync 时都会在内部调用该方法。对 AddToRoleAsync 的调用会导致对 UpdateUserAsync 的调用,最终会通过 IUserValidator<TUser> 的实现以 ValidateAsync 运行 来执行验证。

执行 built-in UserValidator<TUser> that you've replaced handles your problem with the following check (source):

var owner = await manager.FindByEmailAsync(email);
if (owner != null && 
    !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)))
{
    errors.Add(Describer.DuplicateEmail(email));
}

首先,它会检查是否存在使用该电子邮件地址的现有帐户,然后检查该帐户是否与正在验证的帐户相同。如果相同,则不是重复。

这一切都意味着您应该能够扩展您的检查以执行类似的逻辑。例如:

var existingAccount = await _unitOfWork.AuthUsers.Get().FirstOrDefaultAsync(u =>
    u.NormalizedEmail == user.Email.ToUpper() &&
    !u.IsDeleted &&
    u.Id != user.Id);

在这里,我添加了 u.Id != user.Id,它比 built-in 实现要简单得多,因为您的代码知道它正在使用 UserAuth 并且可以直接使用它的属性。