如何允许用户使用身份框架1.0使用重复的用户名进行注册

问题描述 投票:0回答:1

我想使用 Identity Framework 1.0 在 MVC 中开发一个应用程序,该应用程序允许用户使用其他用户使用的相同用户名进行注册。

删除用户时,我想将其

IsDeleted
自定义属性设置为 true,而不是从数据库中删除用户。在这种情况下,另一个用户可以使用
UserName
设置为 true 的用户的
IsDeleted

但是默认的

UserManager.CreateAsync(user, password);
方法阻止这样做。

我已经像这样覆盖了

ValidateEntity
IdentityDbContext
方法

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    if ((entityEntry != null) && (entityEntry.State == EntityState.Added))
    {
        ApplicationUser user = entityEntry.Entity as ApplicationUser;
        if ((user != null) && this.Users.Any<ApplicationUser>(u => string.Equals(u.UserName, user.UserName) && u.IsDeleted==false))
        {
            return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) {
                ValidationErrors = { 
                    new DbValidationError("User", string.Format(CultureInfo.CurrentCulture, 
                       "", new object[] { user.UserName }))
                } 
            };
        }
        IdentityRole role = entityEntry.Entity as IdentityRole;
        if ((role != null) && this.Roles.Any<IdentityRole>(r => string.Equals(r.Name, role.Name)))
        {
            return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) { 
                ValidationErrors = { 
                    new DbValidationError("Role", string.Format(CultureInfo.CurrentCulture, 
                        "", new object[] { role.Name })) } };
        }
    }
    return base.ValidateEntity(entityEntry, items);
}

这是我创建用户的注册方法

public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.UserName, Email = model.Email.ToLower(), CreatedBy = model.UserName, CreatedDate = DateTime.UtcNow, };

        user.ConfirmedEmail = false;
        var result = await _accountService.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {

            TempData["MessageConfirm"] = user.Email;
            return RedirectToAction("Confirm", "Account");
        }
        else
        {
            AddErrors(result);
        }
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}

ValidateEntity
方法应该在
await _accountService.CreateAsync(user, model.Password);
执行时执行。但它是在
register
方法完成执行后执行的。所以结果会抛出错误。

有什么建议我可以实现这一目标吗?

c# asp.net-mvc asp.net-identity
1个回答
30
投票

我最讨厌的是当人们决定提供建议而不是回答所提出的问题时。 用户名不必是唯一的。 db key 是 userid 而不是用户名。 如果您的应用程序为多家公司提供服务,则不同公司的员工有可能拥有相同的用户名。 为了做到这一点,你必须扩展 aspnet 身份。

https://www.scottbrady91.com/ASPNET-Identity/Quick-and-Easy-ASPNET-Identity-Multitenancy

身份用户 首先,我们需要通过扩展 IdentityUser 类来添加 TenantId 声明(您可以根据您的业务需求对其进行重命名)。虽然这在概念上是一个声明,但我们将利用 AspNetUser 表并将 TenantId 添加为属性,因为我们将通过此属性进行相当多的查询。为简单起见,我将 TenantId 添加为 int,但非迭代替代方案是使用字符串。

public class ApplicationUser : IdentityUser 
{
    public int TenantId { get; set; }
}

用户商店 接下来,我们将为了解新属性的新用户实现 UserStore。在这里,我们使用 UserStore 类中的属性来设置 TenantId,从而允许我们使用多租户实现覆盖基本实现。

public class ApplicationUserStore<TUser> : UserStore<TUser> 
    where TUser : ApplicationUser 
{
    public ApplicationUserStore(DbContext context)
        : base(context) {
    }

    public int TenantId { get; set; }
}

创建用户异步

public override Task CreateAsync(TUser user) 
{
    if (user == null) {
        throw new ArgumentNullException("user");
    }
    
    user.TenantId = this.TenantId;
    return base.CreateAsync(user);
}

通过电子邮件查找异步

public override Task<TUser> FindByEmailAsync(string email) 
{
    return this.GetUserAggregateAsync(u => 
        u.Email.ToUpper() == email.ToUpper() && 
        u.TenantId == this.TenantId);
}

