Microsoft.AspNet.Identity in bestehender Anwendung einsetzen

Glaubt man tausenden Quellen im Internet, sollte es eigentlich kein Problem sein, die neuen Identity-Features in bestehende Anwendungen zu integrieren. Sieht man aber genauer hin, merkt man, dass der überwiegende Teil der Beiträge sich das Leben ganz leicht macht. Ein wenig Code-First hier und ein wenig Magie hier und scheinbar läuft alles. Gleich vorweg: So einfach ist es nicht! Damit es trotzdem klappt, dieser Artikel.

!!! VERALTET !!!

Die neue Version des Screencasts steht unter Microsoft.AspNet.Identity in bestehender Anwendung einsetzen Teil 2 oder direkt unten bereit.

Dieser Artikel beschreibt eine Vorgehensweise, die unter dem Release von ASP.NET Identity 2.0 nicht mehr ohne weiteres anwendbar ist. Es wird aktuell an einem Folgeartikel gearbeitet.

Vorweg

Alle hier beschriebenen Schritte sind erst mit der Version 2.0.0-alpha (Prerelease) umsetzbar und sollten daher in keinem Fall bereits in produktiven Umgebungen eingesetzt werden. Als Vorbereitung für eine spätere Migration kann es aber bereits herangezogen werden.

Screencast

Für die Eiligen oder die Englischen hier wieder der Cast (Sorry für den Ton und die hakeligen Erläuterungen ;-)):

Der neue Screencast:

Der alte Screencast, der die Beta benutz hat:

Das Problem

Fangen wir am besten wieder mit der Problemstellung an. Nehmen wir an, wir haben eine MVC-Seite am laufen, die wir irgendwann einmal mit MVC 2 oder MVC 3 erstellt haben. Wir haben damals die gut gemeinten Ratschläge genutzt und die Authentifizierungs-Verrenkungen von Microsoft verwendet. Da wir hier im Business-Umfeld arbeiten, kam Code-First irgendwie nicht in Frage und wir haben einfach eine SQL-Server-Datenbank aufgebaut. Dort gab es dann eine User-Tabelle und eine Rollen-Tabelle usw.

Diese Tabellen haben wir per Entity Framework geladen. Dann haben wir uns eine eigene Klasse, die von MembershipProvider erbt, gebaut und diese dann mühsam an unser Model geknüpft. Außerdem haben wir neben den dort vorgesehenen Standard-Eigenschaften, wie PasswordQuestion und PasswortAnswer auch noch ein paar weitere Metadaten, wie z.B. eine CustomerNumber in unserem Modell. Auch der Prozess ist von uns angepasst, sodass erstmal eine Registrierungs-Mail rausgeht, in der ein neuer User einen gehashten Link anklicken muss, damit wir ihn „scharf“ schalten und dabei wissen, dass er eine Mail-Adresse hat. (Manche haben außerdem noch Captchas und weiß der Fuchs was eingebaut).

Jedenfalls läuft alles und wir lesen einen Artikel über MVC 5 und Authentifizierung. Eine Auswahl:

Mit jedem Artikel wird man ein wenig schlauer und versteht den Ansatz. Der an zweiter Stelle gelistete Beitrag geht wie ich finde sehr kritisch mit dem Thema um, findet aber auch gute Ansätze. Und seien wir mal ehrlich: Wer wirklich schon einmal einen eigenen MembershipProvider implementieren musste, dem hat das damals garantiert keinen Spaß gemacht und der sollte zumindest ein ungutes Gefühl haben, weil das ganze noch online ist.

Das eigentliche Problem aber ist, dass ASP.NET Identity eine eigene Datenbankstruktur verlangt, damit die Funktionalität läuft. Offensichtlich stand bei der Entwicklung des Frameworks leider der Code-First-Ansatz im Mittelpunkt. Da dies in meinen Szenarien nicht in Frage kommt, habe ich nach Alternativen gesucht.

Warum ASP.NET Identity?

Man könnte sich ja trotzdem entsprann zurück lehnen und Identity als eine weitere API-Kuriosität abtun. Das würde ich allerdings nicht empfehlen. Moderne Websites müssen mittlerweile einen gewissen Mindeststandard einhalten, um nicht gleich beim Klick auf „Ja ich will mitmachen hier!“ durchs Raster der Benutzer zu fallen. Ich rede hier nicht von Intranetanwendungen, sonder solchen Seiten, die wirklich externe Benutzer locken sollen. Unsere Kunden wünschen sich fast selbstverständlich einen Login per Google oder Facebook, weil sie das auf so vielen anderen Seiten auch können. Ignorieren bringt einen also nicht weiter.

Wenn man sich nun aber mal genauer ansieht, wie komplex und verworren die Implementierungen der einzelnen O-Standards (OAuth, OpenId und was weiß ich nicht alles) inzwischen geworden sind, wird einem schnell klar, dass eine sichere und vor allem sich automatisch an die Bedürfnisse anpassende Einbindung aufwendig ist. Man muss die Schnittstellen nicht nur richtig bedienen, sondern bei Verfügbarkeit einer neuen Version relativ schnell nachziehen.

Besser ist es da dann doch, sich auf die Basis-Implementierungen im eigenen Framework zu beziehen und dann auch upgradefähig zu bleiben. Das soll jetzt nicht heißen, dass beim Wechsel auf ASP.NET Identity plötzlich all dies gesichert sei. Lässt man den Wechsel sein, ist es aber auf jeden Fall ausgeschlossen!

Der schöne Nebeneffekt an Identity ist, dass die datenbankseitigen Operationen bereits abgedeckt sind. EF ist eingebunden und mit Basisklassen, wie UserManager oder IdentityUser steht bereits vieles an komplexer Logik in diesem Bereich zur Verfügung.

Mein Ziel

Ich möchte also nun die tollen neuen Features von ASP.NET Identity in meine bestehende DB-First-Applikation unterbringen. Mein für dieses Beispiel konstruiertes Datenbankmodell sieht so aus (ich habe nicht die 1:1-Version des SqlServerMembershipProvider benutzt):

Abb. 1: Datenbankstruktur
Abb. 1: Datenbankstruktur

Ich möchte natürlich im Minimum folgendes erreichen:

  1. Meine bestehenden Benutzerdaten sollen vollständig erhalten bleiben.
  2. Die Referenz zwischen UserAction und User soll erhalten bleiben.
  3. Ich möchte meine Grundsätze beim Datenbankdesign anwenden.

Der 3. Punkt mag etwas merkwürdig klingen, der Hintergrund ist aber, dass ASP.NET Identity im Standard einige Konventionen einsetzt, die mir nicht passen. So ist z.B. die Klasse IdentityUser, die in meinem Beispiel durch die User-Tabelle zu repräsentieren wäre, so definiert, dass sie folgende Eigenschaften aufweist:

  • Id (string)
  • UserName (string)
  • Email (string)
  • PasswordHash (string)
  • SecurityStamp (string)
  • IsConfirmed (bool)
  • Roles (ICollection<IdentityUserRole<string>>)
  • Claims (ICollection<IdentityUserClaim<string>>)
  • Logins (ICollection<IdentityUserLogin<string>>)

Dies ist also der Standard. Für alle Eigenschaften erwartet nun Identity, dass es in einer Tabelle namens „AspNetUsers“ eine entsprechende Spalte gibt. Wie wir auch sehen, ist nichts zu sehen, von PasswordQuestion, ActivationToken usw. Für die letzten 3 Eigenschaften brauche ich zusätzliche Tabellen. Zu guter letzt möchte ich, dass meine per AutoIncrement erzeugten bigint-Ids anstelle der GUID verwendet werden. Also los!

Änderungen an der aktuellen Datenbankstruktur

Zunächst möchte ich meine bestehende User-Tabelle anpassen, damit die von Identity geforderten Minimal-Anforderungen erfüllt sind. Das folgende T-SQL-Script erledigt das auf meiner SQL-Server-Datenbank:

EXECUTE sp_rename N'dbo.[User].Login', N'Tmp_UserName_4', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.[User].EMail', N'Tmp_Email_5', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.[User].Password', N'Tmp_PasswordHash_6', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.[User].Tmp_UserName_4', N'UserName', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.[User].Tmp_Email_5', N'Email', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.[User].Tmp_PasswordHash_6', N'PasswordHash', 'COLUMN'
GO
ALTER TABLE dbo.[User] ADD
SecurityStamp nvarchar(254) NULL,
IsConfirmed bit NOT NULL CONSTRAINT DF_User_IsConfirmed DEFAULT 0
GO

Zusammenfassend also dann erstmal folgende neue Struktur:

Abb. 2: User-Tabelle geändert
Abb. 2: User-Tabelle geändert

Ich habe neue Felder gelb und geänderte in orange hervorgehoben.

Achtung: Ab sofort funktioniert das alte Membership-Konstruktut natürlich nicht mehr!

Noch ist die User-Tabelle noch nicht fertig. ASP.NET Identity führt einen Mechanismus ein, der sicherstellen soll, dass Änderungen an wichtigen Daten eines Benutzerdatensatzes schnell erkannt werden können. Zu diesem Zweck gibt es das Feld SecurityStamp. Derzeit beinhaltet es allerdings nur NULL-Werte, was beim Login-Versuch der User unweigerlich zum Absturz führen würde. Daher noch folgendes T-SQL:

UPDATE [dbo].[User]
SET SecurityStamp = 'IMPORT'

Die Benutzertabelle ist nun erst einmal fertig umgestellt. ASP.NET Identity benötigt allerdings noch mehr Tabellen, um sauber zu funktionieren. Hier das Script, das nun diese Tabellen erzeugt:

CREATE TABLE [dbo].[Role] (
    [Id]   BIGINT IDENTITY (1, 1) NOT NULL,
    [Name] NVARCHAR (MAX) NOT NULL,
    CONSTRAINT [PK_Role] PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE TABLE [dbo].[UserRole] (
    [UserId] BIGINT NOT NULL,
    [RoleId] BIGINT NOT NULL,
    CONSTRAINT [PK_UserRole] PRIMARY KEY CLUSTERED ([UserId] ASC, [RoleId] ASC),
    CONSTRAINT [FK_UserRole_Role] FOREIGN KEY ([RoleId]) REFERENCES [dbo].[Role] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_UserRole_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_RoleId]
    ON [dbo].[UserRole]([RoleId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_UserId]
    ON [dbo].[UserRole]([UserId] ASC);
GO
CREATE TABLE [dbo].[UserLogin] (
    [UserId]        BIGINT NOT NULL,
    [LoginProvider] NVARCHAR (128) NOT NULL,
    [ProviderKey]   NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_UserLogin] PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [ProviderKey] ASC),
    CONSTRAINT [FK_UserLogin_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_UserId]
    ON [dbo].[UserLogin]([UserId] ASC);
GO
CREATE TABLE [dbo].[UserClaim] (
    [Id]         BIGINT IDENTITY (1, 1) NOT NULL,
    [UserId]     BIGINT NOT NULL,
    [ClaimType]  NVARCHAR (MAX) NULL,
    [ClaimValue] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_UserClaim] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_UserClaim_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_User_Id]
    ON [dbo].[UserClaim]([UserId] ASC);
GO

Dieses Script ist eine Anpassung der Version von suhasj. Sein Script erzeugt eine extrem ausgeprägte User-Tabelle und bezieht immer noch nicht bigint als Typ für Ids ein. Außerdem will mir das Standard-Naming von Identity nicht gefallen und in den seltensten Fällen wird es mit den Konventionen in einer Firma übereinstimmen, die Tabellen mit „AspNet“ beginnen zu lassen. Nebenbei bemerkt dürfte das Datenbankteam von MS hier am allerwenigsten „amused“ sein, denn die Benennung widerspricht komplett allem, was man von AdventureWorks und Contoso gewohnt ist. Aber egal, wir haben ja einen Weg gefunden, das anzupassen.

Die Datenbankstruktur sieht nun ca. so aus:

Abb. 3: Finales Modell
Abb. 3: Finales Modell

Neue Tabellen sind mit einem blauen Kasten, geänderte mit einem organgen und nicht geänderte in gelb gezeigt. Die Datenbank ist nun fertig.

Vorbereitungen des Projektes

Es gilt nun, die Änderungen auch im Programmcode zu nutzen und ASP.NET Identity einzubinden. Der erste Schritt zum Glück besteht wie so oft in der Installation von Nuget-Packages. Wir benötigen:

Abb. 4: Nuget
Abb. 4: Nuget

Ich möchte an dieser Stelle nochmals den Prerelease-Status der Pakete zum aktuellen Zeitpunkt betonen!!! Warum aber muss es denn unbedingt Version 2 sein? Ganz einfach deshalb, weil Identity erst ab Version 2 generische Versionen der benötigten Klassen anbietet, die es uns u.a. erlauben, festzulegen, dass die Keys der Klassen (z.B. von User) nicht string, sondern bigint usw. sein sollen. Das ist wichtig, damit wir dem unterliegenden EF später mitteilen können, dass er bitte keine GUIDs in der DB ablegen soll, was ja ein Breaking Change für unsere bisherigen Daten wäre. Details dazu kann man hier nachlesen.

Das Projekt ist nun bereit, auf Identity umgestellt zu werden. Man muss nun noch in der Web.config sicherstellen, dass ggf. noch vorhandene Membership-Einträge entfernt werden und man sollte auch in der Startup.cs im Root-Folder sicherstellen, dass Identity initialisert wird:

[assembly: OwinStartupAttribute(typeof(IdentiySample.Startup))]
namespace IdentiySample
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

Das Attribut sorgt also letztlich dafür, dass der Code automatisch beim App-Rampup ausgeführt wird. Die Details regelt meist die Startup.Auth.cs im App_Start-Ordner.

Die meiste Arbeit wird uns in diesem Prozess der AccountController machen, der ja letztlich im Standard-Template fast die gesamten Features anbietet. Natürlich hängt alles im folgenden gesagte letztlich von der Seite ab, die man vor sich hat.

Ich kann nur empfehlen, ein frisches MVC5-Projekt an eine Kopie der bestehenden Datenbank zu koppeln, die Implementierung des AccountControllers laut der hier gezeigten Vorghensweise anzupassen und dann die entsprechenden Korrekturen im eigenen Projekt in einem Rutsch durchzuführen.

Lassen wir aber zunächst den Account-Controller außen vor und kümmern uns darum, dass unsere neue Datenbankstruktur irgendwie duch Identity genutzt wird.

Models generieren

Im Zentrum von Identity steht letztlich der UserManager. Hierbei handelt es sich erst einmal um eine vollständige Implementierung, die man per Properties anpassen oder im Bedarfsfall über Vererbung erweitern kann. Im Standard-Template von MVC 5 wird dieser UserManager wie folgt umgesetzt:

[Authorize]
public class AccountController : Controller
{
    public AccountController()
        : this(new UserManager(new UserStore(new ApplicationDbContext())))
    {
    }

    public AccountController(UserManager userManager)
    {
        UserManager = userManager;
    }

    public UserManager UserManager { get; private set; }

    // ....

Das bringt uns nun aber überhaupt nicht weiter, weil der Standard-Identity-User wie bereits gesagt beispielsweise die vermaledeite Id als GUID ablegen möchte. Was nun?

Zunächst einmal zu dem ApplicationDbContext. Ich erstelle mir eine entsprechende Klasse (z.B. im Model-Ordner), weil es diesen noch gar nicht gibt. Wir haben vielleicht (hoffentlich nicht) ein EF-Model im MVC-Projekt, aber das wollen wir nicht benutzen. Wir erstellen uns den ApplicationDbContext und werden diesen ausschließlich für Identity nutzen. Alle anderen Zugriffe bleiben erhalten. Also dann! Der Standard sagt:

public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}

Hierbei soll „DefaultConnection“ der Schlüssel der Verbindungszeichenfolge aus der Web.config sein, unter der EF das Model ansprechen kann. Das Problem ist aber das Erben von IdentityDbContext. Wir wollen das ändern.

Erstmal die Entitäten schaffen

Die Änderung am Kontext erfordert, dass wir zunächst einmal pro Tabelle unserer neuen Struktur eine Klasse anlegen. Hier erstmal der Code (der Einfachheit halber untereinander in einer Datei):

public class MyUser : IdentityUser<long, MyLogin, MyUserRole, MyClaim>
{

    public string ActivationToken { get; set; }
    public string PasswordAnswer { get; set; }
    public string PasswordQuestion { get; set; }

}

public class MyUserRole : IdentityUserRole { }

public class MyRole : IdentityRole<long, MyUserRole> { }

public class MyClaim : IdentityUserClaim { }

public class MyLogin : IdentityUserLogin { }

Kurz durchatmen! Was haben wir denn hier. Wir sehen 5 Klassen und wir wissen ja noch, dass wir 5 Tabellen in der Datenbank haben, die sich mit der Benutzerverwaltung direkt auseinander setzen. Richtig! Dies sind die Entities dafür. Keine Angst, wir machen hier kein Code-First, sondern wir erstellen erstmal einfach nur Klassen. Jede Klasse erbt dabei zunächst einmal von der dafür vorgesehenen Implementierung aus Microsoft.AspNet.Identity.EntityFramework. Mit einer kleinen Nuance allerdings, die uns erst das Prerelease beschert. Wir geben bei allen Klassen dem generischen Parameter TKey „long“ mit, weil wir in der Datenbank halt bigint als Key verwenden wollen. Außerdem fügen wir natürlich die Eigenschaften hinzu, die die Standard-Implementierungen nicht mitbringen.

Etwas komplizierter ist dies bei den beiden Klassen MyUser und MyRole, weil diese zusätzlich noch jeweils die anderen Typen als generischen Parameter benötigen. Letztlich ist das hier nicht wirklich aufregend. Wir haben jetzt erstmal einfach nur irgendwelche Klassen.

Zurück zum IdentityDbContext

Jetzt sind die Vorarbeiten dafür geschaffen, unseren ApplicationDbContext umzustricken. Die Definition der Klasse wird nun wie folgt geändert (vgl. Listing 6):

public class ApplicationDbContext : IdentityDbContext<MyUser, MyRole, long, MyLogin, MyUserRole, MyClaim>
{
    public ApplicationDbContext() : base("DefaultConnection") {}
}

Jetzt haben wir dem IdentityDbContext mitgeteilt, dass wir unsere Entitäten benutzen wollen und dass er gefälligst immer anzunehmen hat, dass für Primary Keys „long“ (bigint) verwendet werden soll. Natürlich würde das Programm sofort abstürzen, weil EF das Problem hätte, dass er z.B. für die Entität MyUser nach einer Tabelle „AspNetUsers“ suchen würde. Diese Zickereien müssen wir dem EF-Mapper abgewöhnen. Ein Override des ApplicationDbContext schafft Abhilfe:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    // Map Entities to their tables.
    modelBuilder.Entity<MyUser>().ToTable("User");
    modelBuilder.Entity<MyRole>().ToTable("Role");
    modelBuilder.Entity<MyClaim>().ToTable("UserClaim");
    modelBuilder.Entity<MyLogin>().ToTable("UserLogin");
    // Set AutoIncrement-Properties
    modelBuilder.Entity<MyUser>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    modelBuilder.Entity<MyClaim>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    modelBuilder.Entity<MyRole>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}

Die ersten 5 ToTable-Aufrufe machen dem Context klar, dass wir die Tabellen anders nennen, als unsere Kumpels in Redmond. Die letzten 3 Aufrufe stellen sicher, dass EF begreift, dass die Id-Spalten der betreffenden Tabellen nicht vom Client, sondern per Identity vom SQL Server gesetzt werden.

Zurück zum AccountController

Jetzt „meckert“ der AccountController mit uns, sollten wir ihn bereits anhand Listing 5 mit einem UserManager versehen haben. Also tragen wir unsere letzten Änderungen auch hier fort:

public AccountController() : this(new UserManager<MyUser, long>(new UserStore<MyUser, MyRole, long, MyLogin, MyUserRole, MyClaim>(new ApplicationDbContext())))
{
}

public AccountController(UserManager<MyUser, long> userManager)
{
    UserManager = userManager;
}

public UserManager<MyUser, long> UserManager { get; private set; }

Das ist jetzt etwas schwer erkennbar, daher ein paar Worte dazu. Wir sehen von oben nach unten 2 Konstruktoren und eine Property. Die Property ist relativ klar. Wir benutzen halt nun nicht mehr IdentityUser sondern unseren MyUser und müssen dem Manager dann auch long als Typ für die Keys mitgeben.

Der obere Konstruktor ruft den unteren auf. Er ist gleichzeitig der Standard-Konstruktor und somit der, der durch die MVC-Pipeline gerufen wird. Wer also mit DI-Containern arbeitet (hoffentlich viele), sollte hier weitere Eingriffe vornehmen. Der obere Konstruktor instantiiert also den eigentlichen UserManager und übergibt ihm einen neuen ApplicationDbContext.

Damit wäre die Umstellung eigentlich abgeschlossen. Wir könnten uns aber leider immer noch nicht anmelden – zumindest der Wahrscheinlichkeit nach.

Hash-Probleme

Unsere bisherige Datenbank hatte ja irgendwo im Membership-Provider wahrscheinlich eigene Logik, um die Kennwörter der Benutzer zu verwalten. Natürlich bietet auch ASP.NET Identity hier Logik an, aber halt eigene. Zum Glück ist hier nicht so viel Arbeit erforderlich, wie bei MembershipProvider.

Der UserManager verfügt über eine Eigenschaft PasswordHasher. Sie nimmt eine Instanz vom Typ IPasswordHasher entgegen und die wird durch die Basisklasse PasswordHasher auch schon bereit gestellt. Wir erben nun einfach von dieser Klasse:

public class MyPasswordHasher : PasswordHasher
{
    public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
    {
        return hashedPassword.Equals(HashPassword(providedPassword)) ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
    }

    public override string HashPassword(string password)
    {
        // Meine bisherige Logik, um auch einem password im Klartext einen Hash zu bauen
    }
}

In Listing 11 habe ich die Stelle mit einem Kommentar versehen, an der die bisherige Logik zum erzeugen der Hashes aufgerufen werden kann.

Sobald der Hasher selbst funktioniert, kann man den UserManager entsprechend anweisen, ihn auch zu benutzen. Ich wähle dazu gleich den Konstruktor von AccountController aus Listing 10 und ändere ihn ab:

public AccountController() : this(new UserManager<MyUser, long>(new UserStore<MyUser, MyRole, long, MyLogin, MyUserRole, MyClaim>(new ApplicationDbContext()))
{
    PasswordHasher = new MyPasswordHasher()
})
{
}

Build-Probleme beheben

Fast geschafft! Versucht man nun einen Build zu starten, hagelt es Fehlermeldungen, die vor allem aus den Implementierungen des AccountController stammen (Wenn man meiner Empfehlung von oben gefolgt ist, ein Testprojekt mit MVC-5-Standard-Template zu benutzen). Ich erhalte einen Haufen Konvertierungsfehler, weil es einen Haufen Zeilen wie:

var result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey));

Eine etwas krude Methode, dies abzustellen, ist:

var result = await UserManager.RemoveLoginAsync(long.Parse(User.Identity.GetUserId()), new UserLoginInfo(loginProvider, providerKey));

Es geht halt einfach darum, dass unser Key nun vom Typ long ist, der UserManager aber leider nicht automatisch konvertieren kann. GetUserId ist übrigens eine Extension-Methode auf IIdentity, falls jemand Angst haben sollte, dass hier immer der Loginname erscheint).

In meinem Fall konnte ich nun alle Features von ASP.NET Identity mit meiner „alten“ Datenbank nutzen. Ich habe natürlich den ganzen Prozess vorher in einer Testumgebung durchgespielt und ich warte jetzt, bis Identity 2.0 als Stable erscheint. Etwas ruhiger war ich aber schon, als ich einfach in Startup.Auth.cs UseGoogleAuthentication auf aufrief und mich mit meinem Google+-Account anmelden konnte.

Fazit

Ich verstehe nicht, warum so viele unrealistische Samples im Internet kursieren und der Use-Case bestehender Anwendungen immer wieder unter den Tisch fällt. Code-First ist sowieso mein Lieblings-Feind und daher war ich einigermaßen froh, dass es Lösungen für meine Probleme gibt. Das Thema scheint so undurchsichtig zu sein, dass man selbst auf stackoverflow keine vernünftigen Hinweise dazu bekommt. Ein Grund mehr, hier darüber zu schreiben.

Bei dem hier vorgestellten Ansatz muss man ein wenig aufpassen, was die EF-Einbindung der Modelle angeht. Ein Vorteil, der aber auch verwirren kann, ist, dass z.B. die Entity User einmal von Identity gemapped wird und dann aber auch durch die „normalen“ eigenen EF-Modelle. Man sollte jetzt logischerweise keine Änderungen mehr über letztere vornehmen, sondern zum Zugriff auf alles, was DB-seitig mit Benutzern zu tun hat den ApplicationDbContext benutzen.

18 Antworten auf „Microsoft.AspNet.Identity in bestehender Anwendung einsetzen“

  1. I’m getting with the final version:
    Invalid column name ‚EmailConfirmed‘.
    Invalid column name ‚PhoneNumber‘.
    Invalid column name ‚PhoneNumberConfirmed‘.
    Invalid column name ‚TwoFactorEnabled‘.
    exceptions among others.

    I’m really hope you can help me.

    Thanks.

  2. Me too getting errors of ‚invalid column name‘ when registering new user. Funny, because tables I used are just a copy of default tables. I’ve just renamed them, put them into another schema and pretend they are my own tables to whom I want to map Identity model.

    What else I must configure in final (2.0.1) Identity version for it to work?

  3. Hi all of you: I’ll just post a follow up soon explaining what is necessary regarding the final version. In short: Watch out the EF-exceptions you get when creating a new login and add those columns to your table.

  4. Treyarch has done a good job of keeping the game
    free of glitches that give you an advantage, but there are still a few things you
    can pull off. Any kind of exact date will probably be omitted,
    although In my opinion the game probably will center on 1960s
    and maybe 1970’s starting from Bay of Pigs to Vietnam not to mention Watergate.
    Video games are becoming more and more like interactive movies, with gamers not deciding moves
    or outcomes but merely deciding which weapons to use and using them.

  5. Dear Sprinter,
    The Screen cast was very informative and useful. But I am not able to find the source code link. Could you please send me the sample project at the earliest so that I can follow the same in my application. My email ID is damo777_143@yahoo.com.

    Thanks and regards,
    Damo…

  6. Hi there,
    Great example.
    I have a problem tough. The Table UserRole (formed by UserId & RoleId, with FKs) gets modified by EF adding 2 extra columns: MyUSer_Id & MyRoleId, which, indeed, are FKs. So I get 4 columns, UserId & RoleId (the PK) and the MyUser_Id & MyRole_Id (FKs).
    How can I avoid this?
    Thanks!

  7. I would like to get some more information related to roles and user – role management .

    Please help us to understand this also since roles handling is also very important . Hoping best and thank you for making and sharing this approach.

  8. HI, I WOULD like to insert duplicate user name is it possible….
    and second thing i would like to pass three parameter into find method on login page… like username,password,locationName this is example… is it possible or not..
    i download your code but its not compiling properly.

  9. I personally want to thank you for this article and videos. I’ve been looking for a tutorial on this matter for 2 weeks.
    I was really lucky to find this.

    Have a good day sir!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

This site uses Akismet to reduce spam. Learn how your comment data is processed.