web-dev-qa-db-fra.com

Comment changer le type d'identifiant dans Microsoft.AspNet.Identity.EntityFramework.IdentityUser

(ASP.NET MVC 5, EF6, VS2013)

J'essaie de comprendre comment changer le type du champ "Id" de chaîne en int dans le type:

Microsoft.AspNet.Identity.EntityFramework.IdentityUser

afin que les nouveaux comptes d'utilisateurs soient associés à un ID entier plutôt qu'à un GUID. Mais il semble que cela sera plus compliqué que d'ajouter simplement une nouvelle propriété Id de type int dans ma classe d'utilisateurs dérivés. Jetez un oeil à cette signature de méthode:

(de Assembly Microsoft.AspNet.Identity.Core.dll)

public class UserManager<TUser> : IDisposable where TUser : global::Microsoft.AspNet.Identity.IUser
  {
  ...
  public virtual Task<IdentityResult> AddLoginAsync(string userId, UserLoginInfo login);
  ...
  }

Il semble donc que d’autres méthodes intégrées dans la structure d’identité ASP.NET exigent que l’utilisateur userId soit une chaîne. Aurais-je besoin de réimplémenter ces classes aussi?

ne explication de la raison pour laquelle je ne souhaite pas stocker les GUID pour les identifiants dans la table des utilisateurs:

-Il y aura d'autres tables qui relient les données à la table des utilisateurs via une clé étrangère. (Lorsque les utilisateurs enregistrent du contenu sur le site.) Je ne vois aucune raison d'utiliser un type de champ plus grand et de dépenser de l'espace supplémentaire dans la base de données sans aucun avantage clair. (Je sais qu'il existe d'autres publications sur l'utilisation des GUID par rapport aux identifiants int, mais il semblerait que beaucoup suggèrent que les identifiants int sont plus rapides et utilisent moins d'espace, ce qui me laisse toujours perplexe.)

-Je prévois d’exposer un point de terminaison reposant pour permettre aux utilisateurs de récupérer des données sur un utilisateur particulier. Je pense:

/users/123/name

est plus propre que

/users/{af54c891-69ba-4ddf-8cb6-00d368e58d77}/name

Est-ce que quelqu'un sait pourquoi l'équipe ASP.NET a décidé d'implémenter les ID de cette façon? Suis-je myope en essayant de changer cela en un type int? (Il y a peut-être des avantages qui me manquent.)

Merci...

-Ben

66
BenjiFB

Donc, si vous voulez des identifiants int, vous devez créer votre propre classe POCO IUser et implémenter votre IUserStore pour votre classe IUser personnalisée dans la version 1.0 RTM.

C’est quelque chose que nous n’avons pas eu le temps de soutenir, mais j’essaie de le rendre plus facile dans la version 1.1 pour le moment. Espérons que quelque chose sera disponible dans les versions nocturnes bientôt.

Mise à jour avec l'exemple 1.1-alpha1: Comment obtenir des édifices nocturnes

Si vous mettez à jour les derniers bits nocturnes, vous pouvez essayer le nouvel apis 1.1-alpha1, ce qui devrait simplifier les choses maintenant: voici à quoi ressemblerait l'insertion de guids au lieu de chaînes, par exemple

    public class GuidRole : IdentityRole<Guid, GuidUserRole> { 
        public GuidRole() {
            Id = Guid.NewGuid();
        }
        public GuidRole(string name) : this() { Name = name; }
    }
    public class GuidUserRole : IdentityUserRole<Guid> { }
    public class GuidUserClaim : IdentityUserClaim<Guid> { }
    public class GuidUserLogin : IdentityUserLogin<Guid> { }

    public class GuidUser : IdentityUser<Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> {
        public GuidUser() {
            Id = Guid.NewGuid();
        }
        public GuidUser(string name) : this() { UserName = name; }
    }

    private class GuidUserContext : IdentityDbContext<GuidUser, GuidRole, Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> { }
    private class GuidUserStore : UserStore<GuidUser, GuidRole, Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> {
        public GuidUserStore(DbContext context)
            : base(context) {
        }
    }
    private class GuidRoleStore : RoleStore<GuidRole, Guid, GuidUserRole> {
        public GuidRoleStore(DbContext context)
            : base(context) {
        }
    }

    [TestMethod]
    public async Task CustomUserGuidKeyTest() {
        var manager = new UserManager<GuidUser, Guid>(new GuidUserStore(new GuidUserContext()));
        GuidUser[] users = {
            new GuidUser() { UserName = "test" },
            new GuidUser() { UserName = "test1" }, 
            new GuidUser() { UserName = "test2" },
            new GuidUser() { UserName = "test3" }
            };
        foreach (var user in users) {
            UnitTestHelper.IsSuccess(await manager.CreateAsync(user));
        }
        foreach (var user in users) {
            var u = await manager.FindByIdAsync(user.Id);
            Assert.IsNotNull(u);
            Assert.AreEqual(u.UserName, user.UserName);
        }
    }
30
Hao Kung

En utilisant une réponse de Stefan Cebulak et un excellent article de blog de Ben Foster ASP.NET Identity Stripped Bare J'ai mis au point la solution ci-dessous, que j'ai appliquée à ASP.NET Identity 2.0 avec un fichier généré par Visual Studio 2013 AccountController.

La solution utilise un entier comme clé primaire pour les utilisateurs et permet également d'obtenir un ID d'utilisateur actuellement connecté sans effectuer de déplacement vers la base de données.

Voici les étapes à suivre:

1. Créer des classes personnalisées liées à l'utilisateur

Par défaut, AccountController utilise des classes qui utilisent string comme type de clé primaire. Nous devons créer des classes ci-dessous, qui utiliseront plutôt int. J'ai défini toutes les classes ci-dessous dans un seul fichier: AppUser.cs

public class AppUser :
    IdentityUser<int, AppUserLogin, AppUserRole, AppUserClaim>,
    IUser<int>
{

}

public class AppUserLogin : IdentityUserLogin<int> { }

public class AppUserRole : IdentityUserRole<int> { }

public class AppUserClaim : IdentityUserClaim<int> { }

public class AppRole : IdentityRole<int, AppUserRole> { }

Il sera également utile d’avoir un ClaimsPrincipal personnalisé, qui exposera facilement l’ID de l’utilisateur.

public class AppClaimsPrincipal : ClaimsPrincipal
{
    public AppClaimsPrincipal( ClaimsPrincipal principal ) : base( principal )
    { }

    public int UserId
    {
        get { return int.Parse(this.FindFirst( ClaimTypes.Sid ).Value); }
    }
}

2. Créez un personnalisé IdentityDbContext

Le contexte de base de données de notre application va s'étendre à IdentityDbContext, ce qui implémente par défaut tous les DbSets liés à l'authentification. Même si DbContext.OnModelCreating est une méthode vide, je ne suis pas sûr du IdentityDbContext.OnModelCreating, donc lors de la substitution, pensez à appeler base.OnModelCreating( modelBuilder )AppDbContext.cs

public class AppDbContext :
    IdentityDbContext<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppDbContext() : base("DefaultConnection")
    {
        // Here use initializer of your choice
        Database.SetInitializer( new CreateDatabaseIfNotExists<AppDbContext>() );
    }


    // Here you define your own DbSet's



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

        // Here you can put FluentAPI code or add configuration map's
    }
}

3. Créez des UserStore et UserManager personnalisés, à utiliser ci-dessus

AppUserStore.cs

public interface IAppUserStore : IUserStore<AppUser, int>
{

}

public class AppUserStore :
    UserStore<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>,
    IAppUserStore
{
    public AppUserStore() : base( new AppDbContext() )
    {

    }

    public AppUserStore(AppDbContext context) : base(context)
    {

    }
}

AppUserManager.cs

public class AppUserManager : UserManager<AppUser, int>
{
    public AppUserManager( IAppUserStore store ) : base( store )
    {

    }
}

4. Modifiez AccountController pour utiliser vos classes personnalisées

Remplacez tous UserManager par AppUserManager, UserStore par AppUserStore etc. Prenez un exemple de ce constructeur:

public AccountController()
    : this( new AppUserManager( new AppUserStore( new AppDbContext() ) ) )
{
}

public AccountController(AppUserManager userManager)
{
    UserManager = userManager;
}

5. Ajouter l'identifiant de l'utilisateur en tant que revendication à ClaimIdentity stocké dans un cookie

À l'étape 1, nous avons créé AppClaimsPrincipal, qui expose l'ID utilisateur extrait de ClaimType.Sid. Toutefois, pour que cette revendication soit disponible, nous devons l’ajouter lors de la connexion de l’utilisateur. Dans AccountController, une méthode SingInAsync est chargée de la connexion. Nous devons ajouter une ligne à cette méthode pour ajouter la revendication.

private async Task SignInAsync(AppUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    // Extend identity claims
    identity.AddClaim( new Claim( ClaimTypes.Sid, user.Id.ToString() ) );

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

6. Créez un BaseController avec une propriété CurrentUser

Pour avoir un accès facile à l'ID d'un utilisateur actuellement connecté dans vos contrôleurs, créez un résumé BaseController, à partir duquel vos contrôleurs seront dérivés. Dans le BaseController, créez un CurrentUser comme suit:

public abstract class BaseController : Controller
{
    public AppClaimsPrincipal CurrentUser
    {
        get { return new AppClaimsPrincipal( ( ClaimsPrincipal )this.User ); }
    }


    public BaseController()
    {

    }
}

7. Héritez vos contrôleurs de BaseController et profitez

À partir de maintenant, vous pouvez utiliser CurrentUser.UserId dans vos contrôleurs pour accéder à l'ID d'un utilisateur actuellement connecté sans se rendre dans la base de données. Vous pouvez l'utiliser pour interroger uniquement les objets appartenant à l'utilisateur.

Vous n'avez pas à vous préoccuper de la génération automatique des clés primaires des utilisateurs - pas de surprise, Entity Framework utilise par défaut Identity pour les clés primaires de nombres entiers lors de la création de tables.

Attention! Gardez à l'esprit que si vous l'implémentez dans un projet déjà publié, pour les utilisateurs déjà connectés, ClaimsType.Sid n'existera pas et FindFirst retournera null dans AppClaimsPrincipal. Vous devez forcer la déconnexion de tous les utilisateurs ou gérer ce scénario dans AppClaimsPrincipal

52
krzychu

@HaoKung

J'ai réussi à créer des identifiants int avec vos constructions nocturnes. User.Identity.GetUserId () problème est toujours là, mais je viens de faire int.parse () pour le moment.

La plus grosse surprise était que je n'avais pas besoin de créer un identifiant par moi-même, la base de données était créée avec un identifiant d'identité et il était en quelque sorte automatiquement défini pour les nouveaux utilisateurs ...

Modèle:

    public class ApplicationUser : IdentityUser<int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public ApplicationUser()
    {
    }
    public ApplicationUser(string name) : this() { UserName = name; }
}

public class ApplicationDbContext : IntUserContext
{
    public ApplicationDbContext()
    {

    }
}

private class IntRole : IdentityRole<int, IntUserRole>
{
    public IntRole()
    {
    }
    public IntRole(string name) : this() { Name = name; }
}
private class IntUserRole : IdentityUserRole<int> { }
private class IntUserClaim : IdentityUserClaim<int> { }
private class IntUserLogin : IdentityUserLogin<int> { }
private class IntUserContext : IdentityDbContext<ApplicationUser, IntRole, int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public IntUserContext()
        : base("DefaultConnection")
    {

    }
}
private class IntUserStore : UserStore<ApplicationUser, IntRole, int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public IntUserStore(DbContext context)
        : base(context)
    {

    }
}
private class IntRoleStore : RoleStore<IntRole, int, IntUserRole>
{
    public IntRoleStore(DbContext context)
        : base(context)
    {
    }
}

Manette:

        public AccountController()
        : this(new UserManager<ApplicationUser, int>(new IntUserStore(new ApplicationDbContext())))
    {
    }

    public AccountController(UserManager<ApplicationUser, int> userManager)
    {
        UserManager = userManager;
    }

    public UserManager<ApplicationUser, int> UserManager { get; private set; }

La version de sortie d'espoir viendra bientôt: D ...

P.S. Je ne peux pas écrire de commentaires, alors j'ai répondu, désolée.

6
Stefan Cebulak

Comme indiqué ici :

Dans Visual Studio 2013, l'application Web par défaut utilise une valeur de chaîne pour la clé des comptes d'utilisateur. ASP.NET Identity vous permet de modifier le type de clé pour répondre à vos besoins en matière de données. Par exemple, vous pouvez changer le type de clé d'une chaîne en un entier.

Cette rubrique sur le lien ci-dessus montre comment démarrer avec l'application Web par défaut et modifier la clé de compte d'utilisateur en un entier. Vous pouvez utiliser les mêmes modifications pour implémenter tout type de clé dans votre projet. Il montre comment effectuer ces modifications dans l'application Web par défaut, mais vous pouvez appliquer des modifications similaires à une application personnalisée. Il montre les modifications nécessaires lorsque vous utilisez MVC ou Web Forms.

4
Manik Arora

Fondamentalement, vous devez:

-Changez le type de la clé en int dans la classe d'utilisateurs Identity
- Ajouter des classes d'identité personnalisées utilisant int comme clé
- Change la classe de contexte et le gestionnaire d’utilisateurs pour utiliser int comme clé
- Change la configuration de démarrage pour utiliser int comme clé
- Changez le AccountController pour passer int en tant que clé

ici est un lien où toutes les étapes sont expliquées pour y parvenir.

3
lyz