Identité ASP.NET avec EF Database First MVC5

88

Est-il possible d'utiliser la nouvelle identité Asp.net avec Database First et EDMX? Ou seulement avec le code d'abord?

Voici ce que j'ai fait:

1) J'ai créé un nouveau projet MVC5 et j'ai demandé à la nouvelle identité de créer les nouvelles tables User et Roles dans ma base de données.

2) J'ai ensuite ouvert mon fichier Database First EDMX et l'ai glissé dans la nouvelle table Identity Users puisque j'ai d'autres tables qui s'y rapportent.

3) Lors de la sauvegarde de l'EDMX, le générateur Database First POCO créera automatiquement une classe User. Cependant, UserManager et RoleManager s'attendent à ce qu'une classe User hérite du nouvel espace de noms Identity (Microsoft.AspNet.Identity.IUser), donc l'utilisation de la classe POCO User ne fonctionnera pas.

Je suppose qu'une solution possible est de modifier mes classes de génération POCO pour que ma classe utilisateur hérite d'IUser?

Ou est ASP.NET Identity uniquement compatible avec Code First Design?

++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++

Mise à jour: Suite à la suggestion d'Anders Abel ci-dessous, c'est ce que j'ai fait. Cela fonctionne, mais je me demande s'il existe une solution plus élégante.

1) J'ai étendu ma classe User d'entité en créant une classe partielle dans le même espace de noms que mes entités générées automatiquement.

namespace MVC5.DBFirst.Entity
{
    public partial class AspNetUser : IdentityUser
    {
    }
}

2) J'ai changé mon DataContext pour hériter d'IdentityDBContext au lieu de DBContext. Notez que chaque fois que vous mettez à jour votre EDMX et régénérez les classes DBContext et Entity, vous devrez le redéfinir.

 public partial class MVC5Test_DBEntities : IdentityDbContext<AspNetUser>  //DbContext

3) Dans votre classe d'entité User générée automatiquement, vous devez ajouter le mot-clé override aux 4 champs suivants ou commenter ces champs car ils sont hérités d'IdentityUser (étape 1). Notez que chaque fois que vous mettez à jour votre EDMX et régénérez les classes DBContext et Entity, vous devrez le redéfinir.

    override public string Id { get; set; }
    override public string UserName { get; set; }
    override public string PasswordHash { get; set; }
    override public string SecurityStamp { get; set; }
Patrick Tran
la source
1
avez-vous un exemple de code de votre implémentation? comme lorsque j'essaie de reproduire ce qui précède, j'obtiens une erreur lorsque j'essaie de me connecter ou d'enregistrer un utilisateur. "Le type d'entité AspNetUser ne fait pas partie du modèle pour le contexte actuel" où AspNetUser est mon entité utilisateur
Tim
Avez-vous ajouté la table AspNetUser à votre EDMX? Assurez-vous également que votre AccountController utilise MVC5Test_DBEntities (ou quel que soit le nom de votre contexte de base de données) plutôt que ApplicationContext.
Patrick Tran
8
L'identité ASP.NET est une pile fumante de ____. Support horrible pour la base de données d'abord, pas de documentation, mauvaises contraintes référentielles (manquant ON CASCADE DELETE sur le serveur SQL) et utilise des chaînes pour les ID (problème de performances et fragmentation d'index). Et c'est leur 297e tentative de cadre d'identité ...
DeepSpace101
1
@ DeepSpace101 Identity prend en charge DB-first de la même manière que Code-first. Les modèles sont configurés pour faire du code d'abord, donc si vous partez d'un modèle, vous devez changer certaines choses. La suppression en cascade fonctionne très bien, vous pouvez facilement changer les chaînes en entiers. Voir ma réponse ci-dessous.
Chaussure du
1
@Shoe Je dois dire que je pense que vous vous trompez peut-être. Je n'ai pas encore trouvé d'exemple / tutoriel complet et fonctionnel sur la façon de l'implémenter dans db-first (pas de documentation). L'API tente de référencer la table de jonction "IdentityUserRoles" via la propriété IdentityUser.Roles, qui rompt la relation sur EF db-first car les tables de jonction ne sont pas exposées en tant qu'entités (contraintes référentielles médiocres). Je ne suis pas d'accord sur les chaînes pour les ID car cela peut être personnalisé en spécifiant les paramètres de type dans les classes héritées. Pour résumer, il me semble qu'ils n'avaient pas du tout DB en tête.
Lopsided

Réponses:

16

Il devrait être possible d'utiliser le système d'identité avec POCO et Database First, mais vous devrez faire quelques ajustements:

  1. Mettez à jour le fichier .tt pour la génération POCO pour créer les classes d'entité partial. Cela vous permettra de fournir une implémentation supplémentaire dans un fichier séparé.
  2. Faire une implémentation partielle de la Userclasse dans un autre fichier

 

partial User : IUser
{
}

Cela permettra à la Userclasse d'implémenter la bonne interface, sans toucher aux fichiers générés (éditer les fichiers générés est toujours une mauvaise idée).

Anders Abel
la source
Merci. Je vais devoir essayer et faire un rapport si cela fonctionne.
Patrick Tran
J'ai essayé ce que vous avez mentionné. Voir mon article pour des informations mises à jour ... mais la solution n'était pas très élégante: /
Patrick Tran
J'ai eu le même problème. Il semble que la documentation sur DB-first soit très rare. C'est une bonne suggestion mais je pense que vous avez raison, cela ne fonctionne pas tout à fait
Phil
4
Existe-t-il encore une solution qui ne soit pas un hack?
user20358
J'utilise l'architecture Onion et tous les POCO sont dans Core. Là, il n'est pas recommandé d'utiliser IUser. Alors une autre solution?
Usman Khalid
13

Mes démarches sont très similaires mais je voulais partager.

1) Créer un nouveau projet MVC5

