Condividi tramite


Identity personalizzazione del modello in ASP.NET Core

Di Arthur Vickers

ASP.NET Core Identity offre un framework per la gestione e l'archiviazione degli account utente nelle app ASP.NET Core. Identity viene aggiunto al progetto quando l'opzione Account utente singoli è selezionata come meccanismo di autenticazione. Per impostazione predefinita, Identity usa un modello di dati Entity Framework (EF Core). Questo articolo descrive come personalizzare il Identity modello.

Identity e EF Core Migrazioni

Prima di esaminare il modello, è utile comprendere come Identity funziona con EF Core le migrazioni per creare e aggiornare un database. Al livello superiore, il processo è:

  1. Definire o aggiornare un modello di dati nel codice.
  2. Aggiungere una migrazione per convertire questo modello in modifiche che possono essere applicate al database.
  3. Verificare che la migrazione rappresenti correttamente le intenzioni.
  4. Applicare la migrazione per aggiornare il database in modo che sia sincronizzato con il modello.
  5. Ripetere i passaggi da 1 a 4 per perfezionare ulteriormente il modello e mantenere il database sincronizzato.

Usare uno degli approcci seguenti per aggiungere e applicare Migrazioni:

  • Finestra Gestione pacchetti Console (PMC) se si usa Visual Studio. Per altre informazioni, vedere EF Core Strumenti PMC.
  • Interfaccia della riga di comando di .NET se si usa la riga di comando. Per altre informazioni, vedere EF Core Strumenti da riga di comando .NET.
  • Fare clic sul pulsante Applica migrazioni nella pagina di errore quando viene eseguita l'app.

ASP.NET Core dispone di un gestore di pagina degli errori in fase di sviluppo. Il gestore può applicare le migrazioni quando viene eseguita l'app. Le app di produzione generano in genere script SQL dalle migrazioni e distribuiscono le modifiche del database come parte di un'app controllata e di una distribuzione di database.

Quando viene creata una nuova app con Identity , i passaggi 1 e 2 precedenti sono già stati completati. Ovvero, il modello di dati iniziale esiste già e la migrazione iniziale è stata aggiunta al progetto. La migrazione iniziale deve comunque essere applicata al database. La migrazione iniziale può essere applicata tramite uno degli approcci seguenti:

  • Eseguire Update-Database in PMC.
  • Eseguire dotnet ef database update in una shell dei comandi.
  • Fare clic sul pulsante Applica migrazioni nella pagina di errore quando viene eseguita l'app.

Ripetere i passaggi precedenti man mano che vengono apportate modifiche al modello.

Modello Identity

Tipi di entità

Il Identity modello è costituito dai tipi di entità seguenti.

Tipo di entità Descrizione
User Rappresenta l'utente.
Role Rappresenta un ruolo.
UserClaim Rappresenta un'attestazione che un utente possiede.
UserToken Rappresenta un token di autenticazione per un utente.
UserLogin Associa un utente a un account di accesso.
RoleClaim Rappresenta un'attestazione concessa a tutti gli utenti all'interno di un ruolo.
UserRole Entità join che associa utenti e ruoli.

Relazioni tra tipi di entità

I tipi di entità sono correlati tra loro nei modi seguenti:

  • Ognuno User può avere molti UserClaims.
  • Ognuno User può avere molti UserLogins.
  • Ognuno User può avere molti UserTokens.
  • Ognuno di essi Role può avere molti associati RoleClaims.
  • Ognuno User può avere molti associati Rolese ognuno Role può essere associato a molti Users. Si tratta di una relazione molti-a-molti che richiede una tabella join nel database. La tabella join è rappresentata dall'entità UserRole .

Configurazione del modello predefinita

Identity definisce molte classi di contesto che ereditano da DbContext per configurare e usare il modello. Questa configurazione viene eseguita usando l'API EF Core Code First Fluent nel OnModelCreating metodo della classe di contesto. La configurazione predefinita è:

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

Tipi generici di modello

