Identity-Modellanpassung in ASP.NET Core
Von Arthur Vickers
ASP.NET Core Identity stellt ein Framework für die Verwaltung und Speicherung von Benutzerkonten in ASP.NET Core-Apps bereit. Identity wird Ihrem Projekt hinzugefügt, wenn als Authentifizierungsmechanismus Einzelne Benutzerkonten ausgewählt ist. Standardmäßig verwendet Identity ein Entity Framework Core-Datenmodell (EF Core). Dieser Artikel beschreibt, wie Sie das Identity-Modell anpassen können.
Identity- und EF Core-Migrationen
Bevor Sie das Modell untersuchen, ist es nützlich zu verstehen, wie Identity mithilfe von EF Core-Migrationen eine Datenbank erstellt und aktualisiert. Allgemein läuft der Prozess folgendermaßen ab:
- Definieren oder aktualisieren Sie ein Datenmodell im Code.
- Fügen Sie eine Migration hinzu, um dieses Modell in Änderungen zu übersetzen, die auf die Datenbank angewendet werden können.
- Überprüfen Sie, ob die Migration Ihre Absichten korrekt wiedergibt.
- Wenden Sie die Migration an, um die Datenbank zu aktualisieren, damit diese mit dem Modell synchron ist.
- Wiederholen Sie die Schritte 1 bis 4, um das Modell weiter zu verfeinern und die Datenbank synchron zu halten.
Nutzen Sie einen der folgenden Ansätze, um Migrationen hinzuzufügen und anzuwenden:
- Das Fenster Paket-Manager-Konsole, wenn Sie Visual Studio verwenden. Weitere Informationen finden Sie unter EF Core-Tools – Paket-Manager-Konsole.
- Die .NET CLI, wenn Sie die Befehlszeile verwenden. Weitere Informationen finden Sie unter EF Core-Tools – .NET Core CLI.
- Wenn die App ausgeführt wird, klicken Sie auf der Fehlerseite auf die Schaltfläche Migrationen anwenden.
ASP.NET Core bietet zur Entwicklungszeit einen Fehlerseitenhandler. Der Handler kann Migrationen anwenden, wenn die App ausgeführt wird. Produktions-Apps generieren in der Regel SQL-Skripts aus den Migrationen und stellen Datenbankänderungen im Rahmen einer kontrollierten Bereitstellung von Apps und Datenbanken bereit.
Wenn eine neue App mit Identity erstellt wird, sind die oben genannten Schritte 1 und 2 bereits abgeschlossen. Das heißt, das ursprüngliche Datenmodell ist bereits vorhanden, und die anfängliche Migration wurde dem Projekt hinzugefügt. Die anfängliche Migration muss noch auf die Datenbank angewendet werden. Die anfängliche Migration kann auf eine der folgenden Arten angewendet werden:
- Führen Sie in der Paket-Manager-Konsole
Update-Database
aus. - Führen Sie
dotnet ef database update
in einer Befehlsshell aus. - Wenn die App ausgeführt wird, klicken Sie auf der Fehlerseite auf die Schaltfläche Migrationen anwenden.
Wiederholen Sie die vorangegangenen Schritte, wenn Sie Änderungen am Modell vornehmen.
Das Identity-Modell
Entitätstypen
Das Identity-Modell besteht aus den folgenden Entitätstypen.
Entitätstyp | Beschreibung |
---|---|
User |
Repräsentiert eine*n Benutzer*in. |
Role |
Repräsentiert eine Rolle. |
UserClaim |
Repräsentiert einen Anspruch, den ein*e Benutzer*in besitzt. |
UserToken |
Repräsentiert ein Authentifizierungstoken für eine*n Benutzer*in. |
UserLogin |
Ordnet Benutzer*innen eine Anmeldung zu. |
RoleClaim |
Repräsentiert einen Anspruch, der allen Benutzer*innen innerhalb einer Rolle gewährt wird. |
UserRole |
Eine Joinentität, die Benutzer*innen und Rollen zuordnet. |
Entitätstypbeziehungen
Die Entitätstypen stehen in folgender Weise zueinander in Beziehung:
- Jeder
User
kann über vieleUserClaims
verfügen. - Jeder
User
kann über vieleUserLogins
verfügen. - Jeder
User
kann über vieleUserTokens
verfügen. - Jeder
Role
können vieleRoleClaims
zugeordnet sein. - Jedem
User
können vieleRoles
zugeordnet sein, und jedeRole
kann vielenUsers
zugeordnet sein. Dies ist eine m:n-Beziehung, die eine Jointabelle in der Datenbank erfordert. Die Jointabelle wird durch dieUserRole
-Entität repräsentiert.
Standardmäßige Modellkonfiguration
Identity definiert viele Kontextklassen, die von DbContext erben, um das Modell zu konfigurieren und zu verwenden. Diese Konfiguration erfolgt mithilfe der EF Core-Code First-Fluent-API in der OnModelCreating-Methode der Kontextklasse. Die Standardkonfiguration lautet:
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");
});
Generische Modelltypen
Identity definiert Common Language Runtime-Standardtypen (CLR) für jeden der oben aufgeführten Entitätstypen. Diese Typen sind alle mit dem Präfix Identity versehen:
IdentityUser
IdentityRole
IdentityUserClaim
IdentityUserToken
IdentityUserLogin
IdentityRoleClaim
IdentityUserRole
Anstatt diese Typen direkt zu verwenden, können die Typen als Basisklassen für die eigenen Typen der App verwendet werden. Die von Identity definierten DbContext
-Klassen sind generisch, sodass verschiedene CLR-Typen für einen oder mehrere der Entitätstypen im Modell verwendet werden können. Diese generischen Typen erlauben es auch, den Datentyp des User
-Primärschlüssels zu ändern.
Wenn Sie Identity mit Unterstützung für Rollen verwenden, sollten Sie eine IdentityDbContext-Klasse verwenden. Beispiel:
// 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>
Es ist auch möglich, Identity ohne Rollen (nur Ansprüche) zu verwenden. In diesem Fall sollte eine IdentityUserContext<TUser>-Klasse verwendet werden:
// 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>
{
}
Anpassen des Modells
Der Ausgangspunkt für die Modellanpassung ist die Ableitung aus dem passenden Kontexttyp. Weitere Informationen finden Sie im Abschnitt Generische Modelltypen. Dieser Kontexttyp wird üblicherweise als ApplicationDbContext
bezeichnet und über die ASP.NET Core-Vorlagen erstellt.
Der Kontext wird verwendet, um das Modell auf zwei Arten zu konfigurieren:
- Bereitstellung von Entitäts- und Schlüsseltypen für die generischen Typparameter
- Überschreiben von
OnModelCreating
, um die Zuordnung dieser Typen zu ändern
Beim Überschreiben von OnModelCreating
sollte zuerst base.OnModelCreating
und dann die überschreibende Konfiguration aufgerufen werden. EF Core umfasst in der Regel eine Last-One-Wins-Richtlinie für die Konfiguration. Wenn zum Beispiel die ToTable
-Methode für einen Entitätstyp das erste Mal mit einem Tabellennamen und später mit einem anderen Tabellennamen aufgerufen wird, wird der Tabellenname im zweiten Aufruf verwendet.
HINWEIS: Wenn DbContext
nicht von IdentityDbContext
abgeleitet ist, kann AddEntityFrameworkStores
nicht die richtigen POCO-Typen für TUserClaim
, TUserLogin
und TUserToken
ableiten. Wenn AddEntityFrameworkStores
nicht die richtigen POCO-Typen ableitet, besteht eine Problemumgehung darin, die richtigen Typen direkt über services.AddScoped<IUser/RoleStore<TUser>
und UserStore<...>>
hinzuzufügen.
Benutzerdefinierte Benutzerdaten
Benutzerdefinierte Benutzerdaten werden durch Vererbung von IdentityUser
unterstützt. Es ist üblich, diesen Typ ApplicationUser
zu nennen:
public class ApplicationUser : IdentityUser
{
public string CustomTag { get; set; }
}
Verwenden Sie den Typ ApplicationUser
als generisches Argument für den Kontext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
Es ist nicht erforderlich, OnModelCreating
in der Klasse ApplicationDbContext
zu überschreiben. EF Core ordnet die CustomTag
-Eigenschaft per Konvention zu. Die Datenbank muss jedoch aktualisiert werden, um eine neue CustomTag
-Spalte zu erstellen. Zum Erstellen der Spalte fügen Sie eine Migration hinzu und aktualisieren dann die Datenbank, wie in Identity- und EF Core-Migrationen beschrieben.
Aktualisieren Sie Pages/Shared/_LoginPartial.cshtml
, und ersetzen Sie IdentityUser
durch ApplicationUser
:
@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
Aktualisieren Sie Areas/Identity/IdentityHostingStartup.cs
oder Startup.ConfigureServices
, und ersetzen Sie IdentityUser
durch ApplicationUser
.
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Der Aufruf von AddDefaultIdentity entspricht dem folgenden Code:
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 wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von AddDefaultUI
. Weitere Informationen finden Sie unter:
- Identity-Gerüstbau
- Hinzufügen, Herunterladen und Löschen von benutzerdefinierten Benutzerdaten in Identity
Ändern des Primärschlüsseltyps
Eine Änderung des Datentyps der Primärschlüsselspalte nach Erstellung der Datenbank ist bei vielen Datenbanksystemen problematisch. Wenn Sie den Primärschlüssel ändern, müssen Sie die Tabelle normalerweise löschen und neu erstellen. Daher sollten die Schlüsseltypen bei der anfänglichen Migration angegeben werden, wenn die Datenbank erstellt wird.
Führen Sie diese Schritte aus, um den Primärschlüsseltyp zu ändern:
Wenn die Datenbank vor der Änderung des Primärschlüssels erstellt wurde, führen Sie
Drop-Database
(PMC) oderdotnet ef database drop
(.NET CLI) aus, um sie zu löschen.Nachdem Sie die Löschung der Datenbank bestätigt haben, entfernen Sie die ursprüngliche Migration mit
Remove-Migration
(PMC) oderdotnet ef migrations remove
(.NET CLI).Aktualisieren Sie die
ApplicationDbContext
-Klasse, um sie von IdentityDbContext<TUser,TRole,TKey> abzuleiten. Geben Sie den neuen Schlüsseltyp fürTKey
an. Wenn Sie zum Beispiel einenGuid
-Schlüsseltyp verwenden möchten:public class ApplicationDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Im vorangehenden Code müssen die generischen Klassen IdentityUser<TKey> und IdentityRole<TKey> angegeben werden, um den neuen Schlüsseltyp zu verwenden.
Startup.ConfigureServices
muss aktualisiert werden, um den generischen Benutzer zu verwenden:services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
Wenn eine benutzerdefinierte
ApplicationUser
-Klasse verwendet wird, aktualisieren Sie die Klasse, damit sie vonIdentityUser
erbt. Beispiel:using System; using Microsoft.AspNetCore.Identity; public class ApplicationUser : IdentityUser<Guid> { public string CustomTag { get; set; } }
Aktualisieren Sie
ApplicationDbContext
, um auf die benutzerdefinierte KlasseApplicationUser
zu verweisen:public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Registrieren Sie die benutzerdefinierte Datenbankkontextklasse, wenn Sie den Dienst Identity in
Startup.ConfigureServices
hinzufügen:services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
Der Datentyp des Primärschlüssels wird durch Analyse des DbContext-Objekts abgeleitet.
Identity wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von
AddDefaultUI
.Wenn eine benutzerdefinierte
ApplicationRole
-Klasse verwendet wird, aktualisieren Sie die Klasse, damit sie vonIdentityRole<TKey>
erbt. Beispiel:using System; using Microsoft.AspNetCore.Identity; public class ApplicationRole : IdentityRole<Guid> { public string Description { get; set; } }
Aktualisieren Sie
ApplicationDbContext
, um auf die benutzerdefinierte KlasseApplicationRole
zu verweisen. Die folgende Klasse verweist beispielsweise auf einen benutzerdefiniertenApplicationUser
und eine benutzerdefinierteApplicationRole
:using System; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
Registrieren Sie die benutzerdefinierte Datenbankkontextklasse, wenn Sie den Dienst Identity in
Startup.ConfigureServices
hinzufügen: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); }
Der Datentyp des Primärschlüssels wird durch Analyse des DbContext-Objekts abgeleitet.
Identity wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von
AddDefaultUI
.
Hinzufügen von Navigationseigenschaften
Das Ändern der Modellkonfiguration im Hinblick auf Beziehungen kann schwieriger sein als andere Änderungen. Achten Sie darauf, die vorhandenen Beziehungen zu ersetzen, anstatt neue, zusätzliche Beziehungen zu schaffen. Insbesondere muss die geänderte Beziehung die gleiche Fremdschlüsseigenschaft aufweisen wie die bestehende Beziehung. Beispielsweise wird die Beziehung zwischen Users
und UserClaims
standardmäßig wie folgt angegeben:
builder.Entity<TUser>(b =>
{
// Each User can have many UserClaims
b.HasMany<TUserClaim>()
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
});
Der Fremdschlüssel für diese Beziehung wird als UserClaim.UserId
-Eigenschaft angegeben. HasMany
und WithOne
werden ohne Argumente aufgerufen, um die Beziehung ohne Navigationseigenschaften zu erstellen.
Fügen Sie eine Navigationseigenschaft zu ApplicationUser
hinzu, die es ermöglicht, von Benutzer*innen auf zugehörige UserClaims
zu verweisen:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}
Der TKey
für IdentityUserClaim<TKey>
ist der Typ, der für den Primärschlüssel der Benutzer*innen angegeben wird. In diesem Fall weist TKey
den Typ string
auf, weil die Standardwerte verwendet werden. Dies ist nicht der Primärschlüsseltyp für den UserClaim
-Entitätstyp.
Da die Navigationseigenschaft nun vorhanden ist, muss sie in OnModelCreating
konfiguriert werden:
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();
});
}
}
Beachten Sie, dass die Beziehung genau wie zuvor konfiguriert ist, nur mit einer Navigationseigenschaft, die im Aufruf von HasMany
angegeben wird.
Die Navigationseigenschaften sind nur im EF-Modell vorhanden, nicht in der Datenbank. Da sich der Fremdschlüssel für die Beziehung nicht geändert hat, muss bei dieser Art von Modelländerung die Datenbank nicht aktualisiert werden. Sie können dies überprüfen, indem Sie nach der Änderung eine Migration hinzufügen. Die Methoden Up
und Down
sind leer.
Hinzufügen aller Benutzernavigationseigenschaften
Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel unidirektionale Navigationseigenschaften für alle Beziehungen für Benutzer*innen:
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();
});
}
}
Hinzufügen von Benutzer- und Rollennavigationseigenschaften
Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel Navigationseigenschaften für alle Beziehungen für Benutzer*innen und Rollen:
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();
});
}
}
Hinweise:
- Dieses Beispiel umfasst auch die Joinentität
UserRole
, die zum Navigieren durch die m:n-Beziehung zwischen Benutzer*innen und Rollen benötigt wird. - Denken Sie daran, die Typen der Navigationseigenschaften so zu ändern, dass sie widerspiegeln, dass jetzt
Application{...}
-Typen anstelle vonIdentity{...}
-Typen verwendet werden. - Denken Sie daran,
Application{...}
in der generischen Definition vonApplicationContext
zu verwenden.
Hinzufügen aller Navigationseigenschaften
Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel Navigationseigenschaften für alle Beziehungen für alle Entitätstypen:
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();
});
}
}
Verwenden zusammengesetzter Schlüssel
In den vorangegangenen Abschnitten wurde gezeigt, wie Sie den im Identity-Modell verwendeten Schlüsseltyp ändern können. Das Ändern des Identity-Schlüsselmodells zur Verwendung von zusammengesetzten Schlüsseln wird weder unterstützt noch empfohlen. Wenn Sie einen zusammengesetzten Schlüssel mit Identity verwenden, müssen Sie die Art und Weise der Interaktion zwischen dem Identity-Manager-Code und dem Modell ändern. Diese Anpassung liegt außerhalb des Rahmens dieses Dokuments.
Ändern von Tabellen-/Spaltennamen und Facetten
Rufen Sie base.OnModelCreating
auf, um die Namen von Tabellen und Spalten zu ändern. Fügen Sie dann eine Konfiguration hinzu, um einen beliebigen der Standardwerte zu überschreiben. So ändern Sie beispielsweise den Namen aller Identity-Tabellen:
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");
});
}
In diesen Beispielen werden die Identity-Standardtypen verwendet. Wenn Sie einen App-Typ wie ApplicationUser
verwenden, konfigurieren Sie diesen Typ anstelle des Standardtyps.
Im folgenden Beispiel werden einige Spaltennamen geändert:
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");
});
}
Einige Typen für Datenbankspalten können mit bestimmten Facetten konfiguriert werden (z. B. mit der maximal zulässige string
-Länge). Im folgenden Beispiel werden die maximalen Spaltenlängen für mehrere string
-Eigenschaften im Modell festgelegt:
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);
});
}
Zuordnung zu einem anderen Schema
Schemas können sich bei verschiedenen Datenbankanbietern unterschiedlich verhalten. Bei SQL Server werden standardmäßig alle Tabellen im Schema dbo erstellt. Die Tabellen können in einem anderen Schema erstellt werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("notdbo");
}
Lazy Loading
In diesem Abschnitt wird die Unterstützung für Lazy-Loading-Proxys (verzögertes Laden) im Identity-Modell hinzugefügt. Das Lazy Loading ist nützlich, da es die Verwendung von Navigationseigenschaften ermöglicht, ohne vorher sicherzustellen, dass sie geladen sind.
Entitätstypen können auf verschiedene Weise vorbereitet werden, um das Lazy Loading zu unterstützen, wie in der EF Core-Dokumentation beschrieben. Der Einfachheit halber verwenden Sie Lazy-Loading-Proxys, was Folgendes voraussetzt:
- Installation des Pakets Microsoft.EntityFrameworkCore.Proxies
- Einen Aufruf von UseLazyLoadingProxies innerhalb von AddDbContext
- Öffentliche Entitätstypen mit
public virtual
-Navigationseigenschaften
Das folgende Beispiel veranschaulicht den Aufruf von UseLazyLoadingProxies
in Startup.ConfigureServices
:
services
.AddDbContext<ApplicationDbContext>(
b => b.UseSqlServer(connectionString)
.UseLazyLoadingProxies())
.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
In den vorangegangenen Beispielen finden Sie Anleitungen zum Hinzufügen von Navigationseigenschaften zu den Entitätstypen.
Warnung
In diesem Artikel wird die Verwendung von Verbindungszeichenfolge gezeigt. Bei einer lokalen Datenbank muss der Benutzer nicht authentifiziert werden, aber in der Produktion enthalten Verbindungszeichenfolge manchmal ein Kennwort für die Authentifizierung. Ein Ressourcenbesitzer-Kennwortanmeldeinformation (ROPC) ist ein Sicherheitsrisiko, das in Produktionsdatenbanken vermieden werden sollte. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für Apps, die für Test- oder Produktionsumgebungen bereitgestellt werden, finden Sie unter Sichere Authentifizierungsflüsse.