メンバーシップとユーザー プロファイルのユニバーサル プロバイダー データを ASP.NET Identity に移行する (C#)

執筆者: Pranav RastogiRick AndersonRobert McMurraySuhas Joshi

このチュートリアルでは、既存のアプリケーションのユニバーサル プロバイダーを使用して作成されたユーザーとロールのデータとユーザー プロファイル データを ASP.NET Identity モデルに移行するために必要な手順について説明します。 ユーザー プロファイル データを移行するためにここで説明するアプローチは、SQL メンバーシップを持つアプリケーションでも使用できます。

Visual Studio 2013 のリリースでは、ASP.NET チームが新しい ASP.NET Identity システムを導入しました。このリリースの詳細については、こちらをご覧ください。 Web アプリケーションを SQL メンバーシップから 新しい ID システムに移行する記事に引き続き、この記事では、ユーザーとロールの管理のためにプロバイダー モデルに従う既存のアプリケーションを新しい ID モデルに移行する手順について説明します。 このチュートリアルでは主に、ユーザー プロファイル データを移行して、新しいシステムにシームレスにフックすることに重点を置きます。 ユーザーとロールの情報の移行は、SQL メンバーシップと似ています。 プロファイル データを移行するために従ったアプローチは、SQL メンバーシップを持つアプリケーションでも使用できます。

例として、プロバイダー モデルを使用する Visual Studio 2012 を使用して作成された Web アプリから始めます。 次に、プロファイル管理用のコードを追加し、ユーザーを登録し、ユーザーのプロファイル データを追加し、データベース スキーマを移行し、ユーザーとロールの管理に ID システムを使用するようにアプリケーションを変更します。 移行のテストとして、ユニバーサル プロバイダーを使用して作成されたユーザーがログインでき、新しいユーザーが登録できるようになります。

Note

完全なサンプルについては、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 アプリケーションを使用することもできます。

    Screenshot of a newly created Web Forms application in Visual Studio 2012 using the Web Forms template.

  2. プロファイル情報を格納する新しいフォルダー 'Models' を作成します

    Screenshot of the new folder called Models created to store profile information.

  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. 'Account' フォルダーに Web Forms ページを追加して、ユーザーからプロファイル データを取得して保存します。 プロジェクトを右クリックし、[新しい項目の追加] を選択します。 マスター ページ 'AddProfileData.aspx' を持つ新しい Web Forms ページを追加します。 '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' ページに移動し、ユーザーのプロファイル情報を追加します。

    Screenshot of the Add Profile Data page to add profile information for the user.

[サーバー エクスプローラー] ウィンドウを使用して、データがシリアル化された xml として 'Profiles' テーブルに保存されていることを確認できます。 Visual Studio の [表示] メニューから 、[サーバー エクスプローラー] を選択します。 web.config ファイルで定義されているデータベースのデータ接続があります。 データ接続をクリックすると、さまざまなサブカテゴリが表示されます。 'Tables' を展開してデータベース内のさまざまなテーブルを表示し、[プロファイル] を右クリックし、[テーブル データの表示] を選択して、'Profiles' テーブルに保存されているプロファイル データを表示します。

Screenshot of the Server Explorer window that shows the data stored in the 'Profiles' table.

Screenshot of the Profiles data table.

データベース スキーマの移行

既存のデータベースを ID システムと連携させるには、元のデータベースに追加したフィールドをサポートするように ID データベースのスキーマを更新する必要があります。 これを行うには、SQL スクリプトを使用して新しいテーブルを作成し、既存の情報をコピーします。 [サーバー エクスプローラー] ウィンドウで、'DefaultConnection' を展開してテーブルを表示します。 'Tables' を右クリックし、[新しいクエリ] を選択します

Screenshot of updating the schema in the Identity database by selecting New Query.

https://raw.github.com/suhasj/UniversalProviders-Identity-Migrations/master/Migration.txt から SQL スクリプトを貼り付けて実行します。 'DefaultConnection' が更新されると、新しいテーブルが追加されていることを確認できます。 テーブル内のデータをチェックして、情報が移行されたことを確認できます。

Screenshot of the Default Connection refreshed and new tables added.

ASP.NET Identity を使用するためにアプリケーションを移行する

  1. ASP.NET Identity に必要な 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. テーブル内の既存のデータを操作するには、テーブルにマップし直すモデル クラスを作成し、これらを ID システムにフックする必要があります。 ID コントラクトの一環として、モデル クラスは 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' がユーザー クラスのプロパティになったことに注意してください。 このため、このユーザー クラスを使用してプロファイル データを直接操作できます。

ダウンロード ソース (https://github.com/suhasj/UniversalProviders-Identity-Migrations/tree/master/UniversalProviders-Identity-Migrations) から IdentityModels フォルダーと IdentityAccount フォルダー内のファイルをコピーします。 これらには、ASP.NET Identity API を使用したユーザーとロールの管理に必要な残りのモデル クラスと新しいページがあります。 使用される方法は SQL メンバーシップに似ています。詳細な説明については、こちらをご覧ください。

アプリで SQLite を Identity データ ストアとして使用する場合、一部のコマンドはサポートされません。 データベース エンジンの制限により、Alter コマンドは次の例外をスローします。

"System.NotSupportedException: SQLite はこの移行操作をサポートしていません。"

回避策として、データベースで Code First の移行を実行してテーブルを変更します。

プロファイル データを新しいテーブルにコピーする

前述のように、'Profiles' テーブル内の xml データを逆シリアル化し、'AspNetUsers' テーブルの列に保存する必要があります。 前の手順で新しい列はユーザー テーブルに作成されたので、残っている作業は、必要なデータを使用してこれらの列を設定することのみです。 これを行うには、新しく作成された列をユーザー テーブルに設定するために 1 回実行されるコンソール アプリケーションを使用します。

  1. 既存のソリューションに新しいコンソール アプリケーションを作成します。

    Screenshot of creating a new console application in the exiting solution.

  2. Entity Framework パッケージの最新バージョンをインストールします。

  3. 上記で作成した Web アプリケーションを参照としてコンソール アプリケーションに追加します。 これを行うには、[プロジェクト]、[参照の追加]、[ソリューション]の順にクリックし、プロジェクトをクリックし、[OK] をクリックします。

  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' フォルダー内のデータベース ファイルに対して動作します。 これを参照するには、コンソール アプリケーションの app.config ファイル内の接続文字列を、Web アプリケーションの web.config 内の接続文字列で更新します。 また、'AttachDbFilename' プロパティに完全な物理パスを指定します。

  6. コマンド プロンプトを開き、上記のコンソール アプリケーションの bin フォルダーに移動します。 実行可能ファイルを実行し、次の図に示されるログ出力を確認します。

    Screenshot of the executable in the command prompt to run and review the log output.

  7. サーバー エクスプローラーで 'AspNetUsers' テーブルを開き、プロパティが保持されている新しい列のデータを確認します。 これらは、対応するプロパティ値で更新されます。

機能を確認する

ASP.NET Identity を使用して実装された、新しく追加されたメンバーシップ ページを使用して、古いデータベースからのユーザーとしてログインします。 このユーザーは、同じ資格情報を使用してログインできます。 OAuth の追加、新しいユーザーの作成、パスワードの変更、ロールの追加、ロールへのユーザーの追加などのその他の機能を試してみてください。

古いユーザーと新しいユーザーのプロファイル データは、取得されてユーザー テーブルに保存されます。 古いテーブルは参照されなくなります。

まとめ

この記事では、メンバーシップ用としてプロバイダー モデルを使用した Web アプリケーションを ASP.NET Identity に移行するプロセスについて説明しました。 この記事ではさらに、ユーザーのプロファイル データを移行して ID システムにフックする方法についても説明しました。 アプリを移行するときに発生した質問や問題については、以下にコメントを残してください。

この記事をレビューしてくれた Rick Anderson と Robert McMurray に感謝します。