Identità ASP.NET con EF Database First MVC5


88

È possibile utilizzare la nuova identità Asp.net con Database First e EDMX? O solo con il codice prima?

Ecco cosa ho fatto:

1) Ho creato un nuovo progetto MVC5 e ho fatto creare alla nuova identità le nuove tabelle utente e ruoli nel mio database.

2) Ho quindi aperto il mio file Database First EDMX e trascinato nella nuova tabella Identity Users poiché ho altre tabelle ad esso correlate.

3) Dopo aver salvato EDMX, il generatore Database First POCO creerà automaticamente una classe utente. Tuttavia, UserManager e RoleManager prevedono che una classe User erediti dal nuovo spazio dei nomi Identity (Microsoft.AspNet.Identity.IUser), quindi l'utilizzo della classe User POCO non funzionerà.

Immagino che una possibile soluzione sia modificare le mie classi di generazione POCO in modo che la mia classe utente erediti da IUser?

Oppure ASP.NET Identity è compatibile solo con Code First Design?

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

Aggiornamento: seguendo il suggerimento di Anders Abel di seguito, questo è quello che ho fatto. Funziona, ma mi chiedo se esista una soluzione più elegante.

1) Ho esteso la mia classe Utente entità creando una classe parziale all'interno dello stesso spazio dei nomi delle mie entità generate automaticamente.

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

2) Ho cambiato il mio DataContext per ereditare da IdentityDBContext invece di DBContext. Tieni presente che ogni volta che aggiorni il tuo EDMX e rigeneri le classi DBContext ed Entity, dovrai reimpostarlo su questo.

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

3) All'interno della classe di entità utente generata automaticamente, è necessario aggiungere la parola chiave override ai seguenti 4 campi o commentare questi campi poiché sono ereditati da IdentityUser (Passaggio 1). Nota che ogni volta che aggiorni il tuo EDMX e rigeneri le classi DBContext ed Entity, dovrai reimpostarlo su questo.

    override public string Id { get; set; }
    override public string UserName { get; set; }
    override public string PasswordHash { get; set; }
    override public string SecurityStamp { get; set; }

1
hai un codice di esempio della tua implementazione? come quando provo a replicare quanto sopra, ottengo un errore quando provo ad accedere o registrare un utente. "Il tipo di entità AspNetUser non fa parte del modello per il contesto corrente" dove AspNetUser è la mia entità Utente
Tim

Hai aggiunto la tabella AspNetUser al tuo EDMX? Inoltre, assicurati che il tuo AccountController utilizzi MVC5Test_DBEntities (o qualunque sia il nome del tuo contesto DB) anziché ApplicationContext.
Patrick Tran

8
ASP.NET Identity è un mucchio fumante di ____. Supporto orribile per database-first, nessuna documentazione, vincoli referenziali scadenti (mancante SU CASCADE DELETE su SQL server) e utilizza stringhe per ID (problema di prestazioni e frammentazione dell'indice). E questo è il loro 297 ° tentativo di struttura dell'identità ...
DeepSpace101

1
@ DeepSpace101 Identity supporta DB-first come il Code-first. I modelli sono impostati per fare prima il codice, quindi se inizi da un modello devi cambiare alcune cose. L'eliminazione a cascata funziona bene, puoi cambiare facilmente le stringhe in int. Vedi la mia risposta di seguito.
Scarpa

1
@Shoe devo dire che penso che potresti sbagliarti. Devo ancora trovare un esempio / tutorial funzionante e completo su come implementarlo in db-first (nessuna documentazione). L'API tenta di fare riferimento alla tabella di giunzione "IdentityUserRoles" tramite la proprietà IdentityUser.Roles, che interrompe la relazione su EF db-first poiché le tabelle di giunzione non sono esposte come entità (scarsi vincoli referenziali). Non sono d'accordo sulle stringhe per gli ID poiché possono essere personalizzate specificando i parametri di tipo nelle classi ereditate. Per riassumere, mi sembra che non avessero affatto in mente DB.
Sbilenco

Risposte:


16

Dovrebbe essere possibile utilizzare il sistema di identità con POCO e Database First, ma dovrai apportare un paio di modifiche:

  1. Aggiorna il file .tt per la generazione di POCO per creare le classi di entità partial. Ciò consentirà di fornire un'implementazione aggiuntiva in un file separato.
  2. Effettua un'implementazione parziale della Userclasse in un altro file

 

partial User : IUser
{
}

Ciò consentirà alla Userclasse di implementare l'interfaccia corretta, senza toccare i file effettivamente generati (modificare i file generati è sempre una cattiva idea).


Grazie. Dovrò fare una prova e segnalare se funziona.
Patrick Tran

Ho provato quello che hai detto. Vedi il mio post per informazioni aggiornate ... ma la soluzione non era molto elegante: /
Patrick Tran

Ho avuto lo stesso problema. Sembra che la documentazione su DB-first sia molto scarsa. Questo è un bel suggerimento ma penso che tu abbia ragione, non funziona del tutto
Phil

4
Esiste ancora una soluzione che non sia un hack?
user20358

Sto usando Onion Architecture e tutti i POCO sono in Core. Lì non è consigliabile utilizzare IUser. Quindi un'altra soluzione?
Usman Khalid

13

I miei passaggi sono molto simili ma volevo condividere.

1) Crea un nuovo progetto MVC5

