如何允许用户使用Identity Framework 1.0注册重复的UserName

时间:2015-03-17 08:10:07

标签: c# asp.net-mvc asp.net-identity

我想使用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方法完成它的执行后执行。所以结果会引发错误。

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

1 个答案:

答案 0 :(得分:9)

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

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

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

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

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

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

    public int TenantId { get; set; }
}

<强> CreateUserAsync

public override Task CreateAsync(TUser user) {
if (user == null) {
    throw new ArgumentNullException("user");
}

user.TenantId = this.TenantId;
return base.CreateAsync(user);

}

<强> FindByEmailAsync

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

}

<强> FindByNameAsync

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

}

<强> UserValidator 虽然默认的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 }));
    }
}
The ValidateEntity method is a bit more tricky however, as we will have to reimplement the entire method in order to remove the hardcoded username checks:

    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。请参阅下面的示例(注意使用'example',这些类都是一次性的......)。

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