Validación de modelos de Identity en ASP.NET Core
Por Arthur Vickers
ASP.NET Core Identity proporciona un marco para administrar y almacenar cuentas de usuario en aplicaciones ASP.NET Core. Identity se agrega al proyecto cuando se seleccionan cuentas de usuario individuales como mecanismo de autenticación. De forma predeterminada, Identity usa un modelo de datos de Entity Framework (EF) Core. En este artículo se describe cómo personalizar el modelo de Identity.
Identity y Migraciones de EF Core
Antes de examinar el modelo, resulta útil comprender cómo funciona Identity con Migraciones de EF Core para crear y actualizar una base de datos. En el nivel superior, el proceso es:
- Defina o actualice un modelo de datos en el código.
- Agregue una migración para traducir este modelo a los cambios que se pueden aplicar a la base de datos.
- Compruebe que la migración representa correctamente sus intenciones.
- Aplique la migración para actualizar la base de datos para que esté sincronizada con el modelo.
- Repita los pasos del 1 al 4 para refinar aún más el modelo y mantener la base de datos sincronizada.
Use uno de los métodos siguientes para agregar y aplicar migraciones:
- La ventana Consola del administrador de paquetes (PMC) si usa Visual Studio. Para obtener más información, vea Herramientas PMC de EF Core.
- La CLI de .NET si usa la línea de comandos. Para más información, vea Herramientas de línea de comandos de EF Core .NET.
- Hacer clic en el botón Aplicar migraciones de la página de error cuando se ejecute la aplicación.
ASP.NET Core tiene un controlador de páginas de error en tiempo de desarrollo. El controlador puede aplicar migraciones cuando se ejecuta la aplicación. Normalmente, las aplicaciones de producción generan scripts SQL a partir de las migraciones e implementan cambios en la base de datos como parte de una implementación controlada de aplicaciones y bases de datos.
Cuando se crea una nueva aplicación mediante Identity, ya se han completado los pasos 1 y 2 anteriores. Es decir, el modelo de datos inicial ya existe y la migración inicial se agregó al proyecto. La migración inicial todavía debe aplicarse a la base de datos. La migración inicial se puede aplicar a través de uno de los métodos siguientes:
- Ejecute
Update-Database
en PMC. - Ejecute
dotnet ef database update
en un shell de comandos. - Haga clic en el botón Aplicar migraciones de la página de error cuando se ejecute la aplicación.
Repita los pasos anteriores a medida que se hacen cambios en el modelo.
El modelo de Identity
Tipos de entidades
El modelo de Identity consta de los siguientes tipos de entidad.
Tipo de entidad | Descripción |
---|---|
User |
Representa al usuario. |
Role |
Representa un rol. |
UserClaim |
Representa una notificación que el usuario posee. |
UserToken |
Representa un token de autenticación de un usuario. |
UserLogin |
Asocia un usuario a un inicio de sesión. |
RoleClaim |
Representa una notificación que se concede a todos los usuarios dentro de un rol. |
UserRole |
Una entidad de combinación que asocia usuarios y roles. |
Relaciones de tipo de entidad
Los tipos de entidad se relacionan entre sí de las maneras siguientes:
- Cada
User
tiene varios elementosUserClaims
. - Cada
User
tiene varios elementosUserLogins
. - Cada
User
tiene varios elementosUserTokens
. - Cada
Role
puede tener variasRoleClaims
asociadas. - Cada elemento
User
puede tener muchos elementosRoles
asociados, y cada elementoRole
puede estar asociado a varios elementosUsers
. Se trata de una relación de varios a varios que requiere una tabla de combinación en la base de datos. La tabla de combinación se representa mediante la entidadUserRole
.
Configuración predeterminada del modelo
Identity define muchas clases de contexto que heredan de DbContext para configurar y usar el modelo. Esta configuración se realiza mediante Code EF Core First Fluent API en el método OnModelCreating de la clase de contexto. La configuración predeterminada es la siguiente:
builder.Entity<TUser>(b =>
{
// Primary key
b.HasKey(u => u.Id);
// Indexes for "normalized" username and email, to allow efficient lookups
b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
// Maps to the AspNetUsers table
b.ToTable("AspNetUsers");
// A concurrency token for use with the optimistic concurrency checking
b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
// Limit the size of columns to use efficient database types
b.Property(u => u.UserName).HasMaxLength(256);
b.Property(u => u.NormalizedUserName).HasMaxLength(256);
b.Property(u => u.Email).HasMaxLength(256);
b.Property(u => u.NormalizedEmail).HasMaxLength(256);
// The relationships between User and other entity types
// Note that these relationships are configured with no navigation properties
// Each User can have many UserClaims
b.HasMany<TUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
// Each User can have many UserLogins
b.HasMany<TUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
// Each User can have many UserTokens
b.HasMany<TUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<TUserClaim>(b =>
{
// Primary key
b.HasKey(uc => uc.Id);
// Maps to the AspNetUserClaims table
b.ToTable("AspNetUserClaims");
});
builder.Entity<TUserLogin>(b =>
{
// Composite primary key consisting of the LoginProvider and the key to use
// with that provider
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
// Limit the size of the composite key columns due to common DB restrictions
b.Property(l => l.LoginProvider).HasMaxLength(128);
b.Property(l => l.ProviderKey).HasMaxLength(128);
// Maps to the AspNetUserLogins table
b.ToTable("AspNetUserLogins");
});
builder.Entity<TUserToken>(b =>
{
// Composite primary key consisting of the UserId, LoginProvider and Name
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
// Limit the size of the composite key columns due to common DB restrictions
b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
b.Property(t => t.Name).HasMaxLength(maxKeyLength);
// Maps to the AspNetUserTokens table
b.ToTable("AspNetUserTokens");
});
builder.Entity<TRole>(b =>
{
// Primary key
b.HasKey(r => r.Id);
// Index for "normalized" role name to allow efficient lookups
b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();
// Maps to the AspNetRoles table
b.ToTable("AspNetRoles");
// A concurrency token for use with the optimistic concurrency checking
b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
// Limit the size of columns to use efficient database types
b.Property(u => u.Name).HasMaxLength(256);
b.Property(u => u.NormalizedName).HasMaxLength(256);
// The relationships between Role and other entity types
// Note that these relationships are configured with no navigation properties
// Each Role can have many entries in the UserRole join table
b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
// Each Role can have many associated RoleClaims
b.HasMany<TRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<TRoleClaim>(b =>
{
// Primary key
b.HasKey(rc => rc.Id);
// Maps to the AspNetRoleClaims table
b.ToTable("AspNetRoleClaims");
});
builder.Entity<TUserRole>(b =>
{
// Primary key
b.HasKey(r => new { r.UserId, r.RoleId });
// Maps to the AspNetUserRoles table
b.ToTable("AspNetUserRoles");
});
Tipos genéricos de modelos
Identity define los tipos predeterminados de Common Language Runtime (CLR) para cada uno de los tipos de entidad enumerados anteriormente. Estos tipos tienen el prefijo Identity:
IdentityUser
IdentityRole
IdentityUserClaim
IdentityUserToken
IdentityUserLogin
IdentityRoleClaim
IdentityUserRole
En lugar de usar estos tipos directamente, los tipos se pueden usar como clases base para los propios tipos de la aplicación. Las clases DbContext
definidas por Identity son genéricas, de modo que se pueden usar diferentes tipos de CLR para uno o varios de los tipos de entidad del modelo. Estos tipos genéricos también permiten cambiar el tipo de datos de la clave principal (PK) del User
.
Cuando se usa Identity con compatibilidad con roles, se debe usar una clase IdentityDbContext. Por ejemplo:
// Uses all the built-in Identity types
// Uses `string` as the key type
public class IdentityDbContext
: IdentityDbContext<IdentityUser, IdentityRole, string>
{
}
// Uses the built-in Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityDbContext<TUser>
: IdentityDbContext<TUser, IdentityRole, string>
where TUser : IdentityUser
{
}
// Uses the built-in Identity types except with custom User and Role types
// The key type is defined by TKey
public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<
TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>,
IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TKey : IEquatable<TKey>
{
}
// No built-in Identity types are used; all are specified by generic arguments
// The key type is defined by TKey
public abstract class IdentityDbContext<
TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
: IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>
where TUserRole : IdentityUserRole<TKey>
where TUserLogin : IdentityUserLogin<TKey>
where TRoleClaim : IdentityRoleClaim<TKey>
where TUserToken : IdentityUserToken<TKey>
También es posible usar Identity sin roles (solo notificaciones), en cuyo caso se debe usar una clase IdentityUserContext<TUser>:
// Uses the built-in non-role Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityUserContext<TUser>
: IdentityUserContext<TUser, string>
where TUser : IdentityUser
{
}
// Uses the built-in non-role Identity types except with a custom User type
// The key type is defined by TKey
public class IdentityUserContext<TUser, TKey> : IdentityUserContext<
TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>,
IdentityUserToken<TKey>>
where TUser : IdentityUser<TKey>
where TKey : IEquatable<TKey>
{
}
// No built-in Identity types are used; all are specified by generic arguments, with no roles
// The key type is defined by TKey
public abstract class IdentityUserContext<
TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
where TUser : IdentityUser<TKey>
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>
where TUserLogin : IdentityUserLogin<TKey>
where TUserToken : IdentityUserToken<TKey>
{
}
Personaliza el modelo
El punto de partida para la personalización del modelo es derivar del tipo de contexto adecuado. Consulta la sección Tipos genéricos de modelos. Normalmente, se llama a ApplicationDbContext
a este tipo de contexto y se crea mediante las plantillas de ASP.NET Core.
El contexto se usa para configurar el modelo de dos maneras:
- Proporcionar tipos de entidad y clave para los parámetros de tipo genérico.
- Invalidación de
OnModelCreating
para modificar la asignación de estos tipos.
Cuando OnModelCreating
se invalida, se debe llamar primero a base.OnModelCreating
; a continuación, se debe llamar a la configuración de invalidación. Por lo general, EF Core tiene una directiva de "el último gana" para la configuración. Por ejemplo, si primero se llama al método ToTable
de un tipo de entidad con un nombre de tabla y, más tarde, se vuelve a llamar con otro nombre de tabla diferente, se usa el de la segunda llamada.
NOTA: Si DbContext
no se deriva de IdentityDbContext
, es posible que AddEntityFrameworkStores
no infiera los tipos POCO correctos de TUserClaim
, TUserLogin
y TUserToken
. Si AddEntityFrameworkStores
no deduce los tipos POCO correctos, una solución alternativa consiste en agregarlos directamente a través de services.AddScoped<IUser/RoleStore<TUser>
y UserStore<...>>
.
Datos de usuario personalizados
Los datos de usuario personalizados se admiten al heredarlos de IdentityUser
. Es habitual asignar un nombre a este tipo de ApplicationUser
:
public class ApplicationUser : IdentityUser
{
public string CustomTag { get; set; }
}
Usa el tipo ApplicationUser
como argumento genérico para el contexto:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
No es necesario invalidar OnModelCreating
en la clase ApplicationDbContext
. EF Core asigna la propiedad CustomTag
por convención. Sin embargo, la base de datos debe actualizarse para crear una nueva columna CustomTag
. Para crear la columna, agrega una migración y actualiza la base de datos como se describe en Identity y Migraciones de EF Core.
Actualiza Pages/Shared/_LoginPartial.cshtml
y reemplaza IdentityUser
con ApplicationUser
:
@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
Actualiza Areas/Identity/IdentityHostingStartup.cs
o Startup.ConfigureServices
y reemplaza IdentityUser
con ApplicationUser
.
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
La llamada a AddDefaultIdentity es equivalente al siguiente código:
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });
services.AddIdentityCore<TUser>(o =>
{
o.Stores.MaxLengthForKeys = 128;
o.SignIn.RequireConfirmedAccount = true;
})
.AddDefaultUI()
.AddDefaultTokenProviders();
Identity se proporciona como biblioteca de clases Razor. Para obtener más información, consulta Scaffolding Identity en proyectos de ASP.NET Core. Por lo tanto, el código anterior requiere una llamada a AddDefaultUI. Si el proveedor de scaffodling de Identity se usó para agregar archivos de Identity al proyecto, quite la llamada a AddDefaultUI
. Para obtener más información, consulta:
Cambio del tipo de clave principal
Un cambio en el tipo de datos de la columna de PK una vez creada la base de datos es problemático en muchos sistemas de base de datos. Por lo general, el cambio de la clave de acceso implica anular y volver a crear la tabla. Por lo tanto, los tipos de clave se deben especificar en la migración inicial cuando se crea la base de datos.
Siga los siguientes pasos para cambiar el tipo de PK:
Si la base de datos se creó antes del cambio de PK, ejecute
Drop-Database
(PMC) odotnet ef database drop
(CLI de .NET) para eliminarla.Después de confirmar la eliminación de la base de datos, quite la migración inicial con
Remove-Migration
(PMC) odotnet ef migrations remove
(CLI de .NET).Actualice la clase
ApplicationDbContext
para que se derive de IdentityDbContext<TUser,TRole,TKey>. Especifique el nuevo tipo de clave deTKey
. Por ejemplo, para usar un tipo de claveGuid
:public class ApplicationDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
En el código anterior, se deben especificar las clases genéricas IdentityUser<TKey> y se debe especificar IdentityRole<TKey> para usar el nuevo tipo de clave.
Se debe actualizar
Startup.ConfigureServices
para usar el usuario genérico:services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
Si se usa una clase personalizada
ApplicationUser
, actualice la clase para heredar deIdentityUser
. Por ejemplo:using System; using Microsoft.AspNetCore.Identity; public class ApplicationUser : IdentityUser<Guid> { public string CustomTag { get; set; } }
Actualice
ApplicationDbContext
para hacer referencia a la clase personalizadaApplicationUser
:public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Registre la clase de contexto de base de datos personalizada al agregar el servicio de Identity en
Startup.ConfigureServices
:services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
El tipo de datos de la clave principal se deduce mediante el análisis del objeto DbContext.
Identity se proporciona como biblioteca de clases Razor. Para obtener más información, consulta Scaffolding Identity en proyectos de ASP.NET Core. Por lo tanto, el código anterior requiere una llamada a AddDefaultUI. Si el proveedor de scaffodling de Identity se usó para agregar archivos de Identity al proyecto, quite la llamada a
AddDefaultUI
.Si se usa una clase personalizada
ApplicationRole
, actualice la clase para heredar deIdentityRole<TKey>
. Por ejemplo:using System; using Microsoft.AspNetCore.Identity; public class ApplicationRole : IdentityRole<Guid> { public string Description { get; set; } }
Actualice
ApplicationDbContext
para hacer referencia a la clase personalizadaApplicationRole
. Por ejemplo, la siguiente clase hace referencia a unApplicationUser
personalizado y a unApplicationRole
personalizado:using System; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Registre la clase de contexto de base de datos personalizada al agregar el servicio de Identity en
Startup.ConfigureServices
:public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultUI() .AddDefaultTokenProviders(); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
El tipo de datos de la clave principal se deduce mediante el análisis del objeto DbContext.
Identity se proporciona como biblioteca de clases Razor. Para obtener más información, consulta Scaffolding Identity en proyectos de ASP.NET Core. Por lo tanto, el código anterior requiere una llamada a AddDefaultUI. Si el proveedor de scaffodling de Identity se usó para agregar archivos de Identity al proyecto, quite la llamada a
AddDefaultUI
.
Agregar propiedades de navegación
Hacer cambios en la configuración del modelo de las relaciones puede ser más difícil que hacer otros cambios. Se debe tener cuidado a la hora de reemplazar las relaciones existentes en lugar de crear relaciones nuevas y adicionales. En concreto, la relación modificada debe especificar la misma propiedad de clave externa (FK) que la relación existente. Por ejemplo, la relación entre Users
y UserClaims
se especifica de la siguiente manera de forma predeterminada:
builder.Entity<TUser>(b =>
{
// Each User can have many UserClaims
b.HasMany<TUserClaim>()
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
La FK de esta relación se especifica como la propiedad UserClaim.UserId
. Se llama a HasMany
y WithOne
sin argumentos para crear la relación sin propiedades de navegación.
Agregue una propiedad de navegación a ApplicationUser
que permita hacer referencia a las UserClaims
del usuario:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}
El elemento TKey
de IdentityUserClaim<TKey>
es el tipo especificado para la PK de los usuarios. En este caso, TKey
es string
porque se usan los valores predeterminados. No es el tipo de PK del tipo de entidad UserClaim
.
Ahora que la propiedad de navegación existe, debe configurarse en OnModelCreating
:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
}
}
Observe que la relación se configura exactamente como era antes, solo con una propiedad de navegación especificada en la llamada a HasMany
.
Las propiedades de navegación solo existen en el modelo de EF, no en la base de datos. Dado que la FK de la relación no ha cambiado, este tipo de cambio de modelo no necesita que se actualice la base de datos. Esto se puede comprobar al agregar una migración después de hacer el cambio. Los métodos Up
y Down
están vacíos.
Agregar todas las propiedades de navegación del usuario
Con la sección anterior como guía, en el ejemplo siguiente se configuran las propiedades de navegación unidireccionales de todas las relaciones del usuario:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne()
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne()
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
Agregar propiedades de navegación del usuario y rol
Con la sección anterior como guía, en el ejemplo siguiente se configuran las propiedades de navegación de todas las relaciones en Usuario y Rol:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
public class ApplicationDbContext
: IdentityDbContext<
ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne()
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
});
}
}
Notas:
- En este ejemplo también se incluye la entidad e combinación
UserRole
, que es necesaria para navegar por la relación de varios a varios entre Usuarios y Roles. - Recuerde cambiar los tipos de las propiedades de navegación para reflejar ahora se usan que los tipos de
Application{...}
en lugar de tipos deIdentity{...}
. - Recuerde usar
Application{...}
en la definición genérica deApplicationContext
.
Agregar todas las propiedades de navegación
Con la sección anterior como guía, en el ejemplo siguiente se configuran las propiedades de navegación de todos los tipos de entidad:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<ApplicationUserClaim> Claims { get; set; }
public virtual ICollection<ApplicationUserLogin> Logins { get; set; }
public virtual ICollection<ApplicationUserToken> Tokens { get; set; }
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
public virtual ICollection<ApplicationRoleClaim> RoleClaims { get; set; }
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
public class ApplicationUserClaim : IdentityUserClaim<string>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationUserLogin : IdentityUserLogin<string>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationRoleClaim : IdentityRoleClaim<string>
{
public virtual ApplicationRole Role { get; set; }
}
public class ApplicationUserToken : IdentityUserToken<string>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationDbContext
: IdentityDbContext<
ApplicationUser, ApplicationRole, string,
ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin,
ApplicationRoleClaim, ApplicationUserToken>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne(e => e.User)
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne(e => e.User)
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne(e => e.User)
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
// Each Role can have many associated RoleClaims
b.HasMany(e => e.RoleClaims)
.WithOne(e => e.Role)
.HasForeignKey(rc => rc.RoleId)
.IsRequired();
});
}
}
Uso de claves compuestas
En las secciones anteriores se mostró cómo cambiar el tipo de clave usada en el modelo de Identity. No se admite ni recomienda cambiar el modelo de la clave de Identity para usar claves compuestas. El uso de una clave compuesta con Identity implica cambiar cómo el código de Identity interactúa con el modelo. Esta personalización está fuera del ámbito de este documento.
Cambiar los nombres de tabla o columna y las facetas
Para cambiar los nombres de las tablas y columnas, llame a base.OnModelCreating
. A continuación, agregue la configuración para invalidar cualquiera de los valores predeterminados. Por ejemplo, para cambiar el nombre de todas las tablas de Identity:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>(b =>
{
b.ToTable("MyUsers");
});
modelBuilder.Entity<IdentityUserClaim<string>>(b =>
{
b.ToTable("MyUserClaims");
});
modelBuilder.Entity<IdentityUserLogin<string>>(b =>
{
b.ToTable("MyUserLogins");
});
modelBuilder.Entity<IdentityUserToken<string>>(b =>
{
b.ToTable("MyUserTokens");
});
modelBuilder.Entity<IdentityRole>(b =>
{
b.ToTable("MyRoles");
});
modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
{
b.ToTable("MyRoleClaims");
});
modelBuilder.Entity<IdentityUserRole<string>>(b =>
{
b.ToTable("MyUserRoles");
});
}
En estos ejemplos se usan los tipos predeterminados de Identity. Si usa un tipo de aplicación como ApplicationUser
, configúrelo en lugar del tipo predeterminado.
En el ejemplo siguiente se cambian algunos nombres de columnas:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>(b =>
{
b.Property(e => e.Email).HasColumnName("EMail");
});
modelBuilder.Entity<IdentityUserClaim<string>>(b =>
{
b.Property(e => e.ClaimType).HasColumnName("CType");
b.Property(e => e.ClaimValue).HasColumnName("CValue");
});
}
Algunos tipos de columnas de base de datos se pueden configurar con determinadas facetas (por ejemplo, la longitud máxima de string
permitida). En el ejemplo siguiente se establecen las longitudes máximas de columna para varias propiedades string
del modelo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>(b =>
{
b.Property(u => u.UserName).HasMaxLength(128);
b.Property(u => u.NormalizedUserName).HasMaxLength(128);
b.Property(u => u.Email).HasMaxLength(128);
b.Property(u => u.NormalizedEmail).HasMaxLength(128);
});
modelBuilder.Entity<IdentityUserToken<string>>(b =>
{
b.Property(t => t.LoginProvider).HasMaxLength(128);
b.Property(t => t.Name).HasMaxLength(128);
});
}
Asignar a un esquema diferente
Los esquemas se pueden comportar de forma diferente entre los proveedores de bases de datos. Para SQL Server, el valor predeterminado es crear todas las tablas en el esquema dbo. Las tablas se pueden crear en un esquema diferente. Por ejemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("notdbo");
}
Carga diferida
En esta sección, se agrega compatibilidad con servidores proxy de carga diferida en el modelo de Identity. La carga diferida es útil, ya que permite usar las propiedades de navegación sin asegurarse de que se carguen primero.
Hay varias maneras en que los tipos de entidad se pueden hacer adecuados para la carga diferida, como se describe en la documentación de EF Core. Para simplificar, use servidores proxy de carga diferida. Para ello es necesario lo siguiente:
- La instalación del paquete Microsoft.EntityFrameworkCore.Proxies.
- Una llamada a UseLazyLoadingProxies dentro de AddDbContext.
- Tipos de entidad pública con propiedades de navegación
public virtual
.
En el ejemplo siguiente se muestra la forma de llamar a UseLazyLoadingProxies
en Startup.ConfigureServices
:
services
.AddDbContext<ApplicationDbContext>(
b => b.UseSqlServer(connectionString)
.UseLazyLoadingProxies())
.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Consulte los ejemplos anteriores para obtener una guía sobre cómo agregar propiedades de navegación a los tipos de entidad.
Advertencia
En este artículo se muestra el uso de cadena de conexión. Con una base de datos local, el usuario no tiene que autenticarse, pero en producción, cadena de conexión a veces incluye una contraseña para autenticarse. Una credencial de contraseña de propietario de recursos (ROPC) es un riesgo de seguridad que se debe evitar en las bases de datos de producción. Las aplicaciones de producción deben usar el flujo de autenticación más seguro disponible. Para obtener más información sobre la autenticación de aplicaciones implementadas para probar o entornos de producción, consulte Flujos de autenticación seguros.