technoshop

杉並区和泉のソフトウェアハウス、株式会社テラソフトの技術ブログです。主に.NET MVCとKnockoutJSの情報をまとめます。

SimpleMembershipをCode Firstに組み込む

ザクです。

MVC4アプリで認証(authentication)、認可(authorization)をしたい時はSimpleMembershipが便利です。何しろMicrosoft製ですし、MVC4アプリのプロジェクトをVS2012で作成したら自動で組み込まれています。ただ、ユーザー関連のモデル(4つあります。DBのテーブルとしては5つです)は、そのままではCode Firstで管理されていません。こまごま調整して組み込んでやる必要があります。

DAL、モデル、コントローラでのコンテキストを調整する

まず、DALにSimpleMembershipのモデル群を登録しましょう。

public SchoolContext()
    : base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Membership> Membership { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<OAuthMembership> OAuthMembership { get; set; }
      //OnModelCreatingの中に以下のコードを追加
      modelBuilder.Entity<Membership>()
            .HasMany<Role>(r => r.Roles)
              .WithMany(u => u.Members)
              .Map(m =>
              {
                  m.ToTable("webpages_UsersInRoles");
                  m.MapLeftKey("UserId");
                  m.MapRightKey("RoleId");
              });

DALのファイルはSchoolContex.csでしたね。DefaultConnectionの真下に上の4行を追加しましょう。UsersInRolesテーブルも必要ですが、これはリンクテーブルなのでOnModelCreatingの中に定義します。

次にモデルです。SimpleMembershipで使うモデルは、MVC4アプリを作ったらModelsフォルダの中にAccountModels.csとして作られているはずです。UserProfilesだけがありますね。他の3つのモデルを定義してやります。

//UserProfilesの下
    [Table("webpages_Membership")]
    public class Membership
    {
        public Membership()
        {
            Roles = new List<Role>();
            OAuthMemberships = new List<OAuthMembership>();
        }

        [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int UserId { get; set; }
        public DateTime? CreateDate { get; set; }
        [StringLength(128)]
        public string ConfirmationToken { get; set; }
        public bool? IsConfirmed { get; set; }
        public DateTime? LastPasswordFailureDate { get; set; }
        public int PasswordFailuresSinceLastSuccess { get; set; }
        [Required, StringLength(128)]
        public string Password { get; set; }
        public DateTime? PasswordChangedDate { get; set; }
        [Required, StringLength(128)]
        public string PasswordSalt { get; set; }
        [StringLength(128)]
        public string PasswordVerificationToken { get; set; }
        public DateTime? PasswordVerificationTokenExpirationDate { get; set; }

        public ICollection<Role> Roles { get; set; }

        [ForeignKey("UserId")]
        public ICollection<OAuthMembership> OAuthMemberships { get; set; }
    }

    [Table("webpages_OAuthMembership")]
    public class OAuthMembership
    {
        [Key, Column(Order = 0), StringLength(30)]
        public string Provider { get; set; }

        [Key, Column(Order = 1), StringLength(100)]
        public string ProviderUserId { get; set; }

        public int UserId { get; set; }

        [Column("UserId"), InverseProperty("OAuthMemberships")]
        public Membership User { get; set; }
    }

    [Table("webpages_Roles")]
    public class Role
    {
        public Role()
        {
            Members = new List<Membership>();
        }

        [Key]
        public int RoleId { get; set; }
        [StringLength(256)]
        public string RoleName { get; set; }

        public ICollection<Membership> Members { get; set; }
    }
//RegisterExternalLoginModelの上

ここらで一度ビルドしてみます。Filterフォルダ内にあるInitializeSimpleMembershipAttribute.csファイルやAccountController.csで、「'UsersContext'が見つかりませんでした。」というエラーが出ます。UsersContextは、MVC4プロジェクトを作成したときに、勝手につけられるSimpleMembership用のDBコンテキスト名です。Code Firstを使う時は、DBコンテキストはDALで定義しているもの一つにしたいので、これを使わないように既存の設定を調整します。なぜ、DBコンテキストを一つにしたいかというと、Code Firstで管理できるDBコンテキストが1つまでと決まっているからです。(MVC4に含まれるEF5の場合に限る。MVC5のEF6は複数定義できるそうです)

FilterフォルダのInitializeSimpleMembershipAttribute.csファイルをプロジェクトから除外します。ファイルを右クリックで、同名のメニュー選択ですね。次に、AccountController.csの先頭のほうにある[InitializeSimpleMembership]を消します。これは、コントローラで利用できるフィルター属性で、先ほど除外したファイルを呼び出し、ユーザー関連のモデルのテーブルがDBに存在しなければ、自動で作成するものです。Code Firstでユーザーのテーブルを作るので、自動生成はしません。

ビルドします。まだ、AccountController.csでエラーが出ますね。ExternalLoginConfirmationアクションの中です。ここでもUsersContextを使った行があります。そのまま使うには、このコンテキストではなく、SchoolContextの方を使うように変えます。「using (UsersContext~」となっている辺りをコメントアウトしてください。下のクローズする部分も同様にコメントアウトです。次に、コントローラの先頭でSchoolContextの呼び出しを追加してやります。

namespace MvcApplication1.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private SchoolContext db = new SchoolContext();
        //
        // GET: /Account/Login

SchoolContextがどこにあるのかわからないので、usingを追加してやりましょう。

using MvcApplication1.DAL;

ビルドします。今度はエラーを出さずにビルドできました。

ユーザーモデルをマイグレーション

ここまで準備できたら、Code Firstのマイグレーションでユーザーモデルを取り込みます。add-migrationします。

add-migration AddSimpleMembership

DBにテーブルを作成するマイグレーションファイルが作成されました。テーブルだけできても仕方がないので、updateする前にユーザーのシードデータも用意してやりましょう。Configuration.csに書くんでしたよね。

       //以下はコメントアウトするか削除する。
       //public Configuration()
       //{
       //     AutomaticMigrationsEnabled = false;
       //}

       protected override void Seed(SchoolContext context)
        {
      // Seed for UserProfile
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName", autoCreateTables: true);

            if (!WebSecurity.UserExists("testuser"))
                WebSecurity.CreateUserAndAccount(
                    "testuser",
                    "testuser",
                    new{}
                );

Seedメソッドの先頭に上のようにコードを追加します。上の部分のConfigurationのコンストラクタは使わないので、コメントアウトするか削除してください。SimpleMembershipを使うときに使えるメソッドは、WebSecurityに入っているので、これをusingに加えてください。

ロールマネージャーを有効にする

もういいかと思って、update-databseしてしまった人は悔い改めてください。「ロール マネージャー機能は有効にされていません。」というエラーが出たと思います。そうです、SimpleMembershipをちょー便利に使うには、RoleMangerを有効にせねばなりません。これを有効にすることでコントローラに「Autorization(認可)」の属性フィルターが使えるようになります。これについてはまた後日解説します。

RoleMangerを有効にするには、Web.configに設定を追加します。(いろいろ面倒くさいですね)

  <roleManager enabled="true" defaultProvider="SimpleRoleProvider">
      <providers>
        <clear />
        <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
      </providers>
    </roleManager>
    <membership defaultProvider="SimpleMembershipProvider">
      <providers>
        <clear />
        <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
      </providers>
    </membership>

Web.configの内にあるタグの真下に、上のXMLを追加してください。これによって、Membership管理とRole管理にSimpleMembershipを使うと宣言します。変更を保存してupdate-databaseしてみましょう。

update-database 
f:id:technoshop:20140925160127j:plain

SimpleMembershipのテーブルが作成されました。シードデータのtestuserもちゃんと入っています!