68 votes

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 string à 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 soit plus compliqué que de simplement ajouter une nouvelle propriété Id de type int dans ma classe utilisateur dérivée. Jetez un coup d'oeil à la signature de cette méthode :

(de l'assemblée 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 qu'il existe d'autres méthodes intégrées au cadre d'identité ASP.NET qui exigent que l'ID utilisateur soit une chaîne. Devrais-je réimplémenter ces classes également ?

Une explication de la raison pour laquelle je ne veux pas stocker les GUIDs pour les ids 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 le type de champ le plus grand et de dépenser de l'espace supplémentaire dans la base de données sans avantages évidents. (Je sais qu'il y a d'autres posts sur l'utilisation des GUIDs par rapport aux int ids, mais il semble que beaucoup suggèrent que les int ids sont plus rapides et utilisent moins d'espace, ce qui me laisse encore songeur).

-Je prévois d'exposer un point de terminaison Restful 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

Quelqu'un sait-il pourquoi l'équipe ASP.NET a décidé d'implémenter les ID de cette manière ? Est-ce que je manque de perspicacité en essayant de passer à un type int (il y a peut-être des avantages qui me manquent) ?

Merci...

-Ben

0 votes

J'ai mis à jour ma réponse avec un exemple de code permettant de changer le type dans la dernière version nocturne 1.1-alpha1.

6 votes

Note aux futurs lecteurs de cette question : La version 2.0.0 de ASP.NET Identity (publiée le 20 mars 2014) intègre désormais la possibilité de modifier/étendre le type de l'identifiant/clé primaire. Voir blogs.msdn.com/b/webdev/archive/2014/03/20/

2 votes

Et pour les futurs lecteurs, il y a un exemple de solution avec User.ID comme entier : aspnet.codeplex.com/SourceControl/latest#Samples/Identity/

51voto

krzychu Points 595

Utilisation d'un Stefan Cebulak et un excellent article du blog de Ben Foster. L'identité ASP.NET mise à nue J'ai trouvé la solution ci-dessous, que j'ai appliquée à l'identité ASP.NET. 2.0 avec une générée par Visual Studio 2013 AccountController .

La solution utilise un nombre entier comme clé primaire pour les utilisateurs et permet également d'obtenir l'ID de l'utilisateur actuellement connecté sans avoir à se rendre dans la base de données.

Voici les étapes que vous devez suivre :

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

Par défaut, le AccountController utilise des classes, qui utilisent string comme type de clé primaire. Nous devons créer les classes ci-dessous, qui utiliseront une clé primaire. int à la place. 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 IdentityDbContext

Le contexte de la base de données de notre application s'étendra IdentityDbContext 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 certain de l'utilité de l'option IdentityDbContext.OnModelCreating Par conséquent, lorsque vous remplacez une fonction, n'oubliez pas d'appeler la fonction 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 y UserManager qui utilisera la méthode 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. Modifier AccountController pour utiliser vos classes personnalisées

Changez tout UserManager a AppUserManager , UserStore a AppUserStore etc. Prenons un exemple de ces constructeurs :

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

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

5. Ajouter l'ID de l'utilisateur en tant que réclamation à ClaimIdentity stocké dans un cookie

A l'étape 1, nous avons créé AppClaimsPrincipal qui expose le UserId extrait de ClaimType.Sid . Cependant, pour que cette demande soit disponible, nous devons l'ajouter lors de la connexion de l'utilisateur. Dans AccountController a SingInAsync est responsable de l'ouverture de session. Nous devons ajouter une ligne à cette méthode, pour ajouter la réclamation.

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 un CurrentUser propriété

Pour avoir un accès facile à l'ID d'un utilisateur actuellement connecté dans vos contrôleurs, créez une abstraction BaseController dont découleront vos contrôleurs. Dans le BaseController créer un CurrentUser comme suit :

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

    public BaseController()
    {

    }
}

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

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

Vous n'avez pas à vous occuper de la génération automatique des clés primaires des utilisateurs - sans surprise, Entity Framework utilise par défaut Identity pour les clés primaires des entiers, lors de la création des 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 soit forcer la déconnexion de tous les utilisateurs, soit gérer ce scénario dans le cadre de l'application de la méthode de gestion de la sécurité. AppClaimsPrincipal

0 votes

