Partilhar via


Identity personalização do modelo no ASP.NET Core

Por Arthur Vickers

ASP.NET Core Identity fornece uma estrutura para gerenciar e armazenar contas de usuário em aplicativos ASP.NET Core. Identity é adicionado ao seu projeto quando Contas individuais é selecionado como o mecanismo de autenticação. Por padrão, Identity usa um modelo de dados Core do Entity Framework (EF). Este artigo descreve como personalizar o Identity modelo.

Identity e EF Core Migrações

Antes de examinar o modelo, é útil entender como Identity funciona com EF Core Migrações para criar e atualizar um banco de dados. Ao nível superior, o processo é:

  1. Defina ou atualize um modelo de dados no código.
  2. Adicione uma migração para traduzir esse modelo em alterações que podem ser aplicadas ao banco de dados.
  3. Verifique se a Migração representa corretamente as suas intenções.
  4. Aplique a Migração para atualizar o banco de dados para estar em sincronia com o modelo.
  5. 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 PMC (Console do Gerenciador de Pacotes ) se estiver usando o Visual Studio. Para obter mais informações, consulte EF Core Ferramentas PMC.
  • A CLI do .NET se estiver 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 possui um manipulador de páginas de erro durante a fase 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 no banco de dados como parte de uma implantação controlada de aplicativo e banco de dados.

Quando um novo aplicativo é 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 através de uma das seguintes abordagens:

  • Executar Update-Database em 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 forem feitas alterações no modelo.

Importante

Quando as opções que afetam o modelo subjacente são configuradas (por exemplo, Identity ou EF Core), esses valores de opção também devem ser aplicados durante o tempo de desenvolvimento para que as Migrações gerem a forma correta do modelo. Se as ferramentas do EF forem executadas sem essas opções configuradas, as migrações geradas poderão omitir as alterações pretendidas. Para obter mais informações, consulte efcore#36314. Para garantir que Identity as opções sejam aplicadas de forma consistente durante a geração da migração, use uma das seguintes abordagens:

  • Defina o projeto de inicialização: Execute dotnet ef comandos (ou comandos PMC) com o projeto de aplicativo que tenha AddDefaultIdentity ou AddIdentityCore definido como o projeto de inicialização. Por exemplo, ao executar comandos de um projeto de biblioteca de classes, especifique o projeto de inicialização com dotnet ef migrations add {MIGRATION_NAME} --startup-project {PATH_TO_APP_PROJECT}, onde o espaço reservado {MIGRATION_NAME} é o nome da migração e o espaço reservado {PATH_TO_APP_PROJECT} é o caminho para o projeto de aplicativo.
  • Implementar IDesignTimeDbContextFactory: Como alternativa, implemente um IDesignTimeDbContextFactory<TContext> que construa o contexto e aplique a configuração de opção equivalente Identity . Para obter uma Aspiresolução amigável, consulte efcore#35285 (comment).

Exemplo Identity de configuração em Program.cs:

builder.Services
    .AddDefaultIdentity<ApplicationUser>(options =>
    {
        options.Stores.SchemaVersion = IdentitySchemaVersions.Version2;
        options.Stores.MaxLengthForKeys = 256;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>();

Exemplo de fábrica em tempo de design:

public class DesignTimeApplicationDbContextFactory
    : IDesignTimeDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseSqlServer("{CONNECTION_STRING}");

        return new ApplicationDbContext(optionsBuilder.Options);
    }
}

Observação

Você não pode acessar options.Stores.MaxLengthForKeys diretamente dentro OnModelCreating porque a injeção de dependência não está disponível em tempo de design. Em vez disso, especifique o valor configurado diretamente (como HasMaxLength(256)) ou use um mecanismo de tempo de conceção para transmitir as definições, se necessário. Para obter mais detalhes, consulte Configurar ASP.NET Core Identity.

Sugestão

Sempre verifique se o instantâneo do modelo resultante reflete os comprimentos de chave pretendidos ou a versão do esquema depois de adicionar uma migração.

O Identity modelo

Tipos de entidades

O Identity modelo consiste nos seguintes tipos de entidade.

