Raymond Raymond

Migrate from System.Web.Providers to ASP.NET Identity 1.0 and then to 2.1.0

event 2015-01-31 visibility 3,322 comment 1 insights toc
more_vert
insights Stats

Migration from simple ASP.NET membership provider to ASP.NET Identity is not straightforward. In order to migrate smoothly, we can firstly upgrade to Identity 1.0 and then to the latest version 2.0.

Reference

Migrating an Existing Website from SQL Membership to ASP.NET Identity

Updating ASP.NET applications from ASP.NET Identity 1.0 to 2.0.0-alpha1

All the details will be covered in the above two posts. I will only provide the scripts and solutions to some problems you may encounter.

Step 1: Script to Update Database Schema to Identity 1.0

IF OBJECT_ID('AspNetUserRoles', 'U') IS NOT NULL
BEGIN
DROP TABLE AspNetUserRoles;
END

IF OBJECT_ID('AspNetUserClaims', 'U') IS NOT NULL
BEGIN
DROP TABLE AspNetUserClaims;
END

IF OBJECT_ID('AspNetUserLogins', 'U') IS NOT NULL
BEGIN
DROP TABLE AspNetUserLogins;
END

IF OBJECT_ID('AspNetRoles', 'U') IS NOT NULL
BEGIN
DROP TABLE AspNetRoles;
END

IF OBJECT_ID('AspNetUsers', 'U') IS NOT NULL
BEGIN
DROP TABLE AspNetUsers;
END

CREATE TABLE [dbo].[AspNetUsers] (
    [Id]            NVARCHAR (128) NOT NULL,
    [UserName]      NVARCHAR (MAX) NULL,
    [PasswordHash]  NVARCHAR (MAX) NULL,
    [SecurityStamp] NVARCHAR (MAX) NULL,
    [Discriminator] NVARCHAR (128) NOT NULL,
    [ApplicationId]                          UNIQUEIDENTIFIER NOT NULL,
    [LegacyPasswordHash]  NVARCHAR (MAX) NULL,
    [LoweredUserName]  NVARCHAR (256)   NOT NULL,
    [MobileAlias]      NVARCHAR (16)    DEFAULT (NULL) NULL,
    [IsAnonymous]      BIT              DEFAULT ((0)) NOT NULL,
    [LastActivityDate] DATETIME2         NOT NULL,
    [MobilePIN]                              NVARCHAR (16)    NULL,
    [Email]                                  NVARCHAR (256)   NULL,
    [LoweredEmail]                           NVARCHAR (256)   NULL,
    [PasswordQuestion]                       NVARCHAR (256)   NULL,
    [PasswordAnswer]                         NVARCHAR (128)   NULL,
    [IsApproved]                             BIT              NOT NULL,
    [IsLockedOut]                            BIT              NOT NULL,
    [CreateDate]                             DATETIME2             NOT NULL,
    [LastLoginDate]                          DATETIME2         NOT NULL,
    [LastPasswordChangedDate]                DATETIME2         NOT NULL,
    [LastLockoutDate]                        DATETIME2         NOT NULL,
    [FailedPasswordAttemptCount]             INT              NOT NULL,
    [FailedPasswordAttemptWindowStart]       DATETIME2         NOT NULL,
    [FailedPasswordAnswerAttemptCount]       INT              NOT NULL,
    [FailedPasswordAnswerAttemptWindowStart] DATETIME2         NOT NULL,
    [Comment]                                NTEXT            NULL,
    CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC),
    FOREIGN KEY ([ApplicationId]) REFERENCES [dbo].[Applications] ([ApplicationId]),
);

CREATE TABLE [dbo].[AspNetRoles] (
    [Id]   NVARCHAR (128) NOT NULL,
    [Name] NVARCHAR (MAX) NOT NULL,
    PRIMARY KEY NONCLUSTERED ([Id] ASC),
);

CREATE TABLE [dbo].[AspNetUserRoles] (
    [UserId] NVARCHAR (128) NOT NULL,
    [RoleId] NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_dbo.AspNetUserRoles] PRIMARY KEY CLUSTERED ([UserId] ASC, [RoleId] ASC),
    CONSTRAINT [FK_dbo.AspNetUserRoles_dbo.AspNetRoles_RoleId] FOREIGN KEY ([RoleId]) REFERENCES [dbo].[AspNetRoles] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.AspNetUserRoles_dbo.AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);


CREATE TABLE [dbo].[AspNetUserClaims] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [ClaimType]  NVARCHAR (MAX) NULL,
    [ClaimValue] NVARCHAR (MAX) NULL,
    [User_Id]    NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_dbo.AspNetUserClaims] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_dbo.AspNetUserClaims_dbo.AspNetUsers_User_Id] FOREIGN KEY ([User_Id]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);

GO
CREATE NONCLUSTERED INDEX [IX_User_Id]
    ON [dbo].[AspNetUserClaims]([User_Id] ASC);

CREATE TABLE [dbo].[AspNetUserLogins] (
    [UserId]        NVARCHAR (128) NOT NULL,
    [LoginProvider] NVARCHAR (128) NOT NULL,
    [ProviderKey]   NVARCHAR (128) NOT NULL,
    CONSTRAINT [PK_dbo.AspNetUserLogins] PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [ProviderKey] ASC),
    CONSTRAINT [FK_dbo.AspNetUserLogins_dbo.AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);

GO
CREATE NONCLUSTERED INDEX [IX_UserId]
    ON [dbo].[AspNetUserLogins]([UserId] ASC);

Step 2: Migrate Data

DECLARE @CurrentDate DATETIME 
DECLARE @MinDate DATETIME
SET @MinDate = CAST(0 AS DATETIME)
SET @CurrentDate = GETDATE()

-- Insert ASP.NET Users

 INSERT INTO dbo.AspNetUsers (
    Id
    ,UserName
    ,PasswordHash
    ,Discriminator
    ,SecurityStamp
    ,ApplicationId
    ,LoweredUserName
    ,MobileAlias
    ,IsAnonymous
    ,LastActivityDate
    ,LegacyPasswordHash
    ,MobilePIN
    ,Email
    ,LoweredEmail
    ,PasswordQuestion
    ,PasswordAnswer
    ,IsApproved
    ,IsLockedOut
    ,CreateDate
    ,LastLoginDate
    ,LastPasswordChangedDate
    ,LastLockoutDate
    ,FailedPasswordAttemptCount
    ,FailedPasswordAnswerAttemptWindowStart
    ,FailedPasswordAnswerAttemptCount
    ,FailedPasswordAttemptWindowStart
    ,Comment
    )
SELECT U.UserId
    ,U.UserName
    ,(M.Password + '|' + CAST(M.PasswordFormat AS VARCHAR) + '|' + M.PasswordSalt) AS PasswordHash
    ,'User' AS Discriminator
    ,NewID() AS SecurityStamp
    ,U.ApplicationId
    ,LOWER(U.UserName) AS LoweredUserName
    ,NULL AS MobileAlias
    ,U.IsAnonymous
    ,U.LastActivityDate
    ,M.Password
    ,NULL AS MobilePIN
    ,M.Email
    ,LOWER(M.Email) AS LoweredEmail
    ,M.PasswordQuestion
    ,M.PasswordAnswer
    ,M.IsApproved
    ,M.IsLockedOut
    ,M.CreateDate
    ,M.LastLoginDate
    ,M.LastPasswordChangedDate
    ,M.LastLockoutDate
    ,M.FailedPasswordAttemptCount
    ,@CurrentDate AS FailedPasswordAnswerAttemptWindowStart
    ,M.FailedPasswordAnswerAttemptCount
    ,M.FailedPasswordAttemptWindowStart
    ,M.Comment
FROM [dbo].[Users] AS U
LEFT JOIN [dbo].[Memberships] AS M ON M.ApplicationId = U.ApplicationId
    AND U.UserId = M.UserId;

-- insert roles
INSERT INTO dbo.AspNetRoles(Id,Name)
SELECT RoleId, RoleName
FROM dbo.Roles;

-- insert users in roles
INSERT INTO dbo.AspNetUserRoles(UserId,RoleId)
SELECT UserId,RoleId
FROM dbo.UsersInRoles;

Step 3: Upgrade to ASP.NET Identity 2.1.0 Schema

If you only upgraded the reference to 2.0 without schema changes, you would encounter the following exception:

An exception of type 'System.InvalidOperationException' occurred in Microsoft.AspNet.Identity.EntityFramework.dll but was not handled in user code

Additional information: The model backing the 'ApplicationDbContext' context has changed since the database was created. This could have happened because the model used by ASP.NET Identity Framework has changed or the model being used in your application has changed. To resolve this issue, you need to update your database. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=301867).  Before you update your database using Code First Migrations, please disable the schema consistency check for ASP.NET Identity by setting throwIfV1Schema = false in the constructor of your ApplicationDbContext in your application.

        public ApplicationDbContext() : base("ApplicationServices", throwIfV1Schema:false)

Use the follow script to upgrade the database schema to resolve this issue:

/*RenameColumn(table: "dbo.AspNetUserClaims", name: "User_Id", newName: "UserId");

AddColumn("dbo.AspNetUsers", "Email", c => c.String());

AddColumn("dbo.AspNetUsers", "IsConfirmed", c => c.Boolean(nullable: false));

AlterColumn("dbo.AspNetUsers", "UserName", c => c.String(nullable: false));

DropColumn("dbo.AspNetUsers", "Discriminator");
*/



EXEC sp_rename
    @objname = '[dbo].[AspNetUserClaims].User_Id',
    @newname = 'UserId',
    @objtype = 'COLUMN';


ALTER TABLE [dbo].[AspNetUsers] ADD IsConfirmed BIT NOT NULL DEFAULT(1);

ALTER TABLE [dbo].[AspNetUsers] ALTER COLUMN UserName NVARCHAR(256) NOT NULL;

ALTER TABLE [dbo].[AspNetUsers] DROP COLUMN Discriminator;

/*
Invalid column name 'EmailConfirmed'.
Invalid column name 'PhoneNumber'.
Invalid column name 'PhoneNumberConfirmed'.
Invalid column name 'TwoFactorEnabled'.
Invalid column name 'LockoutEndDateUtc'.
Invalid column name 'LockoutEnabled'.
Invalid column name 'AccessFailedCount'.
*/
ALTER TABLE [dbo].[AspNetUsers] ADD EmailConfirmed BIT NOT NULL DEFAULT(1);
ALTER TABLE [dbo].[AspNetUsers] ADD PhoneNumber NVARCHAR(MAX) NULL;
ALTER TABLE [dbo].[AspNetUsers] ADD PhoneNumberConfirmed BIT NOT NULL DEFAULT(0);
ALTER TABLE [dbo].[AspNetUsers] ADD TwoFactorEnabled BIT NOT NULL DEFAULT(0);
ALTER TABLE [dbo].[AspNetUsers] ADD LockoutEndDateUtc DATETIME NULL;
ALTER TABLE [dbo].[AspNetUsers] ADD LockoutEnabled BIT NOT NULL DEFAULT(1);
ALTER TABLE [dbo].[AspNetUsers] ADD AccessFailedCount INT NOT NULL DEFAULT(0);

 

Step 4: Remember to add the new columns into the application user class

If you want to use the extra columns added above, remember to add them into the ApplicationUser class since they are not properties of IdentityUser.

public class ApplicationUser : IdentityUser
    {
        public ApplicationUser()
        {
            CreateDate = DateTime.Now;
            IsApproved = false;
            LastLoginDate = DateTime.Now;
            LastActivityDate = DateTime.Now;
            LastPasswordChangedDate = DateTime.Now;
            LastLockoutDate = DateTime.Parse("1/1/1754");
            FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1/1/1754");
            FailedPasswordAttemptWindowStart = DateTime.Parse("1/1/1754");
        }

        public System.Guid ApplicationId { get; set; }
        public string MobileAlias { get; set; }
        public bool IsAnonymous { get; set; }
        public System.DateTime LastActivityDate { get; set; }
        public string MobilePIN { get; set; }
        public string LoweredEmail { get; set; }
        public string LoweredUserName { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public System.DateTime CreateDate { get; set; }
        public System.DateTime LastLoginDate { get; set; }
        public System.DateTime LastPasswordChangedDate { get; set; }
        public System.DateTime LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public System.DateTime FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public System.DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }

    }

If you don’t need those attributes, you can drop those columns from ASPNETUsers table.

Summary

After several hours’ hard work, my migration is successful.

image

More from Kontext
comment Comments
Raymond Raymond #133 access_time 10 years ago more_vert
I also need to replace or the current Membership class with the new UserManager...

Please log in or register to comment.

account_circle Log in person_add Register

Log in with external accounts