2) Créez un nouveau Model.edmx. Même s'il s'agit d'une nouvelle base de données et n'a pas de tables.

3) Modifiez web.config et remplacez cette chaîne de connexion générée:

<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-SSFInventory-20140521115734.mdf;Initial Catalog=aspnet-SSFInventory-20140521115734;Integrated Security=True" providerName="System.Data.SqlClient" />

avec cette chaîne de connexion:

<add name="DefaultConnection" connectionString="Data Source=.\SQLExpress;database=SSFInventory;integrated security=true;" providerName="System.Data.SqlClient" />

Ensuite, créez et exécutez l'application. Enregistrez un utilisateur et les tables seront créées.

JoshYates1980
la source
1
cela a résolu un problème, je ne pouvais pas voir l'utilisateur de l'application dans la base de données, mais après avoir remplacé la connexion par défaut, cela fonctionnait.
Hassen Ch.
10

EDIT: Identité ASP.NET avec base de données EF d'abord pour le modèle de projet MVC5 CodePlex.


Je voulais utiliser une base de données existante et créer des relations avec ApplicationUser. C'est ainsi que je l'ai fait en utilisant SQL Server, mais la même idée fonctionnerait probablement avec n'importe quelle base de données.

  1. Créer un projet MVC
  2. Ouvrez la base de données répertoriée sous DefaultConnection dans Web.config. Il s'appellera (aspnet- [timestamp] ou quelque chose comme ça.)
  3. Script les tables de la base de données.
  4. Insérez les tables scriptées dans la base de données existante dans SQL Server Management Studio.
  5. Personnalisez et ajoutez des relations à ApplicationUser (si nécessaire).
  6. Créez un nouveau projet Web> MVC> DB First Project> Importer une base de données avec EF ... à l'exclusion des classes d'identité que vous avez insérées.
  7. Dans IdentityModels.cs, modifiez le ApplicationDbContext :base("DefaltConnection")pour utiliser le DbContext de votre projet.

Edit: Diagramme de classes d'identité Asp.Net entrez la description de l'image ici

puanteur
la source
6
Le problème n'est pas DBContext, mais que UserManager et RoleManager attendent une classe qui hérite de Microsoft.AspNet.Identity.EntityFramework.IdentityUser
Patrick Tran
classe publique IdentityDbContext <TUser>: DbContext où TUser: Microsoft.AspNet.Identity.EntityFramework.IdentityUser. Lors de la première utilisation de la base de données, les classes d'entités générées n'héritent d'aucune classe de base.
Patrick Tran
Ensuite, excluez-les simplement de la base de données lorsque vous générez les classes avec le framework d'entité.
puant
Si vous excluez les tables d'identité de l'EDMX, vous perdrez les propriétés de navigation dans d'autres classes qui ont des clés étrangères à votre ID utilisateur
Patrick Tran
34
Je ne suis pas réticent à passer d'abord au code ... C'est juste que dans certains scénarios et entreprises, l'administrateur de la base de données crée les tables de base, pas le codeur.
Patrick Tran
8

IdentityUserne vaut rien ici car c'est l'objet code-first utilisé par le UserStorepour l'authentification. Après avoir défini mon propre Userobjet, j'ai implémenté une classe partielle qui implémente IUserqui est utilisée par la UserManagerclasse. Je voulais que mon Ids soit intau lieu de string, donc je renvoie simplement le toString () de l'ID utilisateur. De même , je voulais nen Usernameêtre sans majuscule.

public partial class User : IUser
{