Tout fonctionne, sauf ceci FindFirst(ClaimTypes.Sid) me donne toujours null, donc j'ai une exception à AppClaimsPrincipal.UserId. Il semble qu'après s'être connecté, l'utilisateur n'a pas reçu de nouvelle demande (j'ai ajouté votre code).

0 votes

Êtes-vous sûr que tout le code de l'étape 5. est exécuté ? Supprimez également la trace que votre connexion utilise (par exemple, le cookie) et vérifiez qu'elle est correctement définie après la connexion.

0 votes

Oui, j'en suis sûr. L'utilisateur est enregistré avec une identité ayant toutes les revendications nécessaires. Lorsque je recherche l'utilisateur dans le contrôleur, il n'a qu'une seule réclamation (à l'adresse GetAuthenticationManager().SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); il a 4 revendications.

31voto

Hao Kung Points 13035

Donc si vous voulez des int ids, vous devez créer votre propre classe IUser POCO 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 prendre en charge, mais je cherche actuellement à rendre cela plus facile dans la version 1.1. J'espère que quelque chose sera bientôt disponible dans les nightly builds.

Mis à jour avec l'exemple 1.1-alpha1 : Comment obtenir les builts nocturnes

Si vous mettez à jour les derniers bits nocturnes, vous pouvez essayer les nouvelles apis 1.1-alpha1 qui devraient rendre cela plus facile maintenant : Voici à quoi devrait ressembler le branchement de Guids au lieu de chaînes de caractères, 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);
        }
    }

0 votes

Merci de cette précision, Hao. Pouvez-vous expliquer pourquoi les GUIDs ont été utilisés au lieu des ints ?

1 votes

Nous avons donc opté pour des clés de type chaîne pour éviter d'avoir à gérer les problèmes de sérialisation des clés, l'implémentation par défaut de EF aurait pu utiliser des ints comme clé primaire, les GUIDs étaient juste un moyen facile de générer une clé de type chaîne unique et aléatoire.

3 votes

@Hao - pourquoi l'implémentation utilise-t-elle le champ "string" dans la base de données au lieu de l'uniqueidentifier ? Y a-t-il un moyen d'utiliser l'uniqueidentifier sql dans la base de données ?

6voto

Stefan Cebulak Points 67

@HaoKung

J'ai réussi à faire des int id avec vos nightly builds. Le problème de User.Identity.GetUserId() est toujours là, mais j'ai juste fait int.parse() pour le moment.

La plus grande surprise a été que je n'ai pas eu besoin de créer l'ID par moi-même, la base de données a été créée avec l'ID de l'identité et il a été en quelque sorte automatiquement mis pour les nouveaux utilisateurs Oo....

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)
    {
    }
}

Contrôleur :

        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; }

J'espère que le release build arrivera bientôt :D...

P.S. Je ne peux pas écrire de commentaires alors j'ai fait une réponse, désolé.

0 votes

Stefan, qu'as-tu fait pour le ChallengeResult() y AuthenticationManager.GetExternalLoginInfoAsync() . Les deux méthodes sont situées dans AccountController() et tous deux s'attendent à ce que le UserId comme paramètre mais comme string . Vous ne pouvez donc pas le convertir en int . Avez-vous analysé User.Identity.GetUserId() à int aussi ou pas ?

0 votes

Non, pour être honnête, je ne l'avais pas remarqué, mais l'authentification par Google et Facebook fonctionne correctement. Je pense que je ne vais pas le changer mais plutôt attendre la release build. Cela semble avoir quelque chose à voir avec la protection XSRF.

0 votes

Que voulez-vous dire par "attendre la version finale" ? La dernière version de asp.net-identity est sorti hier ?

4voto

innomanik Points 21

Comme indiqué aquí :

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

Cette rubrique sur le lien ci-dessus montre comment démarrer avec l'application web par défaut et changer la clé du compte utilisateur en un nombre entier. Vous pouvez utiliser les mêmes modifications pour implémenter n'importe quel type de clé dans votre projet. Elle 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 travaillez avec MVC ou Web Forms.

3voto

lyz Points 112

En gros, vous devez :

-Changez le type de la clé en int dans la classe d'utilisateur Identity.
-Ajouter des classes d'identité personnalisées qui utilisent int comme clé.
-Changer la classe de contexte et le gestionnaire d'utilisateur pour utiliser int comme clé.
-Modifier la configuration de démarrage pour utiliser int comme clé
-Changez le AccountController pour passer int comme clé

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X