Identitydefinisce i tipi CLR (Common Language Runtime) predefiniti per ognuno dei tipi di entità elencati in precedenza. Questi tipi sono tutti preceduti da Identity:

  • IdentityUser
  • IdentityRole
  • IdentityUserClaim
  • IdentityUserToken
  • IdentityUserLogin
  • IdentityRoleClaim
  • IdentityUserRole

Anziché usare direttamente questi tipi, i tipi possono essere usati come classi di base per i tipi personalizzati dell'app. Le DbContext classi definite da Identity sono generiche, in modo che diversi tipi CLR possano essere usati per uno o più tipi di entità nel modello. Questi tipi generici consentono anche di modificare il User tipo di dati chiave primaria (PK).

Quando si usa Identity con il supporto per i ruoli, deve essere usata una IdentityDbContext classe . Ad esempio:

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

È anche possibile usare Identity senza ruoli (solo attestazioni), nel qual caso deve essere usata una IdentityUserContext<TUser> classe:

// 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>
{
}

Personalizzare il modello

Il punto di partenza per la personalizzazione del modello consiste nel derivare dal tipo di contesto appropriato. Vedere la sezione Modelli di tipi generici . Questo tipo di contesto viene chiamato ApplicationDbContext in modo personalizzato e viene creato dai modelli ASP.NET Core.

Il contesto viene usato per configurare il modello in due modi:

  • Specifica di tipi di entità e di chiave per i parametri di tipo generico.
  • Override OnModelCreating di per modificare il mapping di questi tipi.

Quando si esegue l'override OnModelCreatingdi , base.OnModelCreating deve essere chiamato per primo. La configurazione di override deve essere chiamata successivamente. EF Core in genere ha un criterio di ultima vittoria per la configurazione. Ad esempio, se il ToTable metodo per un tipo di entità viene chiamato prima con un nome di tabella e quindi più avanti con un nome di tabella diverso, viene usato il nome della tabella nella seconda chiamata.

NOTA: se l'oggetto DbContext non deriva da IdentityDbContext, AddEntityFrameworkStores potrebbe non dedurre i tipi POCO corretti per TUserClaim, TUserLogine TUserToken. Se AddEntityFrameworkStores non deduce i tipi POCO corretti, una soluzione alternativa consiste nell'aggiungere direttamente i tipi corretti tramite services.AddScoped<IUser/RoleStore<TUser> e UserStore<...>>.

Dati utente personalizzati

I dati utente personalizzati sono supportati ereditando da IdentityUser. È consuetudine denominare questo tipo ApplicationUser:

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

Usare il ApplicationUser tipo come argomento generico per il contesto:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

Non è necessario eseguire l'override OnModelCreating nella ApplicationDbContext classe . EF Core esegue il mapping della CustomTag proprietà per convenzione. Tuttavia, il database deve essere aggiornato per creare una nuova CustomTag colonna. Per creare la colonna, aggiungere una migrazione e quindi aggiornare il database come descritto in Identity e EF Core Migrazioni.

Aggiornare Pages/Shared/_LoginPartial.cshtml e sostituire IdentityUser con ApplicationUser:

@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

Aggiornare Areas/Identity/IdentityHostingStartup.cs o Startup.ConfigureServices sostituire IdentityUser con ApplicationUser.

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

La chiamata AddDefaultIdentity è equivalente al codice seguente:

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 viene fornito come libreria di Razor classi. Per altre informazioni, vedere Scaffolding Identity nei progetti ASP.NET Core. Di conseguenza, il codice precedente richiede una chiamata a AddDefaultUI. Se lo Identity scaffolder è stato usato per aggiungere Identity file al progetto, rimuovere la chiamata a AddDefaultUI. Per altre informazioni, vedi:

Modificare il tipo di chiave primaria

Una modifica al tipo di dati della colonna PK dopo la creazione del database è problematica in molti sistemi di database. La modifica dell'infrastruttura a chiave pubblica comporta in genere l'eliminazione e la ricreazione della tabella. Pertanto, i tipi di chiave devono essere specificati nella migrazione iniziale al momento della creazione del database.

Per modificare il tipo di infrastruttura a chiave pubblica, seguire questa procedura:

  1. Se il database è stato creato prima della modifica pk, eseguire Drop-Database (PMC) o dotnet ef database drop (interfaccia della riga di comando .NET) per eliminarlo.

  2. Dopo aver confermato l'eliminazione del database, rimuovere la migrazione iniziale con Remove-Migration (PMC) o dotnet ef migrations remove l'interfaccia della riga di comando (.NET).

  3. Aggiornare la ApplicationDbContext classe per derivare da IdentityDbContext<TUser,TRole,TKey>. Specificare il nuovo tipo di chiave per TKey. Ad esempio, per usare un Guid tipo di chiave:

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

    Nel codice precedente, le classi generiche IdentityUser<TKey> e IdentityRole<TKey> devono essere specificate per usare il nuovo tipo di chiave.

    Startup.ConfigureServices deve essere aggiornato per usare l'utente generico:

    services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    
  4. Se viene usata una classe personalizzata ApplicationUser , aggiornare la classe per ereditare da IdentityUser. Ad esempio:

    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationUser : IdentityUser<Guid>
    {
        public string CustomTag { get; set; }
    }
    

    Aggiornare ApplicationDbContext per fare riferimento alla classe personalizzata ApplicationUser :

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

    Registrare la classe di contesto del database personalizzata quando si aggiunge il Identity servizio in Startup.ConfigureServices:

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

    Il tipo di dati della chiave primaria viene dedotto analizzando l'oggetto DbContext .

    Identity viene fornito come libreria di Razor classi. Per altre informazioni, vedere Scaffolding Identity nei progetti ASP.NET Core. Di conseguenza, il codice precedente richiede una chiamata a AddDefaultUI. Se lo Identity scaffolder è stato usato per aggiungere Identity file al progetto, rimuovere la chiamata a AddDefaultUI.

  5. Se viene usata una classe personalizzata ApplicationRole , aggiornare la classe per ereditare da IdentityRole<TKey>. Ad esempio:

    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationRole : IdentityRole<Guid>
    {
        public string Description { get; set; }
    }
    

    Aggiornare ApplicationDbContext per fare riferimento alla classe personalizzata ApplicationRole . Ad esempio, la classe seguente fa riferimento a un oggetto personalizzato ApplicationUser e a un oggetto personalizzato 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)
        {
        }
    }
    

    Registrare la classe di contesto del database personalizzata quando si aggiunge il Identity servizio in 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);
    }
    

    Il tipo di dati della chiave primaria viene dedotto analizzando l'oggetto DbContext .

    Identity viene fornito come libreria di Razor classi. Per altre informazioni, vedere Scaffolding Identity nei progetti ASP.NET Core. Di conseguenza, il codice precedente richiede una chiamata a AddDefaultUI. Se lo Identity scaffolder è stato usato per aggiungere Identity file al progetto, rimuovere la chiamata a AddDefaultUI.

Aggiungere proprietà di navigazione

La modifica della configurazione del modello per le relazioni può essere più difficile rispetto ad altre modifiche. È necessario prestare attenzione a sostituire le relazioni esistenti anziché creare nuove relazioni aggiuntive. In particolare, la relazione modificata deve specificare la stessa proprietà di chiave esterna (FK) della relazione esistente. Ad esempio, la relazione tra Users e UserClaims è, per impostazione predefinita, specificata come segue:

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

L'FK per questa relazione viene specificato come UserClaim.UserId proprietà . HasMany e WithOne vengono chiamati senza argomenti per creare la relazione senza proprietà di navigazione.

Aggiungere una proprietà di navigazione a ApplicationUser che consenta di fare riferimento all'utente UserClaims :

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}

Per TKey IdentityUserClaim<TKey> è il tipo specificato per l'infrastruttura a chiave pubblica di utenti. In questo caso, TKey perché string vengono usate le impostazioni predefinite. Non è il tipo PK per il UserClaim tipo di entità.

Ora che la proprietà di navigazione esiste, deve essere configurata in 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();
        });
    }
}

Si noti che la relazione è configurata esattamente come in precedenza, solo con una proprietà di navigazione specificata nella chiamata a HasMany.

Le proprietà di navigazione esistono solo nel modello di Entity Framework, non nel database. Poiché l'FK per la relazione non è stato modificato, questo tipo di modifica del modello non richiede l'aggiornamento del database. Questa operazione può essere controllata aggiungendo una migrazione dopo aver apportato la modifica. I Up metodi e Down sono vuoti.

Aggiungere tutte le proprietà di spostamento utente

Usando la sezione precedente come indicazioni, l'esempio seguente configura le proprietà di navigazione unidirezionali per tutte le relazioni in 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();
        });
    }
}

Aggiungere proprietà di navigazione utente e ruolo

Usando la sezione precedente come materiale sussidiario, nell'esempio seguente vengono configurate le proprietà di navigazione per tutte le relazioni in User e Role:

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

    }
}

Note:

  • Questo esempio include anche l'entità UserRole join, necessaria per spostarsi nella relazione molti-a-molti da Utenti a Ruoli.
  • Ricordarsi di modificare i tipi delle proprietà di navigazione in modo che riflettano che Application{...} i tipi vengono usati invece dei Identity{...} tipi.
  • Ricordarsi di usare nella Application{...} definizione generica ApplicationContext .

Aggiungere tutte le proprietà di navigazione

Usando la sezione precedente come materiale sussidiario, nell'esempio seguente vengono configurate le proprietà di navigazione per tutte le relazioni su tutti i tipi di entità:

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

Usare chiavi composite

Le sezioni precedenti hanno illustrato la modifica del tipo di chiave usata nel Identity modello. La modifica del modello di chiave per l'uso Identity di chiavi composite non è supportata o consigliata. L'uso di una chiave composita con Identity comporta la modifica del modo in cui il codice di Identity gestione interagisce con il modello. Questa personalizzazione esula dall'ambito di questo documento.

Modificare i nomi di tabella/colonna e i facet

Per modificare i nomi di tabelle e colonne, chiamare base.OnModelCreating. Aggiungere quindi la configurazione per eseguire l'override di una delle impostazioni predefinite. Ad esempio, per modificare il nome di tutte le Identity tabelle:

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

Questi esempi usano i tipi predefiniti Identity . Se si usa un tipo di app, ApplicationUserad esempio , configurare tale tipo anziché il tipo predefinito.

Nell'esempio seguente vengono modificati alcuni nomi di colonna:

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

Alcuni tipi di colonne di database possono essere configurati con determinati facet, ad esempio la lunghezza massima string consentita. Nell'esempio seguente vengono impostate le lunghezze massime della colonna per diverse string proprietà nel modello:

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

Eseguire il mapping a uno schema diverso

Gli schemi possono comportarsi in modo diverso tra provider di database. Per SQL Server, l'impostazione predefinita consiste nel creare tutte le tabelle nello schema dbo . Le tabelle possono essere create in uno schema diverso. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.HasDefaultSchema("notdbo");
}

Caricamento lazy

In questa sezione viene aggiunto il supporto per i proxy di caricamento differita nel Identity modello. Il caricamento differita è utile perché consente l'uso delle proprietà di navigazione senza prima assicurarsi che vengano caricate.

I tipi di entità possono essere resi adatti per il caricamento differita in diversi modi, come descritto nella EF Core documentazione. Per semplicità, usare proxy lazy-loading, che richiedono:

Nell'esempio seguente viene illustrata la chiamata UseLazyLoadingProxies in Startup.ConfigureServices:

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

Per indicazioni sull'aggiunta di proprietà di navigazione ai tipi di entità, vedere gli esempi precedenti.

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale della password del proprietario della risorsa (ROPC) è un rischio per la sicurezza che deve essere evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

Risorse aggiuntive