J’essaie quelque chose d’assez exotique, je crois, et je suis confronté à quelques problèmes, qui, j’espère, peuvent être résolus avec l’aide des utilisateurs ici sur StackOverflow.
J’écris et demande ce qui nécessite une authentification et un enregistrement. J’ai choisi d’utiliser Microsoft.AspNet.Identity
. Je ne l’ai pas beaucoup utilisé par le passé, alors ne me jugez pas sur cette décision.
Le cadre mentionné ci-dessus contient une table d’utilisateurs, qui contient tous les utilisateurs enregistrés.
J’ai créé un exemple de photo pour montrer le fonctionnement de l’application.
L’application se compose de 3 composants différents:
J’ai donc un backend sur lequel les clients peuvent s’inscrire. Il y a un utilisateur unique par membre, donc pas de problème ici. Un client est une société ici et les utilisateurs finaux sont des clients de la société.
Vous voyez peut-être déjà le problème. Il est parfaitement possible que l’ User 1
soit un client du Customer 1
mais également du Customer 2
.
Désormais, un client peut inviter un membre à utiliser l’application Mobile. Lorsqu’un client fait cela, l’utilisateur final reçoit un courrier électronique avec un lien pour l’activer lui-même.
Maintenant, tout fonctionne bien tant que vos utilisateurs sont uniques, mais j’ai un utilisateur qui est un client de Customer 1
et Customer 2
. Les deux clients peuvent inviter le même utilisateur et celui-ci devra s’inscrire deux fois, une pour chaque Customer
.
Dans Microsoft.AspNet.Identity
cadre Microsoft.AspNet.Identity
, les utilisateurs doivent être uniques, ce que, selon ma situation, je ne suis pas en mesure de gérer.
Est-il possible d’append à IdentityUser
des parameters supplémentaires IdentityUser
qu’un utilisateur est unique?
Créez une classe personnalisée qui hérite d’ IdentityUser
et qui inclut un ID d’application:
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 }
Modifié mon IDbContext
conséquence:
public class AppServerContext : IdentityDbContext, IDbContext { }
Appels modifiés utilisant le framework.
IUserStore -> IUserStore UserManager(_userStore) -> UserManager(_userStore);
_userStore
est hors du type IUserStore
Cependant, lorsque j’enregistre un utilisateur avec un nom d’utilisateur déjà utilisé, je reçois quand même un message d’erreur indiquant que le nom d’utilisateur est déjà utilisé:
var result = await userManager.CreateAsync(new AppServerUser {UserName = "testing"}, "testing");
Je crois que je dois changer le UserManager
mais je n’en suis pas sûr. J’espère que quelqu’un ici a suffisamment de connaissances sur le cadre pour m’aider, car il bloque vraiment le développement de nos applications.
Si ce n’est pas possible, je voudrais également savoir, et vous pouvez peut-être me diriger vers un autre cadre qui me permet de le faire.
Remarque: je ne veux pas écrire moi-même toute une gestion d’utilisateurs, car cela va réinventer la roue.
Tout d’abord, je comprends l’idée qui sous-tend vos pensées et, à ce titre, je vais commencer à expliquer le «pourquoi» n’est-il pas possible de créer plusieurs utilisateurs avec le même nom.
Le nom d’utilisateur portant le même nom: Le problème rencontré actuellement est lié à IdentityDbContext. Comme vous pouvez le voir ( https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs ), identityDbContext définit les règles relatives aux utilisateurs et rôles uniques, Premier sur la création de modèle :
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 IndexAtsortingbute("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 IndexAtsortingbute("RoleNameIndex") { IsUnique = true })); role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId); }
d’autre part sur l’entité validée:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary
Le conseil: pour résoudre ce problème facilement, c’est, sur le champ ApplicationDbContext que vous possédez actuellement, remplacer ces deux méthodes pour surmonter cette validation.
Avertissement Sans cette validation, vous pouvez maintenant utiliser plusieurs utilisateurs avec le même nom, mais vous devez mettre en œuvre des règles qui vous empêchent de créer des utilisateurs dans le même client, avec le même nom d’utilisateur. Ce que vous pouvez faire est d’append cela à la validation.
J’espère que l’aide était précieuse 🙂 À la votre!
Peut-être que quelqu’un peut trouver cela utile. Dans notre projet, nous utilisons l’identité ASP.NET 2 et, un jour, nous avons rencontré le cas où deux utilisateurs ont des noms identiques. Nous utilisons les e-mails comme identifiants dans notre application et ils doivent, en effet, être uniques. Mais nous ne voulons de toute façon pas que les noms d’utilisateurs soient uniques. Ce que nous avons fait vient de personnaliser quelques classes de cadre d’identité comme suit:
Nous avons modifié notre AppIdentityDbContext
en créant un index sur le champ Nom d’utilisateur non unique et en remplaçant ValidateEntity
de manière délicate. Et ensuite, en utilisant les migrations update database. Le code ressemble à:
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 IndexAtsortingbute("UserNameIndex") { IsUnique = false })); modelBuilder .Entity () .Property(c => c.Email) .IsRequired() .HasColumnAnnotation( "Index", new IndexAnnotation(new[] { new IndexAtsortingbute("EmailIndex") {IsUnique = true} })); } /// /// Override 'ValidateEntity' to support multiple users with the same name /// /// /// /// protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary
Créez une classe personnalisée de l’ IIdentityValidator
et définissez-la sur notre UserManager
:
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 (ssortingng.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(ssortingng.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 (ssortingng.IsNullOrWhiteSpace(email)) { errors.Add(ssortingng.Format("{0} cannot be null or empty.", "Email")); return; } try { var m = new MailAddress(email); } catch (FormatException) { errors.Add(ssortingng.Format("Email '{0}' is invalid", email)); return; } var owner = await Manager.FindByEmailAsync(email); if (owner != null && !owner.Id.Equals(user.Id)) { errors.Add(ssortingng.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);
Et la dernière étape est de changer AppSignInManager
. Parce que nos noms d’utilisateur ne sont pas uniques, nous utilisons un email pour vous connecter:
public class AppSignInManager : SignInManager { .... public virtual async Task PasswordSignInViaEmailAsync(ssortingng userEmail, ssortingng 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; }
Et maintenant, le code ressemble à:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task Index(User model, ssortingng returnUrl) { if (!ModelState.IsValid) { return View(model); } var result = await signInManager.PasswordSignInViaEmailAsync( model.Email, model.Password, model.StaySignedIn, true); var errorMessage = ssortingng.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(ssortingng.Empty, errorMessage); return View(model); }
PS Je n’aime pas trop la façon dont je substitue la méthode ValidateEntity
. Mais j’ai décidé de le faire parce que je devais plutôt implémenter une classe DbContext presque identique à IdentityDbContext
, donc je devais suivre les modifications qui y étaient apscopes lors de la mise à jour du package de framework d’identité dans mon projet.