2) Crea un nuovo Model.edmx. Anche se è un nuovo database e non ha tabelle.

3) Modifica web.config e sostituisci questa stringa di connessione generata:

<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" />

con questa stringa di connessione:

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

Successivamente, crea ed esegui l'applicazione. Registra un utente e quindi le tabelle verranno create.


1
questo ha risolto un problema, non riuscivo a vedere l'utente dell'applicazione nel database, ma dopo aver sostituito la connessione predefinita, ha funzionato.
Hassen Ch.

10

EDIT: ASP.NET Identity with EF Database First for MVC5 CodePlex Project Template.


Volevo utilizzare un database esistente e creare relazioni con ApplicationUser. È così che l'ho fatto usando SQL Server, ma la stessa idea probabilmente funzionerebbe con qualsiasi DB.

  1. Crea un progetto MVC
  2. Aprire il DB elencato sotto DefaultConnection in Web.config. Si chiamerà (aspnet- [timestamp] o qualcosa del genere).
  3. Script le tabelle del database.
  4. Inserire le tabelle con script nel database esistente in SQL Server Management Studio.
  5. Personalizza e aggiungi relazioni ad ApplicationUser (se necessario).
  6. Crea nuovo progetto Web> MVC> Primo progetto DB> Importa DB con EF ... Escluse le classi di identità inserite.
  7. In IdentityModels.cs, modificare ApplicationDbContext :base("DefaltConnection")per utilizzare DbContext del progetto.

Modifica: diagramma della classe di identità Asp.Net inserisci qui la descrizione dell'immagine


6
Il problema non è DBContext, ma che UserManager e RoleManager si aspettano una classe che eredita da Microsoft.AspNet.Identity.EntityFramework.IdentityUser
Patrick Tran

public class IdentityDbContext <TUser>: DbContext dove TUser: Microsoft.AspNet.Identity.EntityFramework.IdentityUser. Quando si utilizza prima il database, le classi di entità generate non ereditano da nessuna classe di base.
Patrick Tran

Quindi escludili dal database quando generi le classi con il framework di entità.
puzza il

Se escludi le tabelle Identity da EDMX, perderai le proprietà di navigazione in altre classi che hanno chiavi esterne per il tuo ID utente
Patrick Tran

35
Non sono riluttante a passare prima al codice ... È solo che in alcuni scenari e aziende, l'amministratore db crea le tabelle di base, non il programmatore.
Patrick Tran

8

IdentityUserè inutile qui perché è l'oggetto code-first utilizzato da UserStoreper l'autenticazione. Dopo aver definito il mio Useroggetto, ho implementato una classe parziale che implementa ciò IUserche viene utilizzato dalla UserManagerclasse. Volevo che il mio Ids fosse intinvece di una stringa, quindi restituisco l'ID utente aString (). Allo stesso modo ho voluto nin Usernameessere in minuscolo.

public partial class User : IUser
{

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

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

Non ne hai assolutamente bisogno IUser. È solo un'interfaccia utilizzata da UserManager. Quindi, se vuoi definire un "IUser" diverso, dovrai riscrivere questa classe per utilizzare la tua implementazione.

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

Ora si scrive il proprio UserStore, che gestisce tutto lo stoccaggio di utenti, reclami, ruoli, ecc implementare le interfacce di tutto ciò che il codice-prima UserStorefa e il cambiamento where TUser : IdentityUsera where TUser : Usercui "Utente" è il vostro oggetto 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
}

Ecco un paio di esempi su alcune delle implementazioni dell'interfaccia

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

Utilizzando il modello MVC 5, ho cambiato il AccountControllerin questo modo.

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

Ora l'accesso dovrebbe funzionare con le tue tabelle.


1
Recentemente ho avuto la possibilità di implementarlo e per qualche motivo (credo a causa dell'aggiornamento 3.0 dell'identità) non sono stato in grado di implementare il login ereditando IdentityUser e quindi sovrascrivendo le proprietà; ma scrivere un UserStore personalizzato ed ereditare IUser ha funzionato bene; Solo dando un aggiornamento, forse qualcuno lo trova utile.
Naz Ekin

Potete fornire il collegamento per tutta l'implementazione dell'interfaccia o per l'intero progetto?
DespeiL

c'è un motivo specifico per cui hai usato una classe parziale qui?
user8964654

Se stai usando un edmx per generare i tuoi modelli, devi usare una classe parziale. Se stai scrivendo prima il codice, puoi ometterlo
Scarpa


3

Buona domanda.

Sono più una persona che fa il database. Il primo paradigma del codice mi sembra un po 'strano e le "migrazioni" sembrano troppo soggette a errori.

Volevo personalizzare lo schema dell'identità aspnet e non essere disturbato dalle migrazioni. Sono esperto con i progetti di database di Visual Studio (sqlpackage, data-dude) e come fa un buon lavoro nell'aggiornamento degli schemi.

La mia soluzione semplicistica è:

1) Creare un progetto di database che rispecchi lo schema di identità aspnet 2) utilizzare l'output di questo progetto (.dacpac) come risorsa del progetto 3) distribuire il .dacpac quando necessario

Per MVC5, la modifica della ApplicationDbContextclasse sembra ottenere questo risultato ...

1) Implementare IDatabaseInitializer

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

2) Nel costruttore, segnalare che questa classe implementerà l'inizializzazione del database:

Database.SetInitializer<ApplicationDbContext>(this);

3) Implementare InitializeDatabase:

Qui, ho scelto di utilizzare DacFX e distribuire il mio .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);
            }
        }
    }

2

Ho passato diverse ore a lavorare su questo e finalmente ho trovato una soluzione che ho condiviso sul mio blog qui . Fondamentalmente, devi fare tutto ciò che viene detto nella risposta di stink , ma con una cosa in più: assicurarti che Identity Framework abbia una specifica stringa di connessione SQL-Client sopra la stringa di connessione Entity Framework utilizzata per le entità dell'applicazione.

In sintesi, l'applicazione utilizzerà una stringa di connessione per Identity Framework e un'altra per le entità dell'applicazione. Ogni stringa di connessione è di un tipo diverso. Leggi il mio post sul blog per un tutorial completo.


Ho provato due volte tutto ciò che hai menzionato nel tuo blog, non ha funzionato per me.
Badhon Jain

@Badhon Mi dispiace molto che le istruzioni nel mio post sul blog non abbiano funzionato per te. Ho avuto innumerevoli persone che hanno espresso i loro ringraziamenti per aver trovato successo dopo il mio articolo. Ricorda sempre che se Microsoft aggiorna qualcosa può influire sul risultato. Ho scritto l'articolo per ASP.NET MVC 5 con Identity Framework 2.0. Qualunque cosa oltre a ciò potrebbe avere problemi, ma finora ho ricevuto commenti molto recenti che indicano il successo. Mi piacerebbe saperne di più sul tuo problema.
Daniel Eagle

Proverò a seguire ancora una volta, il problema che ho dovuto affrontare è che non potevo usare il mio database personalizzato, utilizzava il database costruito automaticamente. Comunque, non volevo dire qualcosa di sbagliato con il tuo articolo. Grazie per avere condiviso le tue conoscenze.
Badhon Jain

1
@Badhon Nessun problema amico mio, non ho mai pensato che stavi dicendo che l'articolo non andava bene. Abbiamo tutti situazioni uniche con vari casi limite, quindi a volte ciò che funziona per gli altri potrebbe non funzionare necessariamente per noi. Spero tu sia riuscito a risolvere il tuo problema.
Daniel Eagle

2

Ho scoperto che @ JoshYates1980 ha la risposta più semplice.

Dopo una serie di tentativi ed errori, ho fatto ciò che Josh ha suggerito e sostituito connectionStringcon la mia stringa di connessione DB generata. quello di cui ero confuso originariamente era il seguente post:

Come aggiungere l'autenticazione dell'identità ASP.NET MVC5 al database esistente

Dove la risposta accettata da @Win ha dichiarato di cambiare il ApplicationDbContext()nome della connessione. Questo è un po 'vago se si utilizza Entity e un approccio Database / Model in cui la stringa di connessione al database viene generata e aggiunta al Web.configfile.

Il ApplicationDbContext()nome della connessione viene mappato alla connessione predefinita nel Web.configfile. Pertanto, il metodo di Josh funziona meglio, ma per renderlo ApplicationDbContext()più leggibile suggerirei di cambiare il nome con il nome del database come @Win originariamente pubblicato, assicurandoti di cambiare il connectionStringper "DefaultConnection" in Web.confige commentare e / o rimuovere l'Entità database generato include.

Esempi di codice:


1

Abbiamo un progetto DLL modello entità in cui manteniamo la nostra classe modello. Manteniamo anche un progetto di database con tutti gli script di database. Il mio approccio è stato il seguente

1) Crea il tuo progetto che ha l'EDMX utilizzando prima il database

2) Script le tabelle nel tuo db, ho usato VS2013 connesso a localDB (connessioni dati) e copiato lo script nel progetto di database, aggiungi eventuali colonne personalizzate, ad es. BirthDate [DATE] non null

3) Distribuire il database

4) Aggiorna il progetto Modello (EDMX) Aggiungi al progetto Modello

5) Aggiungere eventuali colonne personalizzate alla classe dell'applicazione

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

Nel progetto MVC AccountController ha aggiunto quanto segue:

Il provider di identità desidera che una stringa di connessione SQL funzioni, per mantenere solo 1 stringa di connessione per il database, estrarre la stringa del provider dalla stringa di connessione 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)));
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.