Tipo de entidade Descrição
User Representa o usuário.
Role Representa uma função.
UserClaim Representa uma reivindicação que um utilizador possui.
UserToken Representa um token de autenticação para um usuário.
UserLogin Associa um usuário a um login.
RoleClaim Representa uma reivindicação concedida a todos os utilizadores dentro de uma função.
UserRole Uma entidade de associaçã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 muitos UserClaims.
  • Cada User pode ter muitos UserLogins.
  • Cada User pode ter muitos UserTokens.
  • Cada Role pode ter muitos(as) RoleClaims associados(as).
  • Cada User pode ter muitos Roles associados, e cada Role pode ser associado a muitos Users. Esta é uma relação muitos-para-muitos que requer uma tabela de junção no banco de dados. A tabela de junção é representada pela entidade UserRole.

Configuração padrão do modelo

Identity Define muitas classes de contexto que herdam de DbContext para configurar e usar o modelo. Essa configuração é feita usando a EF Core API Code First Fluent no OnModelCreating método da classe context. 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 modelos

Identity define os tipos CLR ( Common Language Runtime ) padrão para cada um dos tipos de entidade listados acima. Estes tipos são todos 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 DbContext classes definidas por Identity são genéricas, de modo que diferentes tipos CLR podem ser usados para um ou mais tipos de entidade no modelo. Esses tipos genéricos também permitem alterar o tipo de dados da chave primária (PK) de User.

Ao usar Identity com suporte para funções, uma IdentityDbContext classe 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 (apenas declarações), caso em que uma IdentityUserContext<TUser> classe 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 do modelo . Esse tipo de contexto é normalmente chamado ApplicationDbContext e é criado pelos modelos ASP.NET Core.

O contexto é usado para configurar o modelo de duas maneiras:

  • Entidade fornecedora e tipos de chave para os parâmetros de tipo genéricos.
  • Sobrescrever OnModelCreating para modificar o mapeamento destes 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 de último ganho para configuração. Por exemplo, se o ToTable método para um tipo de entidade for chamado primeiro com um nome de tabela e depois novamente com um nome de tabela diferente, o nome da tabela na segunda chamada será usado.

NOTA: 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 é adicionar diretamente os tipos corretos via services.AddScoped<IUser/RoleStore<TUser> e UserStore<...>>.

Dados personalizados do usuário

Os dados personalizados do usuário são suportados pela herança do IdentityUser. É costume nomear este tipo ApplicationUser:

public class ApplicationUser : IdentityUser
{
    public string CustomTag { get; set; }
}

Use o ApplicationUser tipo 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 há necessidade de substituir OnModelCreating na classe ApplicationDbContext. EF Core mapeia por convenção a propriedade CustomTag. No entanto, o banco de dados precisa ser atualizado para criar uma nova CustomTag coluna. 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 substitua IdentityUser por ApplicationUser.

services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();                                    

A chamada 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 Razor. Para obter mais informações, consulte Scaffold Identity em projetos ASP.NET Core. Consequentemente, o código anterior requer uma chamada para AddDefaultUI. Se o Identity scaffolder foi usado para adicionar Identity arquivos 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 PK após a criação do banco de dados é problemática em muitos sistemas de banco de dados. Alterar a PK normalmente envolve descartar e recriar a tabela. Portanto, os tipos de chave devem ser especificados na migração inicial quando o banco de dados é criado.

