将成员身份和用户配置文件的通用提供程序数据迁移到 ASP.NET Identity (C#)

作者: 普拉纳夫·拉斯托吉里克·安德森罗伯特·麦克默里苏哈斯·乔希

本教程介绍将用户和角色数据以及使用现有应用程序通用提供程序创建的用户配置文件数据迁移到 ASP.NET 标识模型所需的步骤。 此处提到的迁移用户配置文件数据的方法也可用于具有 SQL 成员身份的应用程序。

Visual Studio 2013发布后,ASP.NET 团队引入了新的 ASP.NET 标识系统,你可以在此处阅读有关该版本的详细信息。 后续文章介绍如何将 Web 应用程序从 SQL 成员身份迁移到新的标识系统,本文演示了将遵循提供程序模型进行用户和角色管理的现有应用程序迁移到新标识模型的步骤。 本教程的重点是迁移用户配置文件数据,以便将其无缝地挂接到新系统。 迁移用户和角色信息与 SQL 成员身份类似。 迁移配置文件数据的方法也可用于具有 SQL 成员身份的应用程序。

例如,我们将从使用 Visual Studio 2012 创建的 Web 应用开始,该应用使用提供程序模型。 然后,我们将添加用于配置文件管理的代码、注册用户、为用户添加配置文件数据、迁移数据库架构,然后将应用程序更改为使用标识系统进行用户和角色管理。 作为迁移测试,使用 通用提供程序 创建的用户应能够登录,新用户应能够注册。

注意

可以在 中找到完整的示例 https://github.com/suhasj/UniversalProviders-Identity-Migrations

配置文件数据迁移摘要

在开始迁移之前,让我们看看在提供程序模型中存储配置文件数据的体验。 应用程序用户的配置文件数据可以通过多种方式存储,其中最常见的是使用随通用提供程序一起提供的内置配置文件提供程序。 步骤包括

  1. 添加具有用于存储配置文件数据的属性的类。
  2. 添加一个类,该类扩展“ProfileBase”并实现方法以获取用户的上述配置文件数据。
  3. 启用 在web.config 文件中使用默认配置文件提供程序,并定义步骤 2 中声明的类以用于访问配置文件信息。

配置文件信息以序列化 xml 和二进制数据的形式存储在数据库中的“Profiles”表中。

迁移应用程序以使用新的 ASP.NET Identity 系统后,配置文件信息将反序列化并存储为用户类上的属性。 然后,可以将每个属性映射到用户表中的列。 此处的优点是,除了无需每次访问数据信息时都序列化/反序列化数据信息外,还可以直接使用用户类处理属性。

入门

  1. 在 Visual Studio 2012 中创建新的 ASP.NET 4.5 Web Forms应用程序。 当前示例使用 Web Forms 模板,但也可以使用 MVC 应用程序。

    使用 Web Forms 模板在 Visual Studio 2012 中新建的Web Forms应用程序的屏幕截图。

  2. 创建新文件夹“Models”以存储配置文件信息

    为存储配置文件信息而创建的名为 Models 的新文件夹的屏幕截图。

  3. 例如,让我们在个人资料中存储用户的出生日期、城市、身高和体重。 高度和重量存储为名为“PersonalStats”的自定义类。 若要存储和检索配置文件,我们需要一个扩展“ProfileBase”的类。 让我们创建一个新类“AppProfile”来获取和存储配置文件信息。

    public class ProfileInfo
    {
        public ProfileInfo()
        {
            UserStats = new PersonalStats();
        }
        public DateTime? DateOfBirth { get; set; }
        public PersonalStats UserStats { get; set; }
        public string City { get; set; }
    }
    
    public class PersonalStats
    {
        public int? Weight { get; set; }
        public int? Height { get; set; }
    }
    
    public class AppProfile : ProfileBase
    {
        public ProfileInfo ProfileInfo
        {
            get { return (ProfileInfo)GetPropertyValue("ProfileInfo"); }
        }
        public static AppProfile GetProfile()
        {
            return (AppProfile)HttpContext.Current.Profile;
        }
        public static AppProfile GetProfile(string userName)
        {
            return (AppProfile)Create(userName);
        }
    }
    
  4. web.config 文件中启用配置文件。 输入用于存储/检索步骤 3 中创建的用户信息的类名。

    <profile defaultProvider="DefaultProfileProvider" enabled="true"
        inherits="UniversalProviders_ProfileMigrations.Models.AppProfile">
      <providers>
        .....
      </providers>
    </profile>
    
  5. 在“帐户”文件夹中添加 Web 窗体页,以便从用户获取配置文件数据并将其存储。 右键单击“项目”,然后选择“添加新项”。 添加包含母版页“AddProfileData.aspx”的新 Web 窗体页面。 复制“MainContent”部分中的以下内容:

    <h2> Add Profile Data for <%# User.Identity.Name %></h2>
    <asp:Label Text="" ID="Result" runat="server" />
    <div>
        Date of Birth:
        <asp:TextBox runat="server" ID="DateOfBirth"/>
    </div>
    <div>
        Weight:
        <asp:TextBox runat="server" ID="Weight"/>
    </div>
    <div>
        Height:
        <asp:TextBox runat="server" ID="Height"/>
    </div>
    <div>
        City:
        <asp:TextBox runat="server" ID="City"/>
    </div>
    <div>
        <asp:Button Text="Add Profile" ID="Add" OnClick="Add_Click" runat="server" />
    </div>
    

    在代码隐藏中添加以下代码:

    protected void Add_Click(object sender, EventArgs e)
    {
        AppProfile profile = AppProfile.GetProfile(User.Identity.Name);
        profile.ProfileInfo.DateOfBirth = DateTime.Parse(DateOfBirth.Text);
        profile.ProfileInfo.UserStats.Weight = Int32.Parse(Weight.Text);
        profile.ProfileInfo.UserStats.Height = Int32.Parse(Height.Text);
        profile.ProfileInfo.City = City.Text;
        profile.Save();
    }
    

    添加定义 AppProfile 类的命名空间,以删除编译错误。

  6. 运行应用并创建一个用户名为“olduser” 的新用户。导航到“AddProfileData”页并添加用户的个人资料信息。

    用于为用户添加配置文件信息的“添加配置文件数据”页的屏幕截图。

可以使用“服务器资源管理器”窗口验证数据是否以序列化 xml 的形式存储在“配置文件”表中。 在 Visual Studio 中,从“视图”菜单中选择“服务器资源管理器”。 应在 web.config 文件中为数据库定义数据连接。 单击数据连接会显示不同的子类别。 展开“表”以显示数据库中的不同表,然后右键单击“配置文件”,然后选择“显示表数据”以查看存储在“配置文件”表中的配置文件数据。

“服务器资源管理器”窗口的屏幕截图,其中显示了存储在“配置文件”表中的数据。

配置文件数据表的屏幕截图。

迁移数据库架构

若要使现有数据库与标识系统一起使用,我们需要更新 Identity 数据库中的架构,以支持添加到原始数据库的字段。 这可以使用 SQL 脚本来创建新表并复制现有信息。 在“服务器资源管理器”窗口中,展开“DefaultConnection”以显示表。 右键单击“表”,然后选择“新建查询”

通过选择“新建查询”更新标识数据库中的架构的屏幕截图。

https://raw.github.com/suhasj/UniversalProviders-Identity-Migrations/master/Migration.txt 粘贴 SQL 脚本并运行它。 如果刷新了“DefaultConnection”,我们可以看到新表已添加。 可以检查表中的数据,以查看信息是否已迁移。

默认连接已刷新并添加新表的屏幕截图。

迁移应用程序以使用 ASP.NET 标识

  1. 安装 ASP.NET 标识所需的 Nuget 包:

    • Microsoft.AspNet.Identity.EntityFramework
    • Microsoft.AspNet.Identity.Owin
    • Microsoft.Owin.Host.SystemWeb
    • Microsoft.Owin.Security.Facebook
    • Microsoft.Owin.Security.Google
    • Microsoft.Owin.Security.MicrosoftAccount
    • Microsoft.Owin.Security.Twitter

    在此处找到有关管理 Nuget 包的详细信息

  2. 若要处理表中的现有数据,我们需要创建映射回表的模型类,并将其挂接到标识系统中。 作为 Identity 协定的一部分,模型类应实现 Identity.Core dll 中定义的接口,也可以扩展 Microsoft.AspNet.Identity.EntityFramework 中提供的这些接口的现有实现。 我们将对角色、用户登录名和用户声明使用现有类。 我们需要对示例使用自定义用户。 右键单击项目并创建新文件夹“IdentityModels”。 添加新的“User”类,如下所示:

    using Microsoft.AspNet.Identity.EntityFramework;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using UniversalProviders_ProfileMigrations.Models;
    
    namespace UniversalProviders_Identity_Migrations
    {
        public class User : IdentityUser
        {
            public User()
            {
                CreateDate = DateTime.UtcNow;
                IsApproved = false;
                LastLoginDate = DateTime.UtcNow;
                LastActivityDate = DateTime.UtcNow;
                LastPasswordChangedDate = DateTime.UtcNow;
                Profile = new ProfileInfo();
            }
    
            public System.Guid ApplicationId { get; set; }
            public bool IsAnonymous { get; set; }
            public System.DateTime? LastActivityDate { get; set; }
            public string Email { 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; }
            public ProfileInfo Profile { get; set; }
        }
    }
    

    请注意,“ProfileInfo”现在是用户类上的属性。 因此,我们可以使用 user 类直接处理配置文件数据。

从下载源 () https://github.com/suhasj/UniversalProviders-Identity-Migrations/tree/master/UniversalProviders-Identity-Migrations 复制 IdentityModelsIdentityAccount 文件夹中的文件。 它们包含使用 ASP.NET 标识 API 管理用户和角色所需的剩余模型类和新页面。 使用的方法类似于 SQL 成员身份,可 在此处找到详细说明。

如果应用使用 SQLite 作为其标识数据存储,则不支持某些命令。 由于数据库引擎的限制, Alter 命令会引发以下异常:

“System.NotSupportedException:SQLite 不支持此迁移操作。”

解决方法是,在数据库上运行 Code First 迁移以更改表。

将配置文件数据复制到新表

如前所述,我们需要反序列化配置文件表中的 xml 数据,并将其存储在 AspNetUsers 表的列中。 新列是在上一步的 users 表中创建的,因此剩下的就是用必要的数据填充这些列。 为此,我们将使用运行一次的控制台应用程序来填充用户表中新创建的列。

  1. 在退出的解决方案中创建新的控制台应用程序。

    在退出的解决方案中创建新控制台应用程序的屏幕截图。

  2. 安装最新版本的 Entity Framework 包。

  3. 添加上面创建的 Web 应用程序作为对控制台应用程序的引用。 为此,右键单击“项目”,然后单击“添加引用”,然后单击解决方案,然后单击项目并单击“确定”。

  4. 在 Program.cs 类中复制以下代码。 此逻辑读取每个用户的配置文件数据,将其序列化为“ProfileInfo”对象,并将其存储回数据库。

    public class Program
    {
        var dbContext = new ApplicationDbContext();
        foreach (var profile in dbContext.Profiles)
        {
            var stringId = profile.UserId.ToString();
            var user = dbContext.Users.Where(x => x.Id == stringId).FirstOrDefault();
            Console.WriteLine("Adding Profile for user:" + user.UserName);
            var serializer = new XmlSerializer(typeof(ProfileInfo));
            var stringReader = new StringReader(profile.PropertyValueStrings);
            var profileData = serializer.Deserialize(stringReader) as ProfileInfo;
            if (profileData == null)
            {
                Console.WriteLine("Profile data deserialization error for user:" + user.UserName);
            }
            else
            {
                user.Profile = profileData;
            }
        }
        dbContext.SaveChanges();
    }
    

    使用的某些模型在 Web 应用程序项目的“IdentityModels”文件夹中定义,因此必须包含相应的命名空间。

  5. 上述代码适用于在前面步骤中创建的 Web 应用程序项目的 App_Data 文件夹中的数据库文件。 若要引用该字符串,请使用 Web 应用程序的 web.config 中的连接字符串更新控制台应用程序的 app.config 文件中的连接字符串。 此外,在“AttachDbFilename”属性中提供完整的物理路径。

  6. 打开命令提示符并导航到上述控制台应用程序的 bin 文件夹。 运行可执行文件并查看日志输出,如下图所示。

    命令提示符中用于运行和查看日志输出的可执行文件的屏幕截图。

  7. 在服务器资源管理器中打开“AspNetUsers”表,并验证包含属性的新列中的数据。 应使用相应的属性值更新它们。

验证功能

使用使用 ASP.NET Identity 实现的新添加的成员身份页从旧数据库登录用户。 用户应能够使用相同的凭据登录。 尝试其他功能,例如添加 OAuth、创建新用户、更改密码、添加角色、将用户添加到角色等。

应检索旧用户和新用户的配置文件数据并将其存储在用户表中。 不应再引用旧表。

结论

本文介绍了将使用提供程序模型的成员身份的 Web 应用程序迁移到 ASP.NET 标识的过程。 本文还概述了要连接到标识系统的用户的迁移配置文件数据。 对于迁移应用时遇到的问题和疑问,请在下面留下评论。

感谢里克·安德森和罗伯特·麦克默里审阅这篇文章。