按名称查找异步

public override Task<TUser> FindByNameAsync(string userName) 
{
    return this.GetUserAggregateAsync(u => 
        u.UserName.ToUpper() == userName.ToUpper() && 
        u.TenantId == this.TenantId);
}

用户验证器 虽然默认的 UserValidator 对重复的用户名进行硬编码检查,但我们对 UserStore 方法 FindByNameAsync 和 FindByEmailAsync 的新实现将允许正确的多租户行为(假设您已在 UserStore 中设置 TenantId)。这意味着我们可以充分利用默认的 UserValidator 并在必要时扩展它。

IdentityDbContext 现在有一个尴尬的地方。 ASP.NET Identity 团队再次对 IdentityDbContext 类中的重复用户名检查进行硬编码,但这一次它既在 ValidateEntity 方法中又在 EF 数据库架构本身中使用索引。

索引可以通过扩展OnModelCreating方法来解决,根据用户名更改唯一索引,同时查找我们的TenantId(复合索引)。这可以避免我们丢失这个有用的索引,并优化我们的数据库以实现多租户。您可以使用以下覆盖方法来执行此操作:

public class ApplicationUserDbContext<TUser> : IdentityDbContext<TUser> 
  where TUser : ApplicationUser 
{
    public ApplicationUserDbContext(string nameOrConnectionString)
      : base(nameOrConnectionString) {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    {
        base.OnModelCreating(modelBuilder);

        var user = modelBuilder.Entity<TUser>();

        user.Property(u => u.UserName)
            .IsRequired()
            .HasMaxLength(256)
            .HasColumnAnnotation("Index", new IndexAnnotation(
                new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 1}));

        user.Property(u => u.TenantId)
            .IsRequired()
            .HasColumnAnnotation("Index", new IndexAnnotation(
                new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 2 }));
    }
}

但是 ValidateEntity 方法有点棘手,因为我们必须重新实现整个方法才能删除硬编码的用户名检查:

protected override DbEntityValidationResult ValidateEntity(
    DbEntityEntry entityEntry, 
    IDictionary<object, object> items) 
{
    if (entityEntry != null && entityEntry.State == EntityState.Added) {
        var errors = new List<DbValidationError>();
        var user = entityEntry.Entity as TUser;

        if (user != null) {
            if (this.Users.Any(u => string.Equals(u.UserName, user.UserName) 
              && u.TenantId == user.TenantId)) {
                errors.Add(new DbValidationError("User", 
                  string.Format("Username {0} is already taken for AppId {1}", 
                    user.UserName, user.TenantId)));
            }

            if (this.RequireUniqueEmail 
              && this.Users.Any(u => string.Equals(u.Email, user.Email) 
              && u.TenantId == user.TenantId)) {
                errors.Add(new DbValidationError("User", 
                  string.Format("Email Address {0} is already taken for AppId {1}", 
                    user.UserName, user.TenantId)));
            }
        }
        else {
            var role = entityEntry.Entity as IdentityRole;

            if (role != null && this.Roles.Any(r => string.Equals(r.Name, role.Name))) 
            {
                errors.Add(new DbValidationError("Role", 
                  string.Format("Role {0} already exists", role.Name)));
            }
        }
        if (errors.Any()) {
            return new DbEntityValidationResult(entityEntry, errors);
        }
    }

    return new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
}

客户 现在剩下的就是初始化类。不要忘记每次新建上下文时都需要提供 TenantId。请参阅下面的示例(请注意“示例”的使用,这些类都是一次性的......)。

var context = new ApplicationUserDbContext<ApplicationUser>("DefaultConnection");
var userStore = new ApplicationUserStore<ApplicationUser>(context) { TenantId = 1 };
var userManager = new UserManager<ApplicationUser, string>(userStore);

src:https://www.scottbrady91.com/aspnet-identity/quick-and-easy-aspnet-identity-multitenancy

© www.soinside.com 2019 - 2024. All rights reserved.