Siga estes passos para alterar o tipo PK:

  1. Se o banco de dados foi criado antes da alteração PK, execute Drop-Database (PMC) ou dotnet ef database drop (.NET CLI) para excluí-lo.

  2. Depois de confirmar a exclusão do banco de dados, remova a migração inicial com Remove-Migration (PMC) ou dotnet ef migrations remove (.NET CLI).

  3. Atualize a ApplicationDbContext classe para derivar de IdentityDbContext<TUser,TRole,TKey>. Especifique o novo tipo de chave para TKey. Por exemplo, para usar um tipo de Guid chave:

    public class ApplicationDbContext
        : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    No código anterior, as classes IdentityUser<TKey> genéricas 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>();
    
  4. Se uma classe personalizada ApplicationUser estiver sendo usada, atualize a classe para herdar do IdentityUser. 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 personalizada ApplicationUser :

    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 Identity serviço em Startup.ConfigureServices:

    services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    

    O tipo de dados da chave primária é inferido analisando o DbContext objeto.

    Identity é fornecido como uma biblioteca de classes Razor. Para obter mais informações, consulte Scaffold Identity em projetos ASP.NET Core. Consequentemente, o código anterior requer uma chamada para AddDefaultUI. Se o Identity scaffolder foi usado para adicionar Identity arquivos ao projeto, remova a chamada para AddDefaultUI.

  5. Se uma classe personalizada ApplicationRole estiver sendo usada, atualize a classe para herdar do IdentityRole<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 personalizada ApplicationRole . Por exemplo, a classe a seguir faz referência a um elemento customizado ApplicationUser e a um elemento personalizado ApplicationRole:

    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 Identity serviço 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 DbContext objeto.

    Identity é fornecido como uma biblioteca de classes Razor. Para obter mais informações, consulte Scaffold Identity em projetos ASP.NET Core. Consequentemente, o código anterior requer uma chamada para AddDefaultUI. Se o Identity scaffolder foi usado para adicionar Identity arquivos ao projeto, remova a chamada para AddDefaultUI.

Adicionar propriedades de navegação

Alterar a configuração do modelo para relacionamentos pode ser mais difícil do que fazer outras alterações. Deve-se ter o cuidado de substituir as relações existentes em vez de criar novas relações adicionais. Em particular, a relação alterada deve especificar a mesma propriedade de chave estrangeira (FK) que a relação existente. Por exemplo, a relação entre Users e UserClaims é, por padrão, especificada da seguinte forma:

builder.Entity<TUser>(b =>
{
    // Each User can have many UserClaims
    b.HasMany<TUserClaim>()
     .WithOne()
     .HasForeignKey(uc => uc.UserId)
     .IsRequired();
});

O FK para essa relação é especificado como 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 permita que UserClaims associados sejam referenciados 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 PK dos utilizadores. Neste caso, TKey é string porque os valores predefinidos estão a ser usados. Não é o tipo PK para o UserClaim tipo de entidade.

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 o relacionamento é configurado exatamente como era antes, apenas com uma propriedade de navegação especificada na chamada para HasMany.

As propriedades de navegação só existem no modelo EF, não no banco de dados. Como o FK da relação não foi alterado, 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 Up métodos 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 propriedades de navegação unidirecional para todos os relacionamentos em User:

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 todos os relacionamentos em Usuário e 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:

  • Este exemplo também inclui a UserRole entidade de associação, que é necessária para navegar nas relações muitos-para-muitos de Utilizadores para Funções.
  • Lembre-se de alterar os tipos das propriedades de navegação para refletir que tipos de Application{...} estão a ser usados agora em vez de tipos de Identity{...}.
  • Lembre-se de usar o Application{...} na definição genérica ApplicationContext .

Adicionar todas as propriedades de navegação

Usando a seção acima como orientação, o exemplo a seguir configura propriedades de navegação para todos os relacionamentos 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 usada no Identity modelo. A alteração do modelo de Identity chave para usar chaves compostas não é suportada ou recomendada. Usar uma chave composta com Identity envolve alterar como o código do Identity gerenciador interage com o modelo. Essa personalização está além do escopo deste documento.

Alterar nomes e facetas de tabelas/colunas

Para alterar os nomes de tabelas e colunas, chame base.OnModelCreating. Em seguida, adicione uma configuração para substituir qualquer configuração padrão. Por exemplo, para alterar o nome de todas as Identity tabelas:

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 comprimentos máximos de coluna para várias string propriedades 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

Os esquemas podem se comportar de forma diferente entre os provedores de banco de dados. Para o 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 preguiçoso

Nesta seção, é adicionado suporte para proxies de carregamento lento no modelo Identity. 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, que exigem:

O exemplo a seguir demonstra a chamada UseLazyLoadingProxies em Startup.ConfigureServices.

services
    .AddDbContext<ApplicationDbContext>(
        b => b.UseSqlServer(connectionString)
              .UseLazyLoadingProxies())
    .AddDefaultIdentity<ApplicationUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Consulte os exemplos anteriores para obter orientação sobre como adicionar propriedades de navegação aos tipos de entidade.

Advertência

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 autenticar. Uma credencial de senha de proprietário de 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 segura.

Recursos adicionais