Personalização de modelo Identity no ASP.NET Core
Por Arthur Vickers
O AsP.NET Core Identity fornece uma estrutura para gerenciar e armazenar contas de usuários em aplicativos ASP.NET Core. Identity é adicionado ao seu projeto quando Contas de usuário individuais são selecionadas como o mecanismo de autenticação. Por padrão, Identity usa um modelo de dados do EF (Entity Framework) Core. Este artigo descreve como personalizar o modelo Identity.
Identity e EF Core migrações
Antes de examinar o modelo, é útil entender como Identity funciona com EF CoreMigrações para criar e atualizar um banco de dados. No nível superior, o processo é:
- Definir ou atualizar um modelo de dados no código.
- Adicione uma migração para converter esse modelo em alterações que podem ser aplicadas ao banco de dados.
- Verifique se a migração representa corretamente suas intenções.
- Aplique a migração para atualizar o banco de dados a ser sincronizado com o modelo.
- Repita as etapas 1 a 4 para refinar ainda mais o modelo e manter o banco de dados sincronizado.
Use uma das seguintes abordagens para adicionar e aplicar migrações:
- A janela do Console do Gerenciador de Pacotes (PMC) se estiver usando o Visual Studio. Para obter mais informações, consulte EF Core Ferramentas do PMC.
- A CLI do .NET caso esteja usando a linha de comando. Para obter mais informações, consulte EF Core Ferramentas de linha de comando do .NET.
- Clicar no botão Aplicar Migrações na página de erro quando o aplicativo é executado.
O ASP.NET Core tem um manipulador de página de erro em tempo de desenvolvimento. O manipulador pode aplicar migrações quando o aplicativo é executado. Os aplicativos de produção normalmente geram scripts SQL a partir das migrações e implantam alterações de banco de dados como parte de uma implantação controlada de aplicativo e banco de dados.
Quando um novo aplicativo usando Identity é criado, as etapas 1 e 2 acima já foram concluídas. Ou seja, o modelo de dados inicial já existe e a migração inicial foi adicionada ao projeto. A migração inicial ainda precisa ser aplicada ao banco de dados. A migração inicial pode ser aplicada por meio de uma das seguintes abordagens:
- Execute
Update-Database
no PMC. - Execute
dotnet ef database update
em um shell de comando. - Clique no botão Aplicar Migrações na página de erro quando o aplicativo for executado.
Repita as etapas anteriores à medida que as alterações são feitas no modelo.
O modelo Identity
Tipos de entidade
O modelo Identity consiste nos seguintes tipos de entidade.
Tipo de entidade | Descrição |
---|---|
User |
Representa o usuário. |
Role |
Representa uma função. |
UserClaim |
Representa uma declaração que um usuário possui. |
UserToken |
Representa um token de autenticação para um usuário. |
UserLogin |
Associa um usuário a um logon. |
RoleClaim |
Representa uma declaração concedida a todos os usuários dentro de uma função. |
UserRole |
Uma entidade de junção que associa usuários e funções. |
Relações de tipo de entidade
Os tipos de entidade estão relacionados entre si das seguintes maneiras:
- Cada
User
pode ter váriosUserClaims
. - Cada
User
pode ter váriosUserLogins
. - Cada
User
pode ter váriosUserTokens
. - Cada
Role
pode ter váriosRoleClaims
associados. - Cada
User
pode ter váriosRoles
associados e cadaRole
pode ser associados a váriosUsers
. Essa é uma relação muitos para muitos que requer uma tabela de junção no banco de dados. A tabela de junção é representada pela entidadeUserRole
.
Configuração do modelo de hospedagem
Identity definem várias classes de contexto que herdam de DbContext para configurar e usar o modelo. Essa configuração é feita usando a EF Core API fluente do Code First no método OnModelCreating da classe de contexto. A configuração padrão é:
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 modelo
Identity define os tipos Common Language Runtime (CLR) padrão para cada um dos tipos de entidade listados acima. Todos esses tipos são prefixados com Identity:
IdentityUser
IdentityRole
IdentityUserClaim
IdentityUserToken
IdentityUserLogin
IdentityRoleClaim
IdentityUserRole
Em vez de usar esses tipos diretamente, os tipos podem ser usados como classes base para os próprios tipos do aplicativo. As classes DbContext
definidas por Identity são genéricas, de modo que diferentes tipos CLR podem ser usados para um ou mais dos tipos de entidade no modelo. Esses tipos genéricos também permitem que o tipo de dados de chave primária (PK) User
seja alterado.
Ao usar Identity com suporte para funções, uma classe IdentityDbContext deve ser usada. Por exemplo:
// 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>
Também é possível usar Identity sem funções (somente declarações). Nesse caso, uma classe IdentityUserContext<TUser> deve ser usada:
// 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>
{
}
Personalizar o modelo
O ponto de partida para a personalização do modelo é derivar do tipo de contexto apropriado. Consulte a seção Tipos genéricos de modelo. Esse tipo de contexto normalmente é chamado ApplicationDbContext
e é criado pelos modelos do ASP.NET Core.
O contexto é usado para configurar o modelo de duas maneiras:
- Fornecendo tipos de entidade e chave para os parâmetros de tipo genérico.
- Substituindo
OnModelCreating
para modificar o mapeamento desses tipos.
Ao substituir OnModelCreating
, base.OnModelCreating
deve ser chamado primeiro; a configuração de substituição deve ser chamada em seguida. EF Core geralmente tem uma política “o último vence” para configuração. Por exemplo, se o método ToTable
para um tipo de entidade for chamado primeiro com um nome de tabela e depois for chamado novamente com um nome de tabela diferente, o nome de tabela na segunda chamada será usado.
OBSERVAÇÃO: se DbContext
não derivar de IdentityDbContext
, AddEntityFrameworkStores
pode não inferir os tipos POCO corretos para TUserClaim
, TUserLogin
e TUserToken
. Se AddEntityFrameworkStores
não inferir os tipos POCO corretos, uma solução alternativa será adicionar diretamente os tipos corretos por meio de services.AddScoped<IUser/RoleStore<TUser>
e UserStore<...>>
.
Dados de usuário personalizados
Há suporte para dados de usuário personalizados ao herdá-los de IdentityUser
. É comum nomear esse tipo ApplicationUser
:
public class ApplicationUser : IdentityUser
{
public string CustomTag { get; set; }
}
Use o tipo ApplicationUser
como um argumento genérico para o contexto:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
Não é necessário substituir OnModelCreating
na classe ApplicationDbContext
. EF Core mapeia a propriedade CustomTag
por convenção. No entanto, o banco de dados precisa ser atualizado para criar uma nova coluna CustomTag
. Para criar a coluna, adicione uma migração e atualize o banco de dados conforme descrito em Identity e EF Core Migrações.
Atualize Pages/Shared/_LoginPartial.cshtml
e substitua IdentityUser
por ApplicationUser
:
@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
Atualize Areas/Identity/IdentityHostingStartup.cs
ou Startup.ConfigureServices
e substitua IdentityUser
por ApplicationUser
.
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Chamar AddDefaultIdentity é equivalente ao seguinte 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 é fornecido como uma biblioteca de classes do Razor. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core. Portanto, o código anterior requer uma chamada para AddDefaultUI. Se o scaffolder Identity foi usado para adicionar arquivos Identity ao projeto, remova a chamada para AddDefaultUI
. Para obter mais informações, consulte:
Alterar o tipo de chave primária
Uma alteração no tipo de dados da coluna da chave primária após a criação do banco de dados é problemática em muitos sistemas de banco de dados. Alterar a chave primária normalmente envolve a remoção e a recriação da tabela. Portanto, os tipos de chave devem ser especificados na migração inicial quando o banco de dados é criado.
Siga as etapas a seguir para alterar o tipo de chave primária:
Caso o banco de dados seja criado antes da alteração da chave primária, execute
Drop-Database
(PMC) oudotnet ef database drop
(CLI do .NET) para excluí-lo.Após confirmar a exclusão do banco de dados, remova a migração inicial com
Remove-Migration
(PMC) oudotnet ef migrations remove
(CLI do .NET).Atualize a classe
ApplicationDbContext
para que ela seja derivada de IdentityDbContext<TUser,TRole,TKey>. Especifique o novo tipo de chave paraTKey
. Por exemplo, para usar um tipo de chaveGuid
:public class ApplicationDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
No código anterior, as classes genéricas IdentityUser<TKey> e IdentityRole<TKey> devem ser especificadas para usar o novo tipo de chave.
Startup.ConfigureServices
deve ser atualizado para usar o usuário genérico:services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
Se uma classe personalizada
ApplicationUser
estiver sendo usada, atualize a classe para herdar deIdentityUser
. Por exemplo:using System; using Microsoft.AspNetCore.Identity; public class ApplicationUser : IdentityUser<Guid> { public string CustomTag { get; set; } }
Atualize
ApplicationDbContext
para fazer referência à classe personalizadaApplicationUser
:public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Registre a classe de contexto de banco de dados personalizada ao adicionar o serviço Identity em
Startup.ConfigureServices
:services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
O tipo de dados da chave primária é inferido analisando o objeto DbContext.
Identity é fornecido como uma biblioteca de classes do Razor. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core. Portanto, o código anterior requer uma chamada para AddDefaultUI. Se o scaffolder Identity foi usado para adicionar arquivos Identity ao projeto, remova a chamada para
AddDefaultUI
.Se uma classe personalizada
ApplicationRole
estiver sendo usada, atualize a classe para herdar deIdentityRole<TKey>
. Por exemplo:using System; using Microsoft.AspNetCore.Identity; public class ApplicationRole : IdentityRole<Guid> { public string Description { get; set; } }
Atualize
ApplicationDbContext
para fazer referência à classe personalizadaApplicationRole
. Por exemplo, a classe a seguir faz referência a umApplicationUser
personalizado e umApplicationRole
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 a classe de contexto de banco de dados personalizada ao adicionar o serviço Identity em
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); }
O tipo de dados da chave primária é inferido analisando o objeto DbContext.
Identity é fornecido como uma biblioteca de classes do Razor. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core. Portanto, o código anterior requer uma chamada para AddDefaultUI. Se o scaffolder Identity foi usado para adicionar arquivos Identity ao projeto, remova a chamada para
AddDefaultUI
.
Adicionar propriedades de navegação
Alterar a configuração do modelo para relações pode ser mais difícil do que fazer outras alterações. É necessário ter cuidado para substituir as relações existentes em vez de criar relações adicionais. Em particular, a relação alterada deve especificar a mesma propriedade de FK (chave estrangeira) que a relação existente. Por exemplo, a relação entre Users
e UserClaims
é, por padrão, especificada da seguinte maneira:
builder.Entity<TUser>(b =>
{
// Each User can have many UserClaims
b.HasMany<TUserClaim>()
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
A chave estrangeira dessa relação é especificada como a propriedade UserClaim.UserId
. HasMany
e WithOne
são chamados sem argumentos para criar a relação sem propriedades de navegação.
Adicione uma propriedade de navegação a ApplicationUser
que permite que o UserClaims
associado seja referenciado pelo usuário:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}
O TKey
para IdentityUserClaim<TKey>
é o tipo especificado para a chave estrangeira dos usuários. Nesse caso, TKey
é string
porque os padrões estão sendo usados. Não é o tipo de chave primária do tipo de entidade UserClaim
.
Agora que a propriedade de navegação existe, ela deve ser configurada em 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 a relação está configurada exatamente como anteriormente, somente com uma propriedade de navegação especificada na chamada para HasMany
.
As propriedades de navegação só existem no modelo do EF, não no banco de dados. Como a chave estrangeira da relação não foi alterada, esse tipo de alteração de modelo não exige que o banco de dados seja atualizado. Isso pode ser verificado adicionando uma migração depois de fazer a alteração. Os métodos Up
e Down
estão vazios.
Adicionar todas as propriedades de navegação do usuário
Usando a seção acima como orientação, o exemplo a seguir configura as propriedades de navegação unidirecional para todas as relações no usuário:
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();
});
}
}
Adicionar propriedades de navegação de usuário e função
Usando a seção acima como orientação, o exemplo a seguir configura as propriedades de navegação para todas as relações no usuário e na função:
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();
});
}
}
Observações:
- Esse exemplo também inclui a entidade de junção
UserRole
, que é necessária para navegar na relação muitos para muitos de usuários para funções. - Lembre-se de alterar os tipos das propriedades de navegação para refletir que os tipos
Application{...}
agora estão sendo usados em vez de tiposIdentity{...}
. - Lembre-se de usar o
Application{...}
na definição genéricaApplicationContext
.
Adicionar todas as propriedades de navegação
Usando a seção acima como orientação, o exemplo a seguir configura as propriedades de navegação para todas as relações em todos os tipos de entidade:
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();
});
}
}
Usar chaves compostas
As seções anteriores demonstraram a alteração do tipo de chave usado no modelo Identity. Não se recomenda ou há suporte para alterar o modelo de chave Identity para usar chaves compostas. Usar uma chave composta com Identity envolve alterar a forma como o código do gerenciador Identity interage com o modelo. Essa personalização está além do escopo deste documento.
Alterar nomes e facetas de tabela/coluna
Para alterar os nomes de tabelas e colunas, chame base.OnModelCreating
. Em seguida, adicione a configuração para substituir os padrões. Por exemplo, para alterar o nome de todas as tabelas 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");
});
}
Esses exemplos usam os tipos padrão Identity. Se estiver usando um tipo de aplicativo como ApplicationUser
, configure esse tipo em vez do tipo padrão.
O exemplo a seguir altera alguns nomes de coluna:
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");
});
}
Alguns tipos de colunas de banco de dados podem ser configurados com determinadas facetas (por exemplo, o comprimento máximo string
permitido). O exemplo a seguir define os comprimentos máximos de coluna para várias propriedades string
no 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);
});
}
Mapear para um esquema diferente
Esquemas podem se comportar de forma diferente entre provedores de banco de dados. Para SQL Server, o padrão é criar todas as tabelas no esquema dbo. As tabelas podem ser criadas em um esquema diferente. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("notdbo");
}
Carregamento lento
Nessa seção, o suporte para proxies de carregamento lento no modelo Identity é adicionado. O carregamento lento é útil, pois permite que as propriedades de navegação sejam usadas sem primeiro garantir que elas sejam carregadas.
Os tipos de entidade podem ser adequados para carregamento lento de várias maneiras, conforme descrito na EF Core documentação. Para simplificar, use proxies de carregamento lento, o que requer:
- Instalação do pacote Microsoft.EntityFrameworkCore.Proxies.
- Uma chamada para UseLazyLoadingProxies dentro de AddDbContext.
- Tipos de entidade pública com propriedades de navegação
public virtual
.
O exemplo a seguir demonstra como chamar UseLazyLoadingProxies
em Startup.ConfigureServices
:
services
.AddDbContext<ApplicationDbContext>(
b => b.UseSqlServer(connectionString)
.UseLazyLoadingProxies())
.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Consulte os exemplos anteriores para obter diretrizes sobre como adicionar propriedades de navegação aos tipos de entidade.
Aviso
Este artigo mostra o uso de cadeias de conexão. Com um banco de dados local, o usuário não precisa ser autenticado, mas na produção, as cadeias de conexão às vezes incluem uma senha para autenticação. Uma credencial de senha do proprietário do recurso (ROPC) é um risco de segurança que deve ser evitado em bancos de dados de produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos implantados em ambientes de teste ou produção, consulte Fluxos de autenticação seguros.