    public string Id
    {
        get { return this.UserID.ToString(); }
    }

    public string UserName
    {
        get
        {
            return this.Username;
        }
        set
        {
            this.Username = value;
        }
    }
}

Vous n'en avez pas besoin IUser. Ce n'est qu'une interface utilisée par le UserManager. Donc, si vous voulez définir un "IUser" différent, vous devrez réécrire cette classe pour utiliser votre propre implémentation.

public class UserManager<TUser> : IDisposable where TUser: IUser

Vous écrivez maintenant votre propre UserStorequi gère tous le stockage des utilisateurs, des réclamations, des rôles, etc. Mettre en œuvre les interfaces de tout ce que le code premier UserStorefait et le changement where TUser : IdentityUserd' where TUser : Useroù « l' utilisateur » est votre objet entité

public class MyUserStore<TUser> : IUserLoginStore<TUser>, IUserClaimStore<TUser>, IUserRoleStore<TUser>, IUserPasswordStore<TUser>, IUserSecurityStampStore<TUser>, IUserStore<TUser>, IDisposable where TUser : User
{
    private readonly MyAppEntities _context;
    public MyUserStore(MyAppEntities dbContext)
    { 
        _context = dbContext; 
    }

    //Interface definitions
}

Voici quelques exemples sur certaines des implémentations d'interface

async Task IUserStore<TUser>.CreateAsync(TUser user)
{
    user.CreatedDate = DateTime.Now;
    _context.Users.Add(user);
    await _context.SaveChangesAsync();
}

async Task IUserStore<TUser>.DeleteAsync(TUser user)
{
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
}

En utilisant le modèle MVC 5, j'ai changé le AccountControllerpour ressembler à ceci.

public AccountController()
        : this(new UserManager<User>(new MyUserStore<User>(new MyAppEntities())))
{
}

La connexion à présent devrait fonctionner avec vos propres tables.

Chaussure
la source
1
J'ai récemment eu l'occasion de l'implémenter, et pour une raison quelconque (je crois à cause de la mise à jour 3.0 de l'identité), je n'ai pas pu implémenter la connexion en héritant d'IdentityUser, puis en remplaçant les propriétés; mais l'écriture d'un UserStore personnalisé et l'héritage d'IUser fonctionnaient bien; Juste en donnant une mise à jour, peut-être que quelqu'un trouve cela utile.
Naz Ekin
Pouvez-vous donner un lien pour toutes les implémentations d'interface ou pour un projet complet?
DespeiL
y a-t-il une raison spécifique pour laquelle vous avez utilisé une classe partielle ici?
user8964654
Si vous utilisez un edmx pour générer vos modèles, vous devez utiliser une classe partielle. Si vous faites d'abord du code, vous pouvez l'omettre
Chaussure
3

Bonne question.

Je suis plutôt une personne à la base de données. Le paradigme du code premier me semble lâche, et les «migrations» semblent trop sujettes aux erreurs.

Je voulais personnaliser le schéma d'identité aspnet et ne pas me soucier des migrations. Je connais bien les projets de base de données Visual Studio (sqlpackage, data-mec) et comment il fait un très bon travail de mise à niveau des schémas.

Ma solution simpliste est de:

1) Créer un projet de base de données qui reflète le schéma d'identité aspnet 2) utiliser la sortie de ce projet (.dacpac) comme ressource de projet 3) déployer le .dacpac si nécessaire

Pour MVC5, la modification de la ApplicationDbContextclasse semble faire avancer les choses ...

1) Mettre en œuvre IDatabaseInitializer

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDatabaseInitializer<ApplicationDbContext> { ... }

2) Dans le constructeur, signalez que cette classe implémentera l'initialisation de la base de données:

Database.SetInitializer<ApplicationDbContext>(this);

3) Mettre en œuvre InitializeDatabase:

Ici, j'ai choisi d'utiliser DacFX et de déployer mon .dacpac

    void IDatabaseInitializer<ApplicationDbContext>.InitializeDatabase(ApplicationDbContext context)
    {
        using (var ms = new MemoryStream(Resources.Binaries.MainSchema))
        {
            using (var package = DacPackage.Load(ms, DacSchemaModelStorageType.Memory))
            {
                DacServices services = new DacServices(Database.Connection.ConnectionString);
                var options = new DacDeployOptions
                {
                    VerifyDeployment = true,
                    BackupDatabaseBeforeChanges = true,
                    BlockOnPossibleDataLoss = false,
                    CreateNewDatabase = false,
                    DropIndexesNotInSource = true,
                    IgnoreComments = true,

                };
                services.Deploy(package, Database.Connection.Database, true, options);
            }
        }
    }
Aaron Hudon
la source
2

