きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

ASP.NET Web Pagesのメンバシップで追加のユーザ情報を扱う

ASP.NET Web Pagesでは標準でWebMatrix.WebData.WebSecurityクラスを通してメンバシップ管理が利用できます。しかし、ユーザ情報としてはメンバシッププロバイダがログイン管理のための利用する情報以外の、ユーザの属性として利用できる情報はEmailくらいしかありません。
一方、多くのWebアプリケーションではユーザの氏名やその他の各種属性を保持・管理したい、という要求は一般的かと思います。


WebSecurityクラスを調べてみると、これらの情報も比較的簡単に扱えることがわかりました。
今回は、「姓、名、年齢」の属性を管理する場合を例にしながら、その方法を紹介してみたいと思います。

1.Webサイトを作成する

 WebMatrixの「スターターサイト」、もしくはVisual Studioで「ASP.NET Webサイト(Razor)」のテンプレートでサイトを新規作成します。言語はC#でもVB.NETでも良いですが、今回はC#でやってみます。

 テンプレートを展開すると、/Account配下にログインやアカウント登録関係の各種サンプルが展開されます。
 今回はこのサンプルを改造して利用します。

 
 【重要】
 展開が終わったら、一度実行(F5キー)してください。
 実行すると、データベースに自動的に必要なテーブルが作成されます。

2.App_Codeフォルダを追加する

 サイトにApp_Codeフォルダを追加します。

 必須というわけではないですが、今回、追加で管理する属性を単純なPOCO*1で管理したいので、クラスを追加するためのフォルダを追加します。
 Visual Studioの場合、ソリューションエクスプローラを右クリック、[ASP.NETフォルダーの追加]→[App_Code]を選択します。WebMatrixの場合は新規にフォルダを追加して、App_Codeにリネームしてください。

3.エンティティを追加

 App_Code配下に以下のクラス、「AdditionalUserInfoEntity.cs」を追加します。
 

/// <summary>
/// ユーザー追加属性のエンティティ
/// </summary>
public class AdditionalUserInfoEntity
{
    /// <summary>
    /// 姓
    /// </summary>
    public string LastName { get; set; }

    /// <summary>
    /// 名
    /// </summary>
    public string FirstName { get; set; }

    /// <summary>
    /// 年齢
    /// </summary>
    public int Age { get; set; }

    public AdditionalUserInfoEntity()
    {
        this.LastName = "";
        this.FirstName = "";
    }
}

4.テーブルにカラムを追加する

 自動作成されているUserProfileテーブルに、今回追加する属性を格納するカラムを追加します。

 例えばサーバエクスプローラでテーブルの定義を開き、以下の列を追加します。
 

列名 データ型
LastName nvarchar(50)
FirstName nvarchar(50)
Age int

5.コードの編集その1:宣言〜値受け取り〜バリデーション

 規定で展開されるRegister.cshtmlを編集します。
 まず、コードの先頭でいくつか変数を宣言しておきます。

 一つ目は、追加属性を格納するエンティティ、AdditionalUserInfoEntityのインスタンスと、項目に入力エラーがあったときのメッセージを格納する変数も宣言しておきます。

    var additionalUserInfo = new AdditionalUserInfoEntity();
    var lastNameErrorMessage = "";
    var firstNameErrorMessage = "";
    var ageErrorMessage = "";

 
 次に、if (IsPost) {}ブロック内に、追加した入力校項目を受け取ってバリデーションを行うコードを記述します。

        additionalUserInfo.LastName = Request.Form["lastName"];
        if (additionalUserInfo.LastName.IsEmpty())
        {
            lastNameErrorMessage = "姓を空白にすることはできません。";
            isValid = false;
        }
       
        additionalUserInfo.FirstName = Request.Form["firstName"];
        if (additionalUserInfo.FirstName.IsEmpty())
        {
            firstNameErrorMessage = "名を空白にすることはできません。";
            isValid = false;
        }
       
        additionalUserInfo.Age = Request.Form["age"].AsInt(0);
        if ( additionalUserInfo.Age < 1 )
        {
            ageErrorMessage = "年齢は1以上の整数で入力してください。";
            isValid = false;
        }

 ※上記のバリデーションはかなり適当です。実際の仕様に沿うようにやってください。

6.フォームの編集

 今回の核心となる部分のコードを触る前に、フォーム部分の編集を先にやっておきましょう。
 やることは、姓・名・年齢を入力するフィールドを用意することです。

 「パスワードの確認入力」の欄の下あたりにでも、以下のコードを挿入します。

            <li class="lastName">
                <label for="lastName">姓:</label>
                <input type="text" id="lastName" name="lastName" title="姓" value="@additionalUserInfo.LastName" @if(!lastNameErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!lastNameErrorMessage.IsEmpty()) {
                    <label for="confirmPassword" class="validation-error">@lastNameErrorMessage</label>
                }
            </li>
            <li class="firstName">
                <label for="firstName">名:</label>
                <input type="text" id="firstName" name="firstName" title="名" value="@additionalUserInfo.FirstName" @if(!firstNameErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!firstNameErrorMessage.IsEmpty()) {
                    <label for="confirmPassword" class="validation-error">@firstNameErrorMessage</label>
                }
            </li>
            <li class="age">
                <label for="age">年齢:</label>
                <input type="text" id="age" name="age" title="年齢" value="@additionalUserInfo.Age.ToString()" @if(!ageErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!ageErrorMessage.IsEmpty()) {
                    <label for="confirmPassword" class="validation-error">@ageErrorMessage</label>
                }
            </li>

 

