Microsoft ASP.NET标识 – 具有相同名称的多个用户

我正在尝试一些非常奇特的东西我相信并且我遇到了一些问题,我希望可以在StackOverflow上的用户帮助下解决。

故事

我正在编写和申请,需要认证和注册。 我选择使用Microsoft.AspNet.Identity 。 我过去没有经常使用它,所以不要在这个决定上评判我。

上面提到的框架包含一个users表,它包含所有注册用户。

我已经创建了一个示例图片来展示应用程序的工作方式。

在此处输入图像描述

该应用程序包含3个不同的组件:

  1. 后端(WebAPI)。
  2. 客户(直接使用WebAPI)。
  3. 最终用户(使用移动应用程序 – iOS)。

所以,我确实有一个客户可以注册的后端。 每个成员有一个唯一用户,所以这里没问题。 客户是这里的公司,最终用户是公司的客户。

您可能已经看到了问题, User 1很可能是Customer 1 ,也是Customer 2

现在,客户可以邀请会员使用移动应用程序。 当客户这样做时,最终用户确实会收到一封电子邮件,其中包含自己激活的链接。

现在,只要您的用户是唯一的,这一切都正常,但我确实拥有一个Customer 1Customer 2 。 两个客户都可以邀请同一个用户,用户需要注册2次,每个Customer

问题

Microsoft.AspNet.Identity框架中,用户应该是唯一的,根据我的情况,我无法管理。

这个问题

是否可以向IdentityUser添加额外的参数以确保用户是唯一的?

我已经做了什么

  1. 创建一个inheritance自IdentityUser的自定义类,其中包含一个应用程序ID:

     public class AppServerUser : IdentityUser { #region Properties ///  /// Gets or sets the id of the member that this user belongs to. ///  public int MemberId { get; set; } #endregion } 
  2. 相应地更改了我的IDbContext

     public class AppServerContext : IdentityDbContext, IDbContext { } 
  3. 使用框架的已修改调用。

     IUserStore -> IUserStore UserManager(_userStore) -> UserManager(_userStore); 

    _userStore偏离IUserStore类型的过程

但是,当我使用已经使用的用户名注册用户时,我仍然收到一条错误消息,指出已经使用了用户名:

 var result = await userManager.CreateAsync(new AppServerUser {UserName = "testing"}, "testing"); 

我相信的是一个解决方案

我相信我需要更改UserManager但我不确定。 我希望这里有人对框架有足够的了解,可以帮助我,因为它确实阻碍了我们的应用程序开发。

如果不可能,我也想知道,也许你可以指出另一个允许我这样做的框架。

注意:我不想自己编写完整的用户管理,因为这将重新启动轮子。

首先,我理解你的想法背后的想法,因此我将开始解释“为什么”你不能创建具有相同名称的多个用户。

具有相同名称的用户名:您现在遇到的问题与IdentityDbContext有关。 如您所见( https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs),identityDbContext设置有关唯一用户和角色的规则,首先是模型创建:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { if (modelBuilder == null) { throw new ArgumentNullException("modelBuilder"); } // Needed to ensure subclasses share the same table var user = modelBuilder.Entity() .ToTable("AspNetUsers"); user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId); user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId); user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId); user.Property(u => u.UserName) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("UserNameIndex") { IsUnique = true })); // CONSIDER: u.Email is Required if set on options? user.Property(u => u.Email).HasMaxLength(256); modelBuilder.Entity() .HasKey(r => new { r.UserId, r.RoleId }) .ToTable("AspNetUserRoles"); modelBuilder.Entity() .HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId }) .ToTable("AspNetUserLogins"); modelBuilder.Entity() .ToTable("AspNetUserClaims"); var role = modelBuilder.Entity() .ToTable("AspNetRoles"); role.Property(r => r.Name) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true })); role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId); } 

其次是validation实体:

 protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary items) { if (entityEntry != null && entityEntry.State == EntityState.Added) { var errors = new List(); var user = entityEntry.Entity as TUser; //check for uniqueness of user name and email if (user != null) { if (Users.Any(u => String.Equals(u.UserName, user.UserName))) { errors.Add(new DbValidationError("User", String.Format(CultureInfo.CurrentCulture, IdentityResources.DuplicateUserName, user.UserName))); } if (RequireUniqueEmail && Users.Any(u => String.Equals(u.Email, user.Email))) { errors.Add(new DbValidationError("User", String.Format(CultureInfo.CurrentCulture, IdentityResources.DuplicateEmail, user.Email))); } } else { var role = entityEntry.Entity as TRole; //check for uniqueness of role name if (role != null && Roles.Any(r => String.Equals(r.Name, role.Name))) { errors.Add(new DbValidationError("Role", String.Format(CultureInfo.CurrentCulture, IdentityResources.RoleAlreadyExists, role.Name))); } } if (errors.Any()) { return new DbEntityValidationResult(entityEntry, errors); } } return base.ValidateEntity(entityEntry, items); } } 

提示:您可以轻松地克服此问题,在您当前拥有的ApplicationDbContext上,覆盖这两种方法以克服此validation

警告如果没有该validation,您现在可以使用具有相同名称的多个用户,但您必须实施规则,阻止您使用相同的用户名在同一客户中创建用户。 你可以做的是,将其添加到validation中。

希望帮助很有价值:)干杯!

可能有人可以找到这个有用的。 在我们的项目中,我们使用ASP.NET身份2,有一天我们遇到了两个用户具有相同名称的情况。 我们在我们的应用程序中使用电子邮件作为登录,它们确实必须是唯一的。 但我们不希望用户名具有唯一性。 我们所做的只是定制了几类身份框架,如下所示:

  1. 通过在UserName字段上创建非唯一的索引并以棘手的方式覆盖ValidateEntity来更改我们的AppIdentityDbContext 。 然后使用迁移更新数据库。 代码如下:

     public class AppIdentityDbContext : IdentityDbContext { public AppIdentityDbContext() : base("IdentityContext", throwIfV1Schema: false) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // This needs to go before the other rules! *****[skipped some other code]***** // In order to support multiple user names // I replaced unique index of UserNameIndex to non-unique modelBuilder .Entity() .Property(c => c.UserName) .HasColumnAnnotation( "Index", new IndexAnnotation( new IndexAttribute("UserNameIndex") { IsUnique = false })); modelBuilder .Entity() .Property(c => c.Email) .IsRequired() .HasColumnAnnotation( "Index", new IndexAnnotation(new[] { new IndexAttribute("EmailIndex") {IsUnique = true} })); } ///  /// Override 'ValidateEntity' to support multiple users with the same name ///  ///  ///  ///  protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary items) { // call validate and check results var result = base.ValidateEntity(entityEntry, items); if (result.ValidationErrors.Any(err => err.PropertyName.Equals("User"))) { // Yes I know! Next code looks not good, because I rely on internal messages of Identity 2, but I should track here only error message instead of rewriting the whole IdentityDbContext var duplicateUserNameError = result.ValidationErrors .FirstOrDefault( err => Regex.IsMatch( err.ErrorMessage, @"Name\s+(.+)is\s+already\s+taken", RegexOptions.IgnoreCase)); if (null != duplicateUserNameError) { result.ValidationErrors.Remove(duplicateUserNameError); } } return result; } } 
  2. 创建IIdentityValidator接口的自定义类,并将其设置为UserManager.UserValidator属性:

     public class AppUserValidator : IIdentityValidator { ///  /// Constructor ///  ///  public AppUserValidator(UserManager manager) { Manager = manager; } private UserManager Manager { get; set; } ///  /// Validates a user before saving ///  ///  ///  public virtual async Task ValidateAsync(AppUser item) { if (item == null) { throw new ArgumentNullException("item"); } var errors = new List(); ValidateUserName(item, errors); await ValidateEmailAsync(item, errors); if (errors.Count > 0) { return IdentityResult.Failed(errors.ToArray()); } return IdentityResult.Success; } private void ValidateUserName(AppUser user, List errors) { if (string.IsNullOrWhiteSpace(user.UserName)) { errors.Add("Name cannot be null or empty."); } else if (!Regex.IsMatch(user.UserName, @"^[A-Za-z0-9@_\.]+$")) { // If any characters are not letters or digits, its an illegal user name errors.Add(string.Format("User name {0} is invalid, can only contain letters or digits.", user.UserName)); } } // make sure email is not empty, valid, and unique private async Task ValidateEmailAsync(AppUser user, List errors) { var email = user.Email; if (string.IsNullOrWhiteSpace(email)) { errors.Add(string.Format("{0} cannot be null or empty.", "Email")); return; } try { var m = new MailAddress(email); } catch (FormatException) { errors.Add(string.Format("Email '{0}' is invalid", email)); return; } var owner = await Manager.FindByEmailAsync(email); if (owner != null && !owner.Id.Equals(user.Id)) { errors.Add(string.Format(CultureInfo.CurrentCulture, "Email '{0}' is already taken.", email)); } } } public class AppUserManager : UserManager { public AppUserManager( IUserStore store, IDataProtectionProvider dataProtectionProvider, IIdentityMessageService emailService) : base(store) { // Configure validation logic for usernames UserValidator = new AppUserValidator(this); 
  3. 最后一步是改变AppSignInManager 。 因为现在我们的用户名不是唯一的,我们使用电子邮件登录:

     public class AppSignInManager : SignInManager { .... public virtual async Task PasswordSignInViaEmailAsync(string userEmail, string password, bool isPersistent, bool shouldLockout) { var userManager = ((AppUserManager) UserManager); if (userManager == null) { return SignInStatus.Failure; } var user = await UserManager.FindByEmailAsync(userEmail); if (user == null) { return SignInStatus.Failure; } if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } if (await UserManager.CheckPasswordAsync(user, password)) { await UserManager.ResetAccessFailedCountAsync(user.Id); await SignInAsync(user, isPersistent, false); return SignInStatus.Success; } if (shouldLockout) { // If lockout is requested, increment access failed count which might lock out the user await UserManager.AccessFailedAsync(user.Id); if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } } return SignInStatus.Failure; } 

    现在代码看起来像:

     [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task Index(User model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } var result = await signInManager.PasswordSignInViaEmailAsync( model.Email, model.Password, model.StaySignedIn, true); var errorMessage = string.Empty; switch (result) { case SignInStatus.Success: if (IsLocalValidUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); case SignInStatus.Failure: errorMessage = Messages.LoginController_Index_AuthorizationError; break; case SignInStatus.LockedOut: errorMessage = Messages.LoginController_Index_LockoutError; break; case SignInStatus.RequiresVerification: throw new NotImplementedException(); } ModelState.AddModelError(string.Empty, errorMessage); return View(model); } 

PS我真的不喜欢我如何覆盖ValidateEntity方法。 但我决定这样做,因为我必须实现几乎与IdentityDbContext相同的DbContext类,因此我必须在我的项目中更新身份框架包时跟踪它的更改。