J'ai passé plusieurs heures à y travailler et j'ai finalement trouvé une solution que j'ai partagée sur mon blog ici . Fondamentalement, vous devez faire tout ce qui est dit dans la réponse de stink , mais avec une chose supplémentaire: vous assurer qu'Identity Framework a une chaîne de connexion SQL-Client spécifique au-dessus de la chaîne de connexion Entity Framework utilisée pour vos entités d'application.

En résumé, votre application utilisera une chaîne de connexion pour Identity Framework et une autre pour vos entités d'application. Chaque chaîne de connexion est d'un type différent. Lisez mon article de blog pour un tutoriel complet.

Daniel Eagle
la source
J'ai essayé tout ce que vous avez mentionné dans votre blog deux fois, cela n'a pas fonctionné pour moi.
Badhon Jain
@Badhon Je suis vraiment désolé que les instructions de mon article de blog n'aient pas fonctionné pour vous. J'ai eu d'innombrables personnes qui ont exprimé leurs remerciements car ils ont réussi à suivre mon article. N'oubliez jamais que si Microsoft met à jour quelque chose, cela peut affecter le résultat. J'ai écrit l'article pour ASP.NET MVC 5 avec Identity Framework 2.0. Tout ce qui va au-delà peut rencontrer des problèmes, mais jusqu'à présent, j'ai reçu des commentaires très récents indiquant le succès. J'aimerais en savoir plus sur votre problème.
Daniel Eagle
Je vais essayer de suivre une fois de plus, le problème auquel j'ai été confronté est que je ne pouvais pas utiliser ma base de données personnalisée, elle utilisait la base de données construite automatiquement. Quoi qu'il en soit, je ne voulais pas dire quelque chose de mal avec votre article. Merci de partager votre savoir.
Badhon Jain
1
@Badhon Pas de soucis mon ami, je n'ai jamais senti que tu disais que quelque chose n'allait pas avec l'article. Nous avons tous des situations uniques avec divers cas extrêmes, donc parfois, ce qui fonctionne pour les autres ne fonctionne pas nécessairement pour nous. J'espère que vous avez pu résoudre votre problème.
Daniel Eagle
1

Nous avons un projet DLL de modèle d'entité dans lequel nous conservons notre classe de modèle. Nous conservons également un projet de base de données avec tous les scripts de base de données. Mon approche était la suivante

1) Créez votre propre projet qui a l'EDMX en utilisant d'abord la base de données

2) Script les tables de votre base de données, j'ai utilisé VS2013 connecté à localDB (Data Connections) et copié le script dans le projet de base de données, ajoutez des colonnes personnalisées, par exemple BirthDate [DATE] non nul

3) Déployez la base de données

4) Mettre à jour le projet de modèle (EDMX) Ajouter au projet de modèle

5) Ajoutez des colonnes personnalisées à la classe d'application

public class ApplicationUser : IdentityUser
{
    public DateTime BirthDate { get; set; }
}

Dans le projet MVC AccountController a ajouté ce qui suit:

Le fournisseur d'identité veut une chaîne de connexion SQL pour que cela fonctionne, pour ne conserver qu'une seule chaîne de connexion pour la base de données, extraire la chaîne de fournisseur de la chaîne de connexion EF

public AccountController()
{
   var connection = ConfigurationManager.ConnectionStrings["Entities"];
   var entityConnectionString = new EntityConnectionStringBuilder(connection.ConnectionString);
        UserManager =
            new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(
                    new ApplicationDbContext(entityConnectionString.ProviderConnectionString)));
}
Haroon
la source
1

J'ai trouvé que @ JoshYates1980 avait la réponse la plus simple.

Après une série d'essais et d'erreurs, j'ai fait ce que Josh a suggéré et connectionStringj'ai remplacé le par ma chaîne de connexion DB générée. ce que j'étais confus à l'origine était le message suivant:

Comment ajouter l'authentification d'identité ASP.NET MVC5 à la base de données existante

Où la réponse acceptée de @Win indiquait de changer le ApplicationDbContext()nom de la connexion. Ceci est un peu vague si vous utilisez Entity et une première approche de base de données / modèle où la chaîne de connexion à la base de données est générée et ajoutée au Web.configfichier.

Le ApplicationDbContext()nom de la connexion est mappé à la connexion par défaut dans le Web.configfichier. Par conséquent, la méthode de Josh fonctionne mieux, mais pour rendre le ApplicationDbContext()plus lisible, je suggérerais de changer le nom de votre base de données comme @Win initialement publié, en veillant à changer le connectionStringpour le "DefaultConnection" dans le Web.configet commenter et / ou supprimer l'entité la base de données générée comprend.

Exemples de code:

TheSchnitz
la source