Identidad ASP.NET con EF Database First MVC5

88

¿Es posible utilizar la nueva identidad de Asp.net con Database First y EDMX? ¿O solo con el código primero?

Esto es lo que hice:

1) Hice un nuevo proyecto MVC5 e hice que la nueva identidad creara las nuevas tablas de usuarios y roles en mi base de datos.

2) Luego abrí mi archivo Database First EDMX y lo arrastré en la nueva tabla Identity Users ya que tengo otras tablas que se relacionan con ella.

3) Al guardar el EDMX, el generador de POCO de Database First creará automáticamente una clase de usuario. Sin embargo, UserManager y RoleManager esperan una clase de usuario heredada del nuevo espacio de nombres de identidad (Microsoft.AspNet.Identity.IUser), por lo que el uso de la clase de usuario de POCO no funcionará.

Supongo que una posible solución es editar mis clases de generación de POCO para que mi clase de usuario herede de IUser.

¿O ASP.NET Identity solo es compatible con Code First Design?

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

Actualización: Siguiendo la sugerencia de Anders Abel a continuación, esto es lo que hice. Funciona, pero me pregunto si hay una solución más elegante.

1) Extendí la clase de usuario de mi entidad creando una clase parcial dentro del mismo espacio de nombres que mis entidades generadas automáticamente.

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

2) Cambié mi DataContext para heredar de IdentityDBContext en lugar de DBContext. Tenga en cuenta que cada vez que actualice su EDMX y vuelva a generar las clases DBContext y Entity, tendrá que volver a configurar esto.

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

3) Dentro de su clase de entidad de usuario generada automáticamente, debe agregar la palabra clave de anulación a los siguientes 4 campos o comentar estos campos ya que se heredan de IdentityUser (Paso 1). Tenga en cuenta que cada vez que actualice su EDMX y vuelva a generar las clases DBContext y Entity, tendrá que volver a configurar esto.

    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
fuente
1
¿Tiene un código de muestra de su implementación? como cuando trato de replicar lo anterior, aparece un error cuando intento iniciar sesión o registrar un usuario. "El tipo de entidad AspNetUser no es parte del modelo para el contexto actual" donde AspNetUser es mi entidad de usuario
Tim
¿Agregó la tabla AspNetUser a su EDMX? Además, asegúrese de que su AccountController esté usando MVC5Test_DBEntities (o cualquiera que sea su nombre de contexto de base de datos) en lugar de ApplicationContext.
Patrick Tran
8
ASP.NET Identity es un montón de ____. Soporte horrible para la base de datos primero, sin documentación, restricciones de referencia deficientes (falta ON CASCADE DELETE en el servidor SQL) y usa cadenas para ID (problema de rendimiento y fragmentación de índices). Y este es su intento número 297 en un marco de identidad ...
DeepSpace101
1
@ DeepSpace101 Identity admite DB-first al igual que Code-first. Las plantillas están configuradas para hacer código primero, por lo que si comienza desde una plantilla, debe cambiar algunas cosas. La eliminación en cascada funciona bien, puede cambiar las cadenas a ints fácilmente. Vea mi respuesta a continuación.
Zapato
1
@Zapato Tengo que decir que creo que puede estar equivocado. Todavía tengo que encontrar un ejemplo / tutorial completo y funcional sobre cómo implementar esto en db-first (sin documentación). La API intenta hacer referencia a la tabla de unión "IdentityUserRoles" a través de la propiedad IdentityUser.Roles, que rompe la relación en EF db-first ya que las tablas de unión no se exponen como entidades (restricciones de referencia deficientes). No estoy de acuerdo con las cadenas de ID, ya que se pueden personalizar especificando los parámetros de tipo en las clases heredadas. Para resumir, me parece que no tenían DB en mente primero.
Desequilibrado

Respuestas:

16

Debería ser posible usar el sistema de identidad con POCO y Database First, pero tendrá que hacer un par de ajustes:

  1. Actualice el archivo .tt para la generación de POCO para crear las clases de entidad partial. Eso le permitirá proporcionar una implementación adicional en un archivo separado.
  2. Realice una implementación parcial de la Userclase en otro archivo

 

partial User : IUser
{
}

Eso hará que la Userclase implemente la interfaz correcta, sin tocar los archivos generados reales (editar archivos generados siempre es una mala idea).

Anders Abel
fuente
Gracias. Tendré que intentarlo e informar si funciona.
Patrick Tran
Probé lo que mencionaste. Vea mi publicación para obtener información actualizada ... pero la solución no fue muy elegante: /
Patrick Tran
He tenido el mismo problema. Parece que la documentación sobre DB-first es muy escasa. Esta es una buena sugerencia, pero creo que tienes razón, no funciona del todo
Phil
4
¿Existe alguna solución que no sea un truco?
user20358
Estoy usando Onion Architecture y todos los POCO están en Core. Allí no se recomienda utilizar IUser. Entonces, ¿alguna otra solución?
Usman Khalid
13