7.コードの編集その2:登録ロジック

 ようやく、本件の核心部分です。
 先に解説しておきますと、今回、WebSecurityクラスのWebSecurity.CreateUserAndAccountメソッドを利用して実装します。
 
 テンプレートにより展開されてサンプル実装は自分でSQLを何度か発行しつつ、WebSecurity.CreateAccountを利用するというちょっとまどろっこしい実装になっていますので、この辺りをまるっと書き換えてしまうことにします。

 まず、

 var db = Database.Open("StarterSite");

を削除します。
 今回、自分で明示的にデータベースにはアクセスしない実装にしますので、この行は不要になります。

 次に、「 var user = db.QuerySingle("SELECT 〜")」でSQLを発行して同一ユーザが存在していないか確認している行がありますので、これをSQLでは無く、WebSecurity.UserExistsメソッドで確認するように変更します。

  var user = db.QuerySingle("SELECT Email FROM UserProfile WHERE LOWER(Email) = LOWER(@0)", email);
  if (user == null) {
   ...
  }

を、

  if (!WebSecurity.UserExists(email)) {
   ...
  }

に書き換えます。かなりシンプルですね。

次に、実際にユーザ情報を登録する個所を変更します。
サンプル実装では「db.Execute("INSERT INTO UserProfile (Email) VALUES (@0)", email);」でINSERT文を発行してユーザ情報をUserProfileに自力でインサートした後、WebSecurity.CreateAccountメソッドで各種メンバーシップ情報を生成しています。これをWebSecurity.CreateUserAndAccountメソッドを利用した形に書き換えます。

具体的には、INSERT文を発行している「db.Execute("INSERT INTO UserProfile (Email) VALUES (@0)", email);」の行を削除したあと、CreateAccountメソッドをコールしている行を以下のように書き換えます。

var token = WebSecurity.CreateUserAndAccount(email, password, additionalUserInfo, requireEmailConfirmation);

ちなみに、今回は追加した属性additionalUserInfoを第三引数として渡していますが、このパラメータをnullにすれば、標準のまま(UserProfleテーブルを拡張しないまま)の状態でユーザを追加することができます。


以上で終了です。
if (isValid) {..}の中を書き出してみると、以下のようになります。
DBに自力でアクセスする部分が無くなったこともあり、最初の実装よりもよりシンプルにになっている気もしますね。

        if (isValid) {
 

            //  ユーザーが既に存在するかどうかを確認します
            if (!WebSecurity.UserExists(email)) {
               
                //  メンバーシップ データベースに新しいエントリを作成して関連付けます。
                //  正常に完了した場合は、要求の処理を続行します
                try {
                    bool requireEmailConfirmation = !WebMail.SmtpServer.IsEmpty();
                    var token = WebSecurity.CreateUserAndAccount(email, password, additionalUserInfo, requireEmailConfirmation);
                    if (requireEmailConfirmation) {
                        var hostUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
                        var confirmationUrl = hostUrl + VirtualPathUtility.ToAbsolute("~/Account/Confirm?confirmationCode=" + HttpUtility.UrlEncode(token));

                        WebMail.Send(
                            to: email,    
                            subject: "アカウントを確認してください",
                            body: "確認コード:  " + token + "。<a href=\"" + confirmationUrl + "\">" + confirmationUrl + "</a> にアクセスしてアカウントを有効にしてください。"
                        );
                    }

                    if (requireEmailConfirmation) {
                        //  ユーザーの登録に感謝し、間もなく電子メールが届くことを伝えます
                        Response.Redirect("~/Account/Thanks");
                    } else {
                        //  ホームページに移動して終了します
                        WebSecurity.Login(email, password);
                        Response.Redirect("~/");
                    }
                } catch (System.Web.Security.MembershipCreateUserException e) {
                    isValid = false;
                    accountCreationErrorMessage = e.ToString();
                }
            } else {
                //  ユーザーは既に存在します
                isValid = false;
                accountCreationErrorMessage = "電子メール アドレスは既に使用中です。";
            }
        }    

 
以上で終了です。
実行後にデータベースを確認すると、追加で設定されたされた氏名と年齢の情報も登録されていることがわかります。

まとめ

以上のように、ASP.NET Web PagesではWebSecurityクラスそしてWebSecurity.CreateUserAndAccountメソッドを利用することで簡単にユーザの登録が行え、さらに追加の属性情報も簡単に扱うことができます。
ちょっとしたWebアプリケーションを実装するレベルなら、これでもかなり実用的なものになるのではないかと思います。

参考:
CreateUserAndAccount Method(MSDN)


蛇足

ちなみに、WebSecurity.CreateUserAndAccountの第三引数には、匿名クラスで値を渡すこともできますので、わざわざエンティティを宣言しなくても

  var additionalUserInfo = new { FirstName = "ほげ", LastName = "ふが", Age = 20 };
  var token = WebSecurity.CreateUserAndAccount(email, password, additionalUserInfo, requireEmailConfirmation);

のようにすることもできます。

*1:Plain Old CLR Object、単純な.NETのクラス