Mis pasos son muy similares pero quería compartirlos.

1) Crea un nuevo proyecto MVC5

2) Cree un nuevo Model.edmx. Incluso si es una base de datos nueva y no tiene tablas.

3) Edite web.config y reemplace esta cadena de conexión generada:

<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 esta cadena de conexión:

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

Luego, compile y ejecute la aplicación. Registre un usuario y luego se crearán las tablas.

JoshYates1980
fuente
1
esto resolvió un problema, no pude ver al usuario de la aplicación en la base de datos, pero después de reemplazar la conexión predeterminada, funcionó.
Hassen Ch.
10

EDITAR: ASP.NET Identity con EF Database First para MVC5 CodePlex Project Template.


Quería usar una base de datos existente y crear relaciones con ApplicationUser. Así es como lo hice usando SQL Server, pero la misma idea probablemente funcionaría con cualquier base de datos.

  1. Crear un proyecto MVC
  2. Abra la base de datos que aparece en DefaultConnection en Web.config. Se llamará (aspnet- [marca de tiempo] o algo así.)
  3. Script de las tablas de la base de datos.
  4. Inserte las tablas con secuencias de comandos en la base de datos existente en SQL Server Management Studio.
  5. Personalice y agregue relaciones a ApplicationUser (si es necesario).
  6. Cree un nuevo proyecto web> MVC> DB First Project> Import DB with EF ... Excluyendo las clases de identidad que insertó.
  7. En IdentityModels.cs, cambie ApplicationDbContext :base("DefaltConnection")para usar el DbContext de su proyecto.

Editar: Diagrama de clases de identidad Asp.Net ingrese la descripción de la imagen aquí

hedor
fuente
6
El problema no es DBContext, pero UserManager y RoleManager esperan una clase que herede de Microsoft.AspNet.Identity.EntityFramework.IdentityUser
Patrick Tran
public class IdentityDbContext <TUser>: DbContext donde TUser: Microsoft.AspNet.Identity.EntityFramework.IdentityUser. Cuando se usa la base de datos primero, las clases de entidad generadas no heredan de ninguna clase base.
Patrick Tran
Luego, simplemente extráigalos de la base de datos cuando genere las clases con el marco de la entidad.
apesta el
Si excluye las tablas de identidad de EDMX, perderá las propiedades de navegación en otras clases que tengan claves externas para su ID de usuario
Patrick Tran
34
No soy reacio a pasar al código primero ... Es solo que en algunos escenarios y empresas, el administrador de la base de datos crea las tablas base, no el codificador.
Patrick Tran
8

IdentityUserno tiene valor aquí porque es el primer objeto de código utilizado por el UserStorepara la autenticación. Después de definir mi propio Userobjeto, implementé una clase parcial que implementa la IUserque usa la UserManagerclase. Quería que mi Ids fuera en intlugar de una cadena, así que solo devuelvo el ID de usuario toString (). Del mismo modo que quería nen Usernameser en minúsculas.

public partial class User : IUser
{

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

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

De ninguna manera lo necesitas IUser. Es solo una interfaz utilizada por UserManager. Entonces, si desea definir un "IUser" diferente, tendrá que reescribir esta clase para usar su propia implementación.

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

Ahora escribe el tuyo propio UserStoreque maneja todo el almacenamiento de usuarios, reclamos, roles, etc. Implementa las interfaces de todo lo que hace el código primero UserStorey cambia where TUser : IdentityUsera where TUser : Userdonde "Usuario" es tu objeto de entidad.

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
}

Aquí hay un par de ejemplos sobre algunas de las implementaciones de la interfaz.

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

Usando la plantilla MVC 5, cambié el AccountControlleraspecto para que se vea así.

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

Ahora, el inicio de sesión debería funcionar con sus propias tablas.

Zapato
fuente
1
Recientemente tuve la oportunidad de implementar esto, y por alguna razón (creo que debido a la actualización 3.0 de la identidad) no pude implementar el inicio de sesión heredando IdentityUser y luego anulando las propiedades; pero escribir un UserStore personalizado y heredar IUser funcionó bien; Solo dando una actualización, tal vez alguien lo encuentre útil.
Naz Ekin
¿Puede dar un enlace para toda la implementación de la interfaz o para el proyecto completo?
DespeiL
¿Hay alguna razón específica por la que usaste una clase parcial aquí?
user8964654
Si está usando un edmx para generar sus modelos, debe usar una clase parcial. Si está haciendo código primero, puede omitir esto
Zapato
3

Buena pregunta.

Soy más una persona de base de datos. El primer paradigma del código me parece que se me escapa y las "migraciones" parecen demasiado propensas a errores.

Quería personalizar el esquema de identidad de aspnet y no molestarme con las migraciones. Estoy bien versado en proyectos de bases de datos de Visual Studio (sqlpackage, data-dude) y cómo hace un buen trabajo actualizando esquemas.

Mi solución simplista es:

1) Cree un proyecto de base de datos que refleje el esquema de identidad de aspnet 2) use el resultado de este proyecto (.dacpac) como un recurso del proyecto 3) implemente el .dacpac cuando sea necesario

Para MVC5, la modificación de la ApplicationDbContextclase parece hacer que esto funcione ...

1) Implementar IDatabaseInitializer

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

2) En el constructor, indique que esta clase implementará la inicialización de la base de datos:

Database.SetInitializer<ApplicationDbContext>(this);

3) Implementar InitializeDatabase:

Aquí, elegí usar DacFX e implementar mi .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
fuente
2

Pasé varias horas trabajando en esto y finalmente encontré una solución que he compartido en mi blog aquí . Básicamente, debe hacer todo lo que se dice en la respuesta de stink , pero con una cosa adicional: asegurarse de que Identity Framework tenga una cadena de conexión SQL-Client específica en la parte superior de la cadena de conexión de Entity Framework utilizada para las entidades de su aplicación.

En resumen, su aplicación utilizará una cadena de conexión para Identity Framework y otra para las entidades de su aplicación. Cada cadena de conexión es de un tipo diferente. Lea la publicación de mi blog para obtener un tutorial completo.

Daniel Eagle
fuente
Intenté todo lo que mencionaste en tu blog dos veces, no funcionó para mí.
Badhon Jain
@Badhon Lamento mucho que las instrucciones de la publicación de mi blog no te hayan funcionado. Innumerables personas han expresado su agradecimiento por haber tenido éxito siguiendo mi artículo. Recuerde siempre que si Microsoft actualiza algo, puede afectar el resultado. Escribí el artículo para ASP.NET MVC 5 con Identity Framework 2.0. Cualquier cosa más allá de eso puede experimentar problemas, pero hasta ahora he recibido comentarios muy recientes que indican éxito. Me encantaría saber más sobre tu problema.
Daniel Eagle
Intentaré seguir una vez más, el problema que enfrenté es que no pude usar mi base de datos personalizada, usó la base de datos construida automáticamente. De todos modos, no quise decir algo malo con tu artículo. Gracias por compartir su conocimiento.
Badhon Jain
1
@Badhon No te preocupes amigo mío, nunca sentí que dijeras que algo andaba mal con el artículo. Todos tenemos situaciones únicas con varios casos extremos, por lo que a veces lo que funciona para otros no necesariamente funciona para nosotros. Espero que hayas podido resolver tu problema.
Daniel Eagle
2

Descubrí que @ JoshYates1980 tiene la respuesta más simple.

Después de una serie de pruebas y errores, hice lo que Josh sugirió y reemplacé connectionStringcon mi cadena de conexión de base de datos generada. lo que me confundió originalmente fue la siguiente publicación:

Cómo agregar la autenticación de identidad ASP.NET MVC5 a la base de datos existente

Donde la respuesta aceptada de @Win indicó cambiar el ApplicationDbContext()nombre de la conexión. Esto es un poco vago si está utilizando Entity y un primer enfoque de base de datos / modelo donde la cadena de conexión de la base de datos se genera y se agrega al Web.configarchivo.

El ApplicationDbContext()nombre de la conexión se asigna a la conexión predeterminada en el Web.configarchivo. Por lo tanto, el método de Josh funciona mejor, pero para hacerlo ApplicationDbContext()más legible, sugeriría cambiar el nombre por el nombre de su base de datos como @Win publicó originalmente, asegurándose de cambiar el connectionStringde "DefaultConnection" en Web.configy comentar y / o eliminar la Entidad la base de datos generada incluye.

Ejemplos de código:

TheSchnitz
fuente
1

Tenemos un proyecto de DLL de modelo de entidad en el que mantenemos nuestra clase de modelo. También mantenemos un proyecto de base de datos con todos los scripts de la base de datos. Mi enfoque fue el siguiente

1) Cree su propio proyecto que tenga el EDMX usando la base de datos primero

2) Script de las tablas en su base de datos, utilicé VS2013 conectado a localDB (Conexiones de datos) y copié el script en el proyecto de la base de datos, agregue cualquier columna personalizada, por ejemplo, Fecha de nacimiento [FECHA] no nula

3) Implementar la base de datos

4) Actualizar el proyecto de modelo (EDMX) Agregar al proyecto de modelo

5) Agregue cualquier columna personalizada a la clase de aplicación

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

En el proyecto MVC, AccountController agregó lo siguiente:

El proveedor de identidad quiere una cadena de conexión SQL para que funcione, para mantener solo 1 cadena de conexión para la base de datos, extraiga la cadena del proveedor de la cadena de conexión 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
fuente