追加のユーザー情報を格納する (C#)

作成者: Scott Mitchell

Note

この記事が作成された後で、ASP.NET メンバーシップ プロバイダーは ASP.NET Identity に置き換えられました。 この記事が作成された時点で使われていたメンバーシップ プロバイダーではなく、 ASP.NET Identity プラットフォームを使うようにアプリを更新することを強くお勧めします。 ASP.NET メンバーシップ システムと比べると、ASP.NET Identity には次のような多くの利点があります。

  • パフォーマンスの向上
  • 向上した拡張性とテストの容易性
  • OAuth、OpenID Connect、2 要素認証のサポート
  • クレームベースの ID のサポート
  • ASP.Net Core との相互運用性の向上

コードのダウンロードまたは PDF のダウンロード

このチュートリアルでは、非常に基本的なゲストブック アプリケーションを構築することで、この質問に答えます。 そのために、データベース内のユーザー情報をモデル化するためのさまざまなオプションを確認し、このデータを Membership フレームワークによって作成されたユーザー アカウントに関連付ける方法を確認します。

はじめに

ASP.NET のMembership フレームワークには、ユーザーを管理するための柔軟なインターフェイスがあります。 Membership API には、資格情報の検証、現在ログオンしているユーザーに関する情報の取得、新しいユーザー アカウントの作成、ユーザー アカウントの削除などのメソッドが含まれています。 Membership フレームワークの各ユーザー アカウントには、資格情報の検証とユーザー アカウント関連の重要なタスクの実行に必要なプロパティのみが含まれています。 Membership フレームワークのユーザー アカウントをモデル化する MembershipUser クラスのメソッドとプロパティがその証拠となります。 このクラスには UserNameEmailIsLockedOut などのプロパティおよび、GetPasswordUnlockUser などのメソッドがあります。

多くの場合、アプリケーションは Membership フレームワークに含まれていない追加のユーザー情報を格納する必要があります。 たとえば、オンライン小売業者の場合、各ユーザーが配送先住所と請求先住所、支払い情報、配送設定、連絡先電話番号を保存できるようにする必要があるでしょう。 さらに、システム内の各オーダーは、特定のユーザー アカウントに関連付けられます。

MembershipUserクラスには、PhoneNumberDeliveryPreferencesPastOrders などのプロパティは含まれません。 では、アプリケーションに必要なユーザー情報を追跡し、Membership フレームワークと統合するにはどうすればよいでしょうか。 このチュートリアルでは、非常に基本的なゲストブック アプリケーションを構築することで、この質問に答えます。 そのために、データベース内のユーザー情報をモデル化するためのさまざまなオプションを確認し、このデータを Membership フレームワークによって作成されたユーザー アカウントに関連付ける方法を確認します。 それでは始めましょう。

手順 1: ゲストブック アプリケーションのデータ モデルを作成する

データベース内のユーザー情報をキャプチャし、Membership フレームワークによって作成されたユーザー アカウントに関連付けるために使用できるさまざまな手法があります。 これらの手法を説明するには、チュートリアル Web アプリケーションを拡張して、ある種類のユーザー関連データをキャプチャする必要があります。 (現在、アプリケーションのデータ モデルには、.SqlMembershipProvider で必要なアプリケーション サービス テーブルのみが含まれています。)

認証されたユーザーがコメントを残すことができる、非常に単純なゲストブック アプリケーションを作成しましょう。 ゲストブックのコメントを保存するだけでなく、各ユーザーが自分のホーム タウン、ホームページ、署名を格納できるようにしましょう。 ユーザーのホーム タウン、ホーム ページ、署名が指定されている場合は、ゲストブックに残した各メッセージに表示されます。

GuestbookComments テーブルの追加

ゲストブックのコメントをキャプチャするには、CommentIdSubjectBodyCommentDate などの列を持つ、GuestbookComments というデータベース テーブルを作成する必要があります。 また、GuestbookComments テーブル内の各レコードで、コメントを残したユーザーを参照する必要があります。

このテーブルをデータベースに追加するには、Visual Studio のデータベース エクスプローラーに移動し、SecurityTutorials データベースにドリルダウンします。 [テーブル] フォルダーを右クリックし、[新しいテーブルの追加] を選択します。 これにより、新しいテーブルの列を定義できるインターフェイスが表示されます。

Add a New Table to the SecurityTutorials Database

図 1: SecurityTutorials データベースに新しいテーブルを追加する (クリックするとフルサイズの画像を表示します)

次に、GuestbookComments の列を定義します。 最初に、CommentId という名前の列を uniqueidentifier 型で追加します。 この列はゲストブック内の各コメントを一意に識別するため、NULL を禁し、テーブルの主キーとしてマークします。 それぞれの INSERTCommentId フィールドの値を指定するのではなく、列の既定値を NEWID() に設定することで、INSERT でこのフィールドに対して新しい uniqueidentifier 値が自動的に生成されることを示すことができます。 この最初のフィールドを追加し、それを主キーとしてマークし、既定値を設定すると、画面は図 2 に示すスクリーンショットのようになります。

Add a Primary Column Named CommentId

図 2: CommentId というプライマリ列を追加する (クリックするとフルサイズの画像を表示します)

次に、Subject という名前の nvarchar(50) 型の列と、Body という名前の nvarchar(MAX) 型の列を追加し、両方の列で NULL を不許容にします。 その後、CommentDate という名前の datetime 型の列を追加します。 NULL を無効にし、CommentDate 列の既定値を getdate() に設定します。

その後やることは、ユーザー アカウントを各ゲストブック コメントに関連付ける列を追加することのみです。 1 つの方法は、UserName という名前の nvarchar(256) 型の列を追加することです。 これは、SqlMembershipProvider 以外のメンバーシップ プロバイダーを使用する場合に適した選択肢です。 ただし、このチュートリアル シリーズと同様に、SqlMembershipProvider を使用する場合、aspnet_Users テーブル内の UserName 列が一意であるとは限りません。 aspnet_Users テーブルの主キーは UserId であり、型は uniqueidentifier です。 そのため、GuestbookComments テーブルには、UserId という、uniqueidentifier 型 (NULL が許可されない) の列が必要です。 先に進み、この列を追加します。

Note

SQL Server での Membership スキーマの 作成」チュートリアルで説明したように、Membership フレームワークは、別々のユーザー アカウントを持つ複数の Web アプリケーションが同じユーザー ストアを共有できるように設計されています。 これを行うには、ユーザー アカウントを別々のアプリケーションに分割します。 また、各ユーザー名はアプリケーション内で一意であることが保証されますが、同じユーザー ストアを使用する別のアプリケーションで同じユーザー名を使用できます。 aspnet_Users テーブルでは、UserName フィールドと ApplicationId フィールドには複合 UNIQUE 制約がありますが、UserName フィールド単体に対する制約はありません。 その結果、aspnet_Users テーブルに同じ UserName 値を持つ 2 つ以上のレコードが含まれる可能性があります。 ただし、aspnet_Users テーブルの UserId フィールドには UNIQUE 制約があります (主キーであるため)。 UNIQUE 制約がないと、GuestbookComments テーブルと aspnet_Users テーブルの間に外部キー制約を確立できないため、この制約が重要です。

UserId 列を追加したら、ツール バーの [保存] アイコンをクリックしてテーブルを保存します。 新しいテーブルに GuestbookComments と名前を付けます。

GuestbookComments テーブルに関する最後の問題が 1 つあります。GuestbookComments.UserId 列と aspnet_Users.UserId 列の間に外部キー制約を作成する必要があります。 これを実現するには、ツール バーの [リレーションシップ] アイコンをクリックして、[外部キー リレーションシップ] ダイアログ ボックスを起動します。 (または、[テーブル デザイナー] メニューに移動し、[リレーションシップ] を選択しても、このダイアログ ボックスを起動できます)。

[外部キーリレーションシップ] ダイアログ ボックスの左下隅にある [追加] ボタンをクリックします。 これにより、新しい外部キー制約が追加されますが、リレーションシップに関係するテーブルを定義する必要があります。

Use the Foreign Key Relationships Dialog Box to Manage a Table's Foreign Key Constraints

図 3: [外部キー リレーションシップ] ダイアログ ボックスを使用してテーブルの外部キー制約を管理する (クリックするとフルサイズの画像を表示します)

次に、右側の [テーブルと列の仕様] 行の省略記号アイコンをクリックします。 [テーブルと列] ダイアログ ボックスが開き、そこから主キー テーブルと列および、GuestbookComments テーブルの外部キー列を指定できます。 具体的には、aspnet_UsersUserId を主キー テーブルと列として選択し、GuestbookComments テーブルの UserId を外部キー列として選択します (図 4 を参照)。 主および外部キーのテーブルと列を定義したら、[OK] をクリックして [外部キーのリレーションシップ] ダイアログ ボックスに戻ります。

Establish a Foreign Key Constraint Between the aspnet_Users and GuesbookComments Tables

図 4: aspnet_Users および GuesbookComments テーブル間の外部キー制約を確立する (クリックするとフルサイズの画像を表示します)

この時点で、外部キー制約が確立されました。 この制約を設けることで、存在しないユーザー アカウントを参照するゲストブック エントリが存在しないことが保証され、2 つのテーブル間のリレーションに整合性が保証 されます。 既定では、外部キー制約は、対応する子レコードがある場合、親レコードの削除を禁止します。 つまり、ユーザーが 1 つ以上のゲストブック コメントを作成し、そのユーザー アカウントを削除しようとしても、まずゲストブックのコメントを削除しない限り、削除は失敗します。

外部キー制約は、親レコードが削除されたときに関連付けられている子レコードを自動的に削除するように構成できます。 つまり、この外部キー制約は、ユーザー アカウントが削除されたときにユーザーのゲストブック エントリを自動的に削除するように設定できます。 これを行うには、[INSERT と UPDATE の仕様] セクションを展開し、[ルールの削除] プロパティを Cascade に設定します。

Configure the Foreign Key Constraint to Cascade Deletes

図 5: 削除を連鎖するように外部キー制約を構成する (クリックするとフルサイズの画像を表示します)

外部キー制約を保存するには、[閉じる] ボタンをクリックして [外部キー リレーションシップ] を終了します。 次に、ツール バーの [保存] アイコンをクリックして、テーブルとこのリレーションシップを保存します。

ユーザーのホーム タウン、ホームページ、署名の保存

GuestbookComments 表は、ユーザー アカウントとの一対多リレーションシップを共有する情報を格納する仕組みを示しています。 各ユーザー アカウントには任意の数のコメントが関連付けられるため、このリレーションシップは、各コメントを特定のユーザーにリンクする列を含む、一連のコメントを保持するテーブルを作成することによってモデル化されます。 SqlMembershipProvider を使用する場合、このリンクは、UserId という名前の uniqueidentifier 型の列と、この列と aspnet_Users.UserId との外部キー制約を作成することで最適に確立されます。

ここでは、ユーザーのゲストブック コメントに表示されるホーム タウン、ホームページ、署名を格納するために、各ユーザー アカウントに 3 つの列を関連付ける必要があります。 これを実現するには、さまざまな方法があります。

  • aspnet_Users または aspnet_Membership テーブルに新しい列を追加します。SqlMembershipProvider で使用されるスキーマが変更されるため、このアプローチはお勧めしません。 この方法では、今後面倒なことが起こる可能性があります。 たとえば、将来のバージョンの ASP.NET で別の SqlMembershipProvider スキーマが使用されることになると、どうなるでしょうか。 Microsoft が ASP.NET 2.0 SqlMembershipProvider データを新しいスキーマに移行するためのツールを収録する可能性はありますが、ASP.NET 2.0 SqlMembershipProvider スキーマに変更を行っている場合は、その変換は行えなくなります。

  • ASP.NET の Profile フレームワークを使用し、ホーム タウン、ホームページ、署名のプロパティを定義します。 ASP.NET には、追加のユーザー固有のデータを格納するように設計された Profile フレームワークが含まれています。 Membership フレームワークと同様に、Profile フレームワークはプロバイダー モデルの上に構築されます。 .NET Framework には、SQL Server データベースにプロファイル データを格納する SqlProfileProvider が付属しています。 実際、データベースには、既に、SqlProfileProvider (aspnet_Profile) が使用するテーブルが含まれています。このテーブルは、SQL Server での Membership スキーマの作成チュートリアルでアプリケーション サービスを追加したときに追加されました。
    プロファイル フレームワークの主な利点は、開発者が Web.config でプロファイル プロパティを定義できる点です。基になるデータ ストアとの間でプロファイル データをシリアル化するコードを記述する必要はありません。 要するに、プロファイル プロパティのセットを定義し、コードで操作することは非常に簡単です。 ただし、バージョン管理に関しては、依然としてプロファイル システムが必要な場合が多くあります。そのため、後で新しいユーザー固有のプロパティを追加する必要があるアプリケーションや、既存のプロパティを削除または変更するアプリケーションがある場合は、Profile フレームワークが最適なオプションではない可能性があります。 さらに、SqlProfileProvider はプロファイル プロパティを高度に非正規化された方法で格納するため、プロファイル データに対して直接クエリを実行することはほぼ不可能になります (たとえば、ニューヨークがホーム タウンのユーザー数など)。
    Profile フレームワークの詳細については、このチュートリアルの最後にある「その他の情報」セクションを参照してください。

  • これら 3 つの列をデータベースの新しいテーブルに追加し、このテーブルと aspnet_Users の間に一対一のリレーションシップを確立します。この方法では、Profile フレームワークよりも少し多くの作業が必要ですが、データベースで追加のユーザー プロパティをモデル化する方法について、最大限の柔軟性が得られます。 このチュートリアルではこの方法をとります。

ホーム タウン、ホームページ、およびユーザーごとの署名を保存するための、UserProfiles というテーブルを新規作成します。 [データベース エクスプローラー] ウィンドウで [テーブル] フォルダーを右クリックし、新しいテーブルを作成します。 最初の列に UserId と名前を付け、その型を uniqueidentifier に設定します。 NULL 値を禁止し、列を主キーとしてマークします。 次に、名前が HomeTownnvarchar(50) 型、名前が HomepageUrlnvarchar(100) 型の列および、nvarchar(500) 型の署名を追加します。 これら 3 つの列はそれぞれ、NULL 値を受け取ることができます。

Create the UserProfiles Table

図 6:UserProfiles テーブルを作成する (クリックするとフルサイズの画像を表示します)

テーブルを UserProfiles という名前で保存します。 最後に、UserProfiles テーブルの UserId フィールドと aspnet_Users.UserId フィールドの間に外部キー制約を設定します。 GuestbookCommentsaspnet_Users テーブル間の外部キー制約と同様に、この制約をカスケード削除にします。 UserProfilesUserId フィールドは主キーであるため、これにより、各ユーザー アカウントの UserProfiles テーブルに複数のレコードが発生しないことが保証されます。 この種類のリレーションシップは、1 対 1 と呼ばれます。

データ モデルが作成されたので、使用する準備ができました。 手順 2 と 3 では、現在ログオンしているユーザーがホーム タウン、ホームページ、署名情報を表示および編集する方法について説明します。 手順 4 では、認証されたユーザーがゲストブックに新しいコメントを送信し、既存のコメントを表示するためのインターフェイスを作成します。

手順 2: ユーザーのホーム タウン、ホームページ、署名を表示する

現在ログオンしているユーザーが自分のホーム タウン、ホームページ、署名情報を表示および編集できるようにするには、さまざまな方法があります。 TextBox コントロールと Label コントロールを使用してユーザー インターフェイスを手動で作成することも、DetailsView コントロールなどのデータ Web コントロールのいずれかを使用することもできます。 データベース SELECTUPDATE ステートメントを実行するには、ページの分離コード クラスに ADO.NET コードを記述するか、または SqlDataSource で宣言型アプローチを採用します。 アプリケーションには階層化されたアーキテクチャが含まれていることが望ましいです。このアーキテクチャは、ページの分離コード クラスからプログラムで呼び出すか、ObjectDataSource コントロールを使用して宣言により呼び出すことができます。

このチュートリアル シリーズでは、フォーム認証、承認、ユーザー アカウント、ロールに重点を置いているため、これらのさまざまなデータ アクセス オプションや、ASP.NET ページから直接 SQL ステートメントを実行するよりも階層化アーキテクチャが推奨される理由については詳しく説明しません。 最も手早く簡単なオプションである DetailsView と SqlDataSource を使用して説明しますが、ここで説明する概念は、代替の Web コントロールとデータ アクセス ロジックにも確実に適用できます。 ASP.NET でデータを操作する方法の詳細については、私が執筆した「ASP.NET 2.0 でのデータ操作」チュートリアル シリーズをご覧ください。

Membership フォルダーの AdditionalUserInfo.aspx ページを開き、DetailsView コントロールをページに追加し、その ID プロパティを UserProfile に設定し、Width プロパティと Height プロパティをクリアします。 DetailsView のスマート タグを展開し、新しいデータ ソース コントロールにバインドするオプションを選択します。 こうすると、DataSource 構成ウィザードが起動します (図 7 を参照)。 最初の手順では、データ ソースの種類を指定するように求められます。 SecurityTutorials データベースに直接接続するため、[データベース] アイコンを選択し、IDUserProfileDataSource と指定します。

Add a New SqlDataSource Control Named UserProfileDataSource

図 7: UserProfileDataSource という新しい SqlDataSource コントロールを追加する (クリックするとフルサイズの画像を表示します)

次の画面では、使用するデータベースを指定するよう求められます。 SecurityTutorials データベースの接続文字列を既に Web.config で定義しました。 この SecurityTutorialsConnectionString という接続文字列名は、ドロップダウン リストに含まれているでしょう。 このオプションを選択し、[次へ] をクリックします。

Choose SecurityTutorialsConnectionString from the Drop-Down List

図 8: ドロップダウン リストから SecurityTutorialsConnectionString を選択する (クリックするとフルサイズの画像を表示します)

次の画面では、クエリを実行するテーブルと列を指定するように求められます。 ドロップダウン リストから UserProfiles テーブルを選択し、すべての列を確認します。

Bring Back All of the Columns from the UserProfiles Table

図 9: UserProfiles テーブルからすべての列を取得する (クリックするとフルサイズの画像を表示します)

図 9 の現在のクエリは、UserProfiles 内のすべてのレコードを返しますが、必要なものは現在ログオンしているユーザーのレコードのみです。 WHERE 句を追加するには、WHERE ボタンをクリックして [WHERE 句の追加] ダイアログ ボックスを表示します (図 10 を参照)。 ここでは、フィルターを適用する列、演算子、およびフィルター パラメーターのソースを選択できます。 列に UserId、演算子に "=" を選択します。

残念ながら、現在ログオンしているユーザーの UserId 値を返す組み込みのパラメーター ソースはありません。 プログラムでこの値を取得する必要があります。 そこで、[ソース] ドロップダウン リストを [なし] に設定し、[追加] ボタンをクリックしてパラメーターを追加したら、[OK] をクリックします。

Add a Filter Parameter on the UserId Column

図 10: UserId 列にフィルター パラメーターを追加する (クリックするとフルサイズの画像を表示します)

[OK] をクリックすると、図 9 に示す画面に戻ります。 ただし、今回は、画面の下部にある SQL クエリに WHERE 句が含まれています。 [次へ] をクリックして、[クエリのテスト] 画面に移動します。 ここでは、クエリを実行して結果を確認できます。 [完了] をクリックして、ウィザードを完了します。

DataSource 構成ウィザードを完了すると、Visual Studio はウィザードで指定された設定に基づいて SqlDataSource コントロールを作成します。 さらに、SqlDataSource の SelectCommand から返される各列の DetailsView に BoundFields を手動で追加します。 ユーザーはこの値を知る必要がないため、DetailsView に UserId フィールドを表示する必要はありません。 このフィールドは、DetailsView コントロールの宣言型マークアップから直接削除するか、スマート タグから [フィールドの編集] リンクをクリックして削除できます。

この時点で、ページの宣言型マークアップは次のようになります。

<asp:DetailsView ID="UserProfile" runat="server"
     AutoGenerateRows="False" DataKeyNames="UserId"
     DataSourceID="UserProfileDataSource">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"
               SortExpression="Signature" />
     </Fields>
</asp:DetailsView>
<asp:SqlDataSource ID="UserProfileDataSource" runat="server"
          ConnectionString="<%$ ConnectionStrings:SecurityTutorialsConnectionString %>"
          SelectCommand="SELECT [UserId], [HomeTown], [HomepageUrl], [Signature] FROM
          [UserProfiles] WHERE ([UserId] = @UserId)">
     <SelectParameters>
          <asp:Parameter Name="UserId" Type="Object" />
     </SelectParameters>
</asp:SqlDataSource>

データが選択される前に、SqlDataSource コントロールの UserId パラメーターを現在ログインしているユーザーの UserId にプログラムで設定する必要があります。 これを実現するには、SqlDataSource の Selecting イベントのイベント ハンドラーを作成し、そこに次のコードを追加します。

protected void UserProfileDataSource_Selecting(object sender, 
          SqlDataSourceSelectingEventArgs e)
{
     // Get a reference to the currently logged on user
     MembershipUser currentUser = Membership.GetUser();
 
     // Determine the currently logged on user's UserId value
     Guid currentUserId = (Guid)currentUser.ProviderUserKey;
 
     // Assign the currently logged on user's UserId to the @UserId parameter
     e.Command.Parameters["@UserId"].Value = currentUserId;
}

上記のコードはまず、Membership クラスの GetUser メソッドを呼び出して、現在ログオンしているユーザーへの参照を取得します。 これにより、ProviderUserKey プロパティに UserId を含む MembershipUser オブジェクトが返されます。 その後、UserId 値は SqlDataSource の @UserId パラメーターに割り当てられます。

Note

Membership.GetUser() メソッドは、現在ログオンしているユーザーに関する情報を返します。 匿名ユーザーがページにアクセスすると、値 null が返されます。 この場合、ProviderUserKey プロパティを読み取ろうとすると、次のコード行の NullReferenceException が表示されます。 もちろん、前のチュートリアルでは認証されたユーザーのみがこのフォルダー内の ASP.NET リソースにアクセスできるように、 URL 承認を構成したため、Membership.GetUser()AdditionalUserInfo.aspx ページで null 値を返す心配はありません。 匿名アクセスが許可されているページで現在ログオンしているユーザーに関する情報にアクセスする必要がある場合は、プロパティを参照する前に、GetUser() メソッドから null MembershipUser 以外のオブジェクトが返されるようにする必要があります。

ブラウザーから AdditionalUserInfo.aspx ページにアクセスすると、UserProfiles テーブルに行を追加していないため、空白のページが表示されます。 手順 6 では、CreateUserWizard コントロールをカスタマイズして、新しいユーザー アカウントの作成時に UserProfiles テーブルに新しい行を自動的に追加する方法について説明します。 ただし、現時点では、テーブルにレコードを手動で作成する必要があります。

Visual Studio のデータベース エクスプローラーに移動し、[テーブル] フォルダーを展開します。 aspnet_Users テーブルを右クリックし、[テーブル データの表示] を選択してテーブル内のレコードを表示します。UserProfiles テーブルに対しても同じことを行います。 図 11 は、垂直方向に並べて表示された場合のこれらの結果を示しています。 私のデータベースには現在、Bruce、Fred、Tito の aspnet_Users レコードがありますが、UserProfiles テーブルにはレコードがありません。

The Contents of the aspnet_Users and UserProfiles Tables are Displayed

図 11: テーブル aspnet_Users および UserProfiles の内容が表示されます (クリックするとフルサイズの画像を表示します)

HomeTownHomepageUrlSignature フィールドの値を手動で入力して、UserProfiles テーブルに新しいレコードを追加します。 新しい UserProfiles レコードで有効な UserId 値を取得する最も簡単な方法は、aspnet_Users テーブル内の特定のユーザー アカウントから UserId フィールドを選択し、それをコピーして UserProfilesUserId フィールドに貼り付けることです。 図 12 は、Bruce の新しいレコードが追加された後の UserProfiles テーブルを示しています。

A Record was Added to UserProfiles for Bruce

図 12: Bruce のレコードが UserProfiles に追加されました (クリックするとフルサイズの画像を表示します)

AdditionalUserInfo.aspx ページに戻り、Bruce としてログインします。 図 13 に示すように、Bruce の設定が表示されます。

The Currently Visiting User is Shown His Settings

図 13: 現在アクセスしているユーザーが自分の設定を表示する (クリックするとフルサイズの画像を表示します)

Note

次に進み、各メンバーシップ ユーザーの UserProfiles テーブルにレコードを手動で追加します。 手順 6 では、CreateUserWizard コントロールをカスタマイズして、新しいユーザー アカウントの作成時に UserProfiles テーブルに新しい行を自動的に追加する方法について説明します。

手順 3: ユーザーが自分のホーム タウン、ホームページ、署名を編集できるようにする

この時点で、現在ログインしているユーザーはホーム タウン、ホームページ、署名の設定を表示できますが、まだ変更することはできません。 データを編集できるように DetailsView コントロールを更新しましょう。

最初に、実行する UPDATE ステートメントおよび対応するパラメーターを指定し、SqlDataSource の UpdateCommand を追加する必要があります。 SqlDataSource を選択し、[プロパティ] ウィンドウで、UpdateQuery プロパティの横にある省略記号をクリックして、[コマンドおよびパラメーター エディター] ダイアログ ボックスを表示します。 テキスト ボックスに次の UPDATE ステートメントを入力します。

UPDATE UserProfiles SET
     HomeTown = @HomeTown,
     HomepageUrl = @HomepageUrl,
     Signature = @Signature
WHERE UserId = @UserId

次に、[パラメーターの更新] ボタンをクリックすると、UPDATE ステートメント内の各パラメーターについて、SqlDataSource コントロールの UpdateParameters コレクションにパラメーターが作成されます。 すべてのパラメーターのソースを [なし] に設定したまま、[OK] ボタンをクリックしてダイアログ ボックスを完了します。

Specify the SqlDataSource's UpdateCommand and UpdateParameters

図 14: SqlDataSource の UpdateCommandUpdateParameters を指定する (クリックするとフルサイズの画像を表示します)

SqlDataSource コントロールに行った追加により、DetailsView コントロールで編集をサポートできるようになりました。 DetailsView のスマート タグで、[編集を有効にする] チェック ボックスをオンにします。 これにより、ShowEditButton プロパティが True に設定された CommandField がコントロールの Fields コレクションに追加されます。 DetailsView が読み取り専用モードで表示される場合は [編集] ボタンが表示され、編集モードで表示される場合は [更新] ボタンと [キャンセル] ボタンが表示されます。 ただし、ユーザーに [編集] をクリックさせるのではなく、DetailsView コントロールの DefaultMode プロパティEdit に設定することで、DetailsView を "常に編集可能" な状態でレンダリングできます。

これらの変更により、DetailsView コントロールの宣言型マークアップは次のようになります。

<asp:DetailsView ID="UserProfile" runat="server"
          AutoGenerateRows="False" DataKeyNames="UserId"
          DataSourceID="UserProfileDataSource" DefaultMode="Edit">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"
               SortExpression="Signature" />
          <asp:CommandField ShowEditButton="True" />
     </Fields>
</asp:DetailsView>

CommandField と DefaultMode プロパティの追加に注意してください。

先に進み、ブラウザーを使用してこのページをテストします。 UserProfiles に対応するレコードを持つユーザーでアクセスすると、ユーザーの設定が編集可能なインターフェイスに表示されます。

The DetailsView Renders an Editable Interface

図 15: DetailsView が編集可能なインターフェイスをレンダリングします (クリックするとフルサイズの画像を表示します)

値を変更し、[更新] ボタンをクリックしてみてください。 何も起こっていないかのように見えます。 ポストバックが発生し、値はデータベースに保存されますが、保存が発生したことを示す視覚的なフィードバックはありません。

これを解決するには、Visual Studio に戻り、DetailsView の上にラベル コントロールを追加します。 IDSettingsUpdatedMessageに、Text プロパティ を "設定が更新されました" に、設定し、Visible および EnableViewState プロパティを false に設定します。

<asp:Label ID="SettingsUpdatedMessage" runat="server"
     Text="Your settings have been updated."
     EnableViewState="false"
     Visible="false"></asp:Label>

DetailsView が更新されるたびに SettingsUpdatedMessage ラベルを表示する必要があります。 これを行うには、DetailsView の ItemUpdated イベントのイベント ハンドラーを作成し、次のコードを追加します。

protected void UserProfile_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
     SettingsUpdatedMessage.Visible = true;
}

ブラウザーから AdditionalUserInfo.aspx ページに戻り、データを更新します。 役に立つステータス メッセージが表示されるようになります。

A Short Message is Displayed When the Settings are Updated

図 16: 設定が更新されたときに短いメッセージが表示されます (クリックするとフルサイズの画像を表示します)

Note

DetailsView コントロールの編集インターフェイスには、まだ多くの改善点があります。 標準サイズのテキスト ボックスを使用しますが、署名フィールドはおそらく複数行のテキスト ボックスである必要があります。 RegularExpressionValidator を使用して、ホームページの URL が入力された場合、それが "http://" または "https://" で始まっていることを確認する必要があります。 さらに、DetailsView コントロールでは DefaultMode プロパティが Edit に設定 されているため、[キャンセル] ボタンは機能しません。 削除するか、クリックすると、ユーザーを他のページ (~/Default.aspx など) にリダイレクトする必要があります。 これらの改善は、読者の演習として残します。

現在、Web サイトには AdditionalUserInfo.aspx ページへのリンクはありません。 アクセスする唯一の方法は、ページの URL をブラウザーのアドレス バーに直接入力することです。 Site.master マスター ページでこのページへのリンクを追加しましょう。

マスター ページの LoginContent ContentPlaceHolder に LoginView Web コントロールが含まれており、認証された訪問者と匿名の訪問者に対して別々のマークアップが表示されることを思い出してください。 LoginView コントロールの LoggedInTemplate を更新して、 AdditionalUserInfo.aspx ページへのリンクを含めます。 これらの変更を行った後、LoginView コントロールの宣言型マークアップは次のようになります。

<asp:LoginView ID="LoginView1" runat="server">
     <LoggedInTemplate>
          Welcome back,
          <asp:LoginName ID="LoginName1" runat="server" />.
          <br />
          <asp:HyperLink ID="lnkUpdateSettings" runat="server" 
               NavigateUrl="~/Membership/AdditionalUserInfo.aspx">
               Update Your Settings</asp:HyperLink>
     </LoggedInTemplate>
     <AnonymousTemplate>
          Hello, stranger.
     </AnonymousTemplate>
</asp:LoginView>

lnkUpdateSettings HyperLink コントロールが LoggedInTemplate に追加されていることに注意してください。 このリンクを設定すると、認証されたユーザーはすぐに、ホーム タウン、ホームページ、署名の設定を表示および変更できるページに移動できます。

手順 4: 新しいゲストブック コメントを追加する

Guestbook.aspx ページでは、認証されたユーザーがゲストブックを表示し、コメントを残すことができます。 まず、新しいゲストブックコメントを追加するインターフェイスを作成します。

Visual Studio で Guestbook.aspx ページを開き、2 つの TextBox コントロール (新しいコメントの件名用と本文用) で構成されるユーザー インターフェイスを構築します。 最初の TextBox コントロールの ID プロパティを Subject、そのColumns プロパティを 40に設定し、2 番目の IDBody、その TextModeMultiLine に設定し、その Width プロパティおよび Rows プロパティをそれぞれ "95%" と 8 に設定します。 ユーザー インターフェイスを完成させるために、PostCommentButton と名付けた Button Web コントロールを追加し、その Text プロパティを "Post Your Comment" に設定します。

各ゲストブックのコメントには件名と本文が必要であるため、各 テキストボックスに RequiredFieldValidator を追加します。 ValidationGroup これらのコントロールのプロパティを "EnterComment" に設定します。同様に、コントロールの ValidationGroup プロパティを "EnterComment" に設定 PostCommentButton します。 ASP.NET の検証コントロールに関する詳細については、「ASP.NETでのフォーム検証」を確認してください。

ユーザー インターフェイスを作成した後、ページの宣言型マークアップは次のようになります。

<h3>Leave a Comment</h3>
<p>
     <b>Subject:</b>
     <asp:RequiredFieldValidator ID="SubjectReqValidator" runat="server"
          ErrorMessage="You must provide a value for Subject"
          ControlToValidate="Subject" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br/>
     <asp:TextBox ID="Subject" Columns="40" runat="server"></asp:TextBox>
</p>
<p>
     <b>Body:</b>
     <asp:RequiredFieldValidator ID="BodyReqValidator" runat="server"
          ControlToValidate="Body"
          ErrorMessage="You must provide a value for Body" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br/>
     <asp:TextBox ID="Body" TextMode="MultiLine" Width="95%"
          Rows="8" runat="server"></asp:TextBox>
</p>
<p>
     <asp:Button ID="PostCommentButton" runat="server" 
          Text="Post Your Comment"
          ValidationGroup="EnterComment" />
</p>

ユーザー インターフェイスが完了したら、次のタスクでは、PostCommentButton がクリックされたときに GuestbookComments テーブルに新しいレコードを挿入します。 これは、次に示すようにさまざまな方法で実現できます。ボタンの Click イベント ハンドラーに ADO.NET コードを記述します。または、ページに SqlDataSource コントロールを追加し、InsertCommand を構成した後 Click のイベント ハンドラーから Insert メソッドを呼び出します。または、新しいゲストブック コメントを挿入する中間層を構築し、Click のイベント ハンドラーからこの機能を呼び出します。 手順 3 で SqlDataSource の使い方を見たので、ここでは ADO.NET コードを使用します。

Note

Microsoft SQL Server データベースからプログラムでデータにアクセスするために使用される ADO.NET クラスは、名前空間にあります System.Data.SqlClient。 この名前空間をページの分離コード クラス (つまり using System.Data.SqlClient;) にインポートする必要がある場合があります。

PostCommentButtonClick イベントのイベント ハンドラーを作成し、次のコードを追加します。

protected void PostCommentButton_Click(object sender, EventArgs e)
{
     if (!Page.IsValid)
          return;
 
     // Determine the currently logged on user's UserId
     MembershipUser currentUser = Membership.GetUser();
     Guid currentUserId = (Guid)currentUser.ProviderUserKey;
 
     // Insert a new record into GuestbookComments
     string connectionString = 
          ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
     string insertSql = "INSERT INTO GuestbookComments(Subject, Body, UserId) VALUES(@Subject,
               @Body, @UserId)";
 
     using (SqlConnection myConnection = new SqlConnection(connectionString))
     {
          myConnection.Open();
          SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
          myCommand.Parameters.AddWithValue("@Subject", Subject.Text.Trim());
          myCommand.Parameters.AddWithValue("@Body", Body.Text.Trim());
          myCommand.Parameters.AddWithValue("@UserId", currentUserId);
          myCommand.ExecuteNonQuery();
          myConnection.Close();
     }
 
     // "Reset" the Subject and Body TextBoxes
     Subject.Text = string.Empty;
     Body.Text = string.Empty;
}

Click イベント ハンドラーは最初に、ユーザーが指定したデータが有効であることを確認します。 そうでない場合、イベント ハンドラーはレコードを挿入する前に終了します。 指定されたデータが有効である前提で、現在ログオンしているユーザーの UserId 値が取得され、ローカル変数 currentUserId に格納されます。 この値は、GuestbookComments にレコードを挿入するときに UserId 値を指定する必要があるために 必要です。

その後、Web.config から SecurityTutorials データベースの接続文字列が取得され、INSERT の SQL ステートメントが指定されます。 SqlConnection その後、オブジェクトが作成されて開きます。 すると、SqlCommand オブジェクトが構築され、INSERT クエリで使用されるパラメーターの値が割り当てられます。 その後、INSERT ステートメントが実行され、接続が閉じます。 イベント ハンドラーの最後に、Subject および Body TextBox の Text がクリアされることで、ユーザーの値がポストバック全体で保持されるのを防ぎます。

先に進み、ブラウザーでこのページをテストします。 このページは Membership フォルダー内にありますので、匿名の訪問者はアクセスできません。 そのため、まだログオンしていない場合は、最初にログオンする必要があります。 Subject TextBox と Body TextBox に値を入力し、PostCommentButton ボタンをクリックします。 これにより、新しいレコードが GuestbookComments に追加されます。 ポストバック時に、指定した件名と本文がテキストボックスから消去されます。

PostCommentButton ボタンをクリックした後、コメントがゲストブックに追加されたことを示す視覚的なフィードバックは発生しません。 既存のゲストブックコメントを表示するために、このページを更新する必要があります。これは手順 5 で行います。 これを完了すると、追加されたコメントがコメントの一覧に表示され、適切な視覚的フィードバックが行われます。 ここでは、GuestbookComments テーブルの内容を調べて、ゲストブックのコメントが保存されたことを確認します。

図 17 は、2 つのコメントが投稿された後の GuestbookComments テーブルの内容を示しています。

You Can See the Guestbook Comments in the GuestbookComments Table

図 17: GuestbookComments テーブルにゲストブックのコメントを表示できます (クリックするとフルサイズの画像を表示します)

Note

ユーザーが潜在的に危険なマークアップ (HTML など) を含むゲストブック コメントを挿入しようとすると、ASP.NET が HttpRequestValidationException をスローします。 この例外の詳細、スローされる理由、および危険な可能性のある値の送信をユーザーに許可する方法については、要求の検証に関するホワイトペーパーを参照してください。

手順 5: 既存のゲストブックのコメントを一覧表示する

コメントを残すことに加え、Guestbook.aspx ページにアクセスするユーザーもゲストブックの既存のコメントを閲覧できる必要があります。 これを実現するには、ページの下部にCommentList という名前の ListView コントロールを追加します。

Note

ListView コントロールは、ASP.NET バージョン 3.5 で初めて導入されました。 これは、非常にカスタマイズ可能かつ柔軟なレイアウトで項目の一覧を表示するように設計されており、GridView のような組み込みの編集、挿入、削除、ページング、並べ替え機能も備わっています。 ASP.NET 2.0 を使用している場合は、代わりに DataList または Repeater コントロールを使用する必要があります。 ListView の使用方法の詳細については、Scott Guthrie のブログ エントリ「asp:ListView コントロール」および「ListView コントロールを使用したデータの表示」を参照してください。

ListView のスマート タグを開き、[データ ソースの選択] ドロップダウン リストからコントロールを新しいデータ ソースにバインドします。 手順 2 と同様に、データ ソース構成ウィザードが起動します。 [データベース] アイコンを選択し、結果の SqlDataSource に CommentsDataSource と名前を付け、[OK] をクリックします。 次に、ドロップダウン リストから接続文字列 SecurityTutorialsConnectionString を選択し、[次へ] をクリックします。

手順 2 のこの時点で、ドロップダウン リストから UserProfiles テーブルを選択し、返す列を選択して、クエリするデータを指定しました (図 9 を参照)。 ただし、今回は、GuestbookComments のレコードだけでなく、投稿者のホーム タウン、ホームページ、署名、ユーザー名も返される SQL ステートメントを作成します。 そのため、[カスタム SQL ステートメントまたはストアド プロシージャを指定する] ラジオ ボタンを選択し、[次へ] をクリックします。

これにより、[カスタム ステートメントまたはストアド プロシージャの定義] 画面が表示されます。 [クエリ ビルダー] ボタンをクリックして、クエリをグラフィカルに作成します。 まず、クエリ ビルダーにより、クエリの対象となるテーブルを指定するように求められます。 テーブル GuestbookCommentsUserProfilesaspnet_Users を 選択し、[OK] をクリックします。 これにより、3 つのテーブルすべてがデザイン サーフェイスに追加されます。 テーブル GuestbookCommentsUserProfilesaspnet_Users には外部キー制約があるため、クエリ ビルダーは自動的に、これらのテーブルに JOIN を行います。

あとは、返す列を指定すると完了です。 GuestbookComments テーブルから、SubjectBodyCommentDate 列を選択すると、UserProfiles テーブルが HomeTownHomepageUrlSignature 列を返し、aspnet_UsersUserName を返します。 また、SELECT クエリの末尾に "ORDER BY CommentDate DESC" を追加して、最新の投稿が最初に返されるようにします。 これらの選択を行った後、クエリ ビルダー インターフェイスは図 18 のスクリーン ショットのようになります。

The Constructed Query JOINs the GuestbookComments, UserProfiles, and aspnet_Users Tables

図 18: 構築されたクエリ JOINGuestbookCommentsUserProfilesaspnet_Users テーブル (クリックするとフルサイズの画像を表示します)

[OK] をクリックしてクエリ ビルダー ウィンドウを閉じ、[カスタム ステートメントまたはストアド プロシージャの定義] 画面に戻ります。 [次へ] をクリックして [クエリのテスト] 画面に進み、[クエリのテスト] ボタンをクリックすると、クエリ結果を表示できます。 準備ができたら、[完了] をクリックしてデータ ソースの構成ウィザードを完了します。

手順 2 でデータ ソースの構成ウィザードを完了した時点で、関連付けられている DetailsView コントロールの Fields コレクションが更新され、SelectCommand から返される各列の BoundField が含まれるようになりました。 ただし、ListView は変更されません。レイアウトを定義する必要があります。 ListView のレイアウトは、宣言型マークアップで構築するか、スマート タグの [ListView の構成] オプションから手動で構築することができます。 私は普段、手動でマークアップを定義することを好みますが、あなたにとって最も自然な方法を使用してください。

私は自分の ListView コントロールに、次の LayoutTemplateItemTemplateItemSeparatorTemplate を使用しました。

<asp:ListView ID="CommentList" runat="server" DataSourceID="CommentsDataSource">
     <LayoutTemplate>
          <span ID="itemPlaceholder" runat="server" />
          <p>
               <asp:DataPager ID="DataPager1" runat="server">
                    <Fields>
                         <asp:NextPreviousPagerField ButtonType="Button" 
                              ShowFirstPageButton="True"
                              ShowLastPageButton="True" />
                    </Fields>
               </asp:DataPager>
          </p>
     </LayoutTemplate>
     <ItemTemplate>
          <h4><asp:Label ID="SubjectLabel" runat="server" 
               Text='<%# Eval("Subject") %>' /></h4>
          <asp:Label ID="BodyLabel" runat="server" 
               Text='<%# Eval("Body").ToString().Replace(Environment.NewLine, "<br />") %>' />
          <p>
               ---<br />
               <asp:Label ID="SignatureLabel" Font-Italic="true" runat="server"
                    Text='<%# Eval("Signature") %>' />
               <br />
               <br />
               My Home Town:
               <asp:Label ID="HomeTownLabel" runat="server" 
                    Text='<%# Eval("HomeTown") %>' />
               <br />
               My Homepage:
               <asp:HyperLink ID="HomepageUrlLink" runat="server" 
                    NavigateUrl='<%# Eval("HomepageUrl") %>' 
                    Text='<%# Eval("HomepageUrl") %>' />
          </p>
          <p align="center">
               Posted by
               <asp:Label ID="UserNameLabel" runat="server" 
                    Text='<%# Eval("UserName") %>' /> on
               <asp:Label ID="CommentDateLabel" runat="server" 
                    Text='<%# Eval("CommentDate") %>' />
          </p>
     </ItemTemplate>
     <ItemSeparatorTemplate>
          <hr />
     </ItemSeparatorTemplate>
</asp:ListView>

LayoutTemplate はコントロールによって出力されるマークアップを定義し、ItemTemplate はSqlDataSource によって返される各項目をレンダリングします。 ItemTemplate の結果となるマークアップは、LayoutTemplateitemPlaceholder コントロールに配置されます。 LayoutTemplate には、itemPlaceholder の他に、DataPager コントロールが含まれています。このコントロールでは、ListView がページあたり 10 件のゲストブック コメント (既定) のみを表示するように制限され、ページング インターフェイスがレンダリングされます。

ItemTemplate では、各ゲストブック コメントの件名が <h4> 要素に表示され、本文が件名の下に表示されます。 本文の表示に使用される構文は、Eval("Body") データバインド構文から返されるデータを受け取り、それを文字列に変換し、改行を <br /> 要素で置き換えることに注意してください。 空白は HTML で無視されるため、コメントの送信時に入力された改行を表示するには、この変換が必要です。 ユーザーの署名は、本文の下に斜体で表示され、その後にユーザーのホーム タウン、ホームページへのリンク、コメントが作成された日時、コメントを残したユーザーのユーザー名が表示されます。

少し時間をとり、ブラウザーでページを表示してください。 手順 5 でゲストブックに追加したコメントがここに表示されます。

Guestbook.aspx Now Displays the Guestbook's Comments

図 19: Guestbook.aspx にゲストブックのコメントが表示されるようになりました (クリックするとフルサイズの画像を表示します)

ゲストブックに新しいコメントを追加してみてください。 PostCommentButton ボタンをクリックすると、ページがポストバックを行い、コメントがデータベースに追加されますが、ListView コントロールは新しいコメントを表示するための更新を行いません。 これは、次のいずれかの方法で修正できます。

  • PostCommentButton ボタンの Click イベント ハンドラーを更新し、ListView コントロールの DataBind() メソッドがデータベースに新しいコメントが挿入された後に呼び出されるようにします。または、
  • ListView コントロールの EnableViewState プロパティを false に設定します。 この方法が有効な理由は、コントロールのビューの状態を無効にすることで、すべてのポストバックの基になるデータに再バインドする必要があるためです。

このチュートリアルからダウンロードできるチュートリアル Web サイトは、両方の手法を示しています。 Click イベント ハンドラーには、false に対する ListView コントロールの EnableViewState プロパティと、プログラムでデータを ListView に再バインドするために必要なコードがありますが、コメントアウトされています。

Note

現在の AdditionalUserInfo.aspx ページでは、ユーザーがホーム タウン、ホームページ、署名の設定を表示および編集できます。 AdditionalUserInfo.aspx を更新し、ログインしたユーザーのゲストブック コメントを表示すると便利でしょう。 つまり、ユーザーは自分の情報を調べて変更するだけでなく、AdditionalUserInfo.aspx ページにアクセスして、過去に行った自分のゲストブックのコメントを確認できます。 これは興味を持っている読者のための演習として残します。

手順 6: CreateUserWizard コントロールをカスタマイズしてホーム タウン、ホームページ、署名のインターフェイスを含める

Guestbook.aspx ページで使用される SELECT クエリは、INNER JOIN を使用して GuestbookCommentsUserProfilesaspnet_Users テーブルの関連するレコードを結合します。 UserProfiles にレコードのないユーザーがゲストブック コメントを作成しても、INNER JOINUserProfiles および aspnet_Users に一致するレコードがある場合のみ GuestbookComments を返すため、ListView にコメントは表示されません。 手順 3 で説明したように、ユーザーのレコードが UserProfiles にない場合、そのユーザーは AdditionalUserInfo.aspx ページでユーザー設定を閲覧または編集することはできません。

言うまでもなく、設計上の決定により、Membership システムのすべてのユーザー アカウントに一致するレコードが UserProfiles テーブルに含まれている必要があります。 必要なのは、CreateUserWizard を使用して新しいメンバーシップ ユーザー アカウントが作成されるたびに、対応するレコードを UserProfiles に追加することです。

ユーザー アカウントの作成」チュートリアルで説明したように、新しいメンバーシップ ユーザー アカウントが作成された後、CreateUserWizard コントロールによってCreatedUser イベントが発生します。 このイベントのイベント ハンドラーを作成し、先ほど作成されたユーザーの UserId を取得した後、UserProfiles テーブルにレコードを挿入します (HomeTownHomepageUrlSignature 列には、既定値が設定されます)。 さらに、追加のテキストボックスを含めて CreateUserWizard コントロールのインターフェイスをカスタマイズすることで、ユーザーにこれらの値の入力を求めることができます。

まず、CreatedUser イベント ハンドラーの UserProfiles テーブルに既定値を使用して新しい行を追加する方法を見てみましょう。 その後、CreateUserWizard コントロールのユーザー インターフェイスをカスタマイズして、新しいユーザーのホーム タウン、ホームページ、署名を収集する追加のフォーム フィールドを含める方法について説明します。

UserProfiles に規定の行を追加する

ユーザー アカウントの作成チュートリアルでは、Membership フォルダー内の CreatingUserAccounts.aspx ページに CreateUserWizard コントロールを追加しました。 CreateUserWizard コントロールでユーザー アカウントの作成時に UserProfiles テーブルにレコードを追加するには、CreateUserWizard コントロールの機能を更新する必要があります。 このチュートリアルでは、これらの変更を CreatingUserAccounts.aspx ページに加えるのではなく、新しい CreateUserWizard コントロールを EnhancedCreateUserWizard.aspx ページに追加し、変更を行います。

Visual Studio で EnhancedCreateUserWizard.aspx ページを開き、ツールボックスからページに CreateUserWizard コントロールをドラッグします。 CreateUserWizard コントロールの ID プロパティを NewUserWizard に設定します。 ユーザー アカウントの作成チュートリアルで説明したように、CreateUserWizard の既定のユーザー インターフェイスは、必要な情報を訪問者に求めます。 この情報が提供されると、コントロールは内部的に Membership フレームワークで新しいユーザー アカウントを作成します。ユーザーがコードを記述する必要はありません。

CreateUserWizard コントロールでは、ワークフロー中に多数のイベントが発生します。 訪問者が要求情報を入力してフォームを送信すると、CreateUserWizard コントロールは最初に CreatingUserイベントを発生させます。 作成プロセス中に問題が発生した場合、CreateUserError イベントが発生します。ただし、ユーザーが正常に作成された場合は、CreatedUser イベントが発生します。 ユーザー アカウントの作成チュートリアルでは、指定されたユーザー名に先頭または末尾のスペースが含まれていないことを確認し、そのユーザー名がパスワードのどこにも含まれないようにするために、CreatingUser イベントのイベント ハンドラーを作成しました。

先ほど作成したユーザーの UserProfiles テーブルに行を追加するには、CreatedUser イベントのイベント ハンドラーを作成する必要があります。 CreatedUser イベントが発生した時点で、ユーザー アカウントは既に Membership フレームワークに作成されているため、そのアカウントの UserId 値を取得できます。

次に、NewUserWizardCreatedUser イベントのイベント ハンドラーを作成し、次のコードを追加します。

protected void NewUserWizard_CreatedUser(object sender, EventArgs e)
{
     // Get the UserId of the just-added user
     MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
     Guid newUserId = (Guid)newUser.ProviderUserKey;
 
     // Insert a new record into UserProfiles
     string connectionString = 
          ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
     string insertSql = "INSERT INTO UserProfiles(UserId, HomeTown, HomepageUrl,
          Signature) VALUES(@UserId, @HomeTown, @HomepageUrl, @Signature)";
 
     using (SqlConnection myConnection = new SqlConnection(connectionString))
     {
          myConnection.Open();
          SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
          myCommand.Parameters.AddWithValue("@UserId", newUserId);
          myCommand.Parameters.AddWithValue("@HomeTown", DBNull.Value);
          myCommand.Parameters.AddWithValue("@HomepageUrl", DBNull.Value);
          myCommand.Parameters.AddWithValue("@Signature", DBNull.Value);
          myCommand.ExecuteNonQuery();
          myConnection.Close();
     }
}

上記のコードはまず、追加されたユーザー アカウントの UserId を取得します。 これを行うには、Membership.GetUser(username) メソッドを使用して特定のユーザーに関する情報を返してから、ProviderUserKey プロパティを使用して UserId を取得します。 CreateUserWizard コントロールでユーザーが入力したユーザー名は、その UserName プロパティを介して利用可能になります。

次に、Web.config から接続文字列が取得され、INSERT ステートメントが指定されます。 必要な ADO.NET オブジェクトがインスタンス化され、コマンドが実行されます。 このコードは DBNull インスタンスをパラメーター @HomeTown@HomepageUrl@Signature に割り当てます。これにより、HomeTownHomepageUrlSignature フィールドにデータベースの NULL 値が挿入されます。

ブラウザーから EnhancedCreateUserWizard.aspx ページにアクセスし、新しいユーザー アカウントを作成します。 その後、Visual Studio に戻り、aspnet_Users テーブルと UserProfiles テーブルの内容を確認します (以前と同様に。図 12 参照)。 aspnet_Users に新しいユーザー アカウントが表示され、対応する UserProfiles 行が表示されます (HomeTownHomepageUrlSignature には NULL の値)。

A New User Account and UserProfiles Record Have Been Added

図 20: 新しいユーザー アカウントと UserProfiles レコードが追加されました (クリックするとフルサイズの画像を表示します)

訪問者が新しいアカウント情報を入力し、[ユーザーの作成] ボタンをクリックすると、ユーザー アカウントが作成され、UserProfiles テーブルに行が追加されます。 次に、CreateUserWizard に CompleteWizardStep が表示され、成功メッセージと [続行] ボタンが表示されます。 [続行] ボタンをクリックするとポストバックが発生しますが、アクションは実行されません。ユーザーは EnhancedCreateUserWizard.aspx ページに残ったままです。

CreateUserWizard コントロールの ContinueDestinationPageUrl プロパティを介して、[続行] ボタンがクリックされたときに、ユーザーが移動する先の URL を指定できます。 ContinueDestinationPageUrl プロパティを "~/Membership/AdditionalUserInfo.aspx" に設定します。 これにより、新しいユーザーが AdditionalUserInfo.aspx に移動し、設定を表示、更新できるようになります。

CreateUserWizard のインターフェイスをカスタマイズして、新しいユーザーのホーム タウン、ホームページ、署名を要求する

CreateUserWizard コントロールの既定のインターフェイスは、ユーザー名、パスワード、電子メールなどの主要なユーザー アカウント情報のみを収集する必要があるシンプルなアカウント作成シナリオには十分です。 しかし、アカウントの作成中に、訪問者にホームタウン、ホームページ、署名の入力を求める場合はどうでしょうか。 サインアップ時に追加情報を収集するように CreateUserWizard コントロールのインターフェイスをカスタマイズできます。この情報は、CreatedUser のイベント ハンドラーで使用して、基になるデータベースに追加のレコードを挿入できます。

CreateUserWizard コントロールは、ASP.NET ウィザード コントロールを拡張します。このコントロールでは、ページ開発者が順序付けされた WizardSteps を定義できます。 ウィザード コントロールは、アクティブなステップをレンダリングし、訪問者がこれらの手順を移動できるようにするナビゲーション インターフェイスを提供します。 ウィザード コントロールは、長いタスクをいくつかの短い手順に分割するのに最適です。 ウィザード コントロールの詳細については、「ASP.NET 2.0 ウィザード コントロールを使用したステップ バイ ステップ ユーザー インターフェイスの作成」を参照してください。

CreateUserWizard コントロールの既定のマークアップでは、2 つの WizardSteps を定義します: CreateUserWizardStepCompleteWizardStep

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

1 つ目の WizardStep である CreateUserWizardStep は、ユーザー名、パスワード、電子メールなどを求めるインターフェイスをレンダリングします。 訪問者がこの情報を入力し、[ユーザーの作成] をクリックすると、CompleteWizardStep が表示され、成功メッセージと [続行] ボタンが表示されます。

CreateUserWizard コントロールのインターフェイスをカスタマイズして、追加のフォーム フィールドを含めるには、次の操作を行います。

  • 新しく 1 つ以上の WizardStep を作成し、追加のユーザー インターフェイス要素を含めます。 CreateUserWizard に新規 WizardStep を追加するには、スマート タグの [WizardSteps の追加と削除] リンクをクリックして WizardStep コレクション エディターを起動します。 そこから、ウィザードの手順を追加、削除、並べ替えを行えます。 このチュートリアルではこのアプローチを使用します。

  • CreateUserWizardStep を編集可能な WizardStep に変換します。これにより、CreateUserWizardStepWizardStep に置き換えられ、そのマークアップは CreateUserWizardStep に一致するユーザー インターフェイスを定義します。 CreateUserWizardStepWizardStep に変換すると、コントロールの位置を変更したり、このステップにユーザー インターフェイス要素を追加したりできます。 CreateUserWizardStep または CompleteWizardStep を編集可能な WizardStep に変換するには、コントロールのスマート タグから [ユーザー ステップの作成のカスタマイズ] または [ステップ全体のカスタマイズ] リンクをクリックします。

  • 上記の 2 つのオプションを組み合わせて使用します。

注意すべき重要な点の 1 つは、CreateUserWizardStep 内から [ユーザーの作成] ボタンをクリックしたときに、CreateUserWizard コントロールがユーザー アカウントの作成プロセスを実行することです。 CreateUserWizardStep の後にさらに WizardStep があるかどうかは関係ありません。

CreateUserWizard コントロールにカスタム WizardStep を追加して追加のユーザー入力を収集する場合、カスタム WizardStepCreateUserWizardStep の前にも後にも置けます。 CreateUserWizardStep の前に置く場合は、追加のユーザー入力は CreatedUser イベント ハンドラーで使用可能なカスタム WizardStep から収集されます。 ただし、カスタム WizardStepCreateUserWizardStep の後にある場合は、カスタム WizardStep が表示されるまでに、新しいユーザー アカウントが作成済みであり、CreatedUser イベントが発生済みとなります。

図 21 は、追加された WizardStepCreateUserWizardStep よりも前にある場合のワークフローを示しています。 追加のユーザー情報は CreatedUser イベントが発生する時点で収集されているため、これらの入力を取得し、DBNull.Value ではなく、INSERT ステートメントのパラメーター値に使用するように CreatedUser イベント ハンドラーを更新するだけで済みます。

The CreateUserWizard Workflow When an Additional WizardStep Precedes the CreateUserWizardStep

図 21: 追加の WizardStepCreateUserWizardStep に先行する場合の CreateUserWizard ワークフロー (クリックするとフルサイズの画像を表示します)

ただし、CreateUserWizardStepにカスタム WizardStep を配置した場合は、ユーザー アカウント作成プロセスはユーザーがホーム タウン、ホームページ、または署名を入力することになる前に行われます。 このような場合は、図 22 に示すように、ユーザー アカウントの作成後に、この追加情報をデータベースに挿入する必要があります。

The CreateUserWizard Workflow When an Additional WizardStep Comes After the CreateUserWizardStep

図 22: 追加の WizardStepCreateUserWizardStep の後になる場合の CreateUserWizard ワークフロー (クリックするとフルサイズの画像を表示します)

図 22 に示すワークフローは、手順 2 が完了するまで、UserProfiles テーブルへのレコードの挿入を待機します。 ただし、手順 1 の後に訪問者がブラウザーを閉じると、ユーザー アカウントが作成された状態に達しますが、UserProfiles にレコードは追加されません。 回避策の 1 つは、NULL または既定値を持つレコードを CreatedUser イベント ハンドラーの UserProfiles に挿入してから (手順 1 後に発生)、手順 2 の完了後にこのレコードを更新することです。 これにより、ユーザーが途中で登録プロセスを終了した場合でも、ユーザー アカウントの UserProfiles レコードが追加されます。

このチュートリアルでは CreateUserWizardStep の後、CompleteWizardStep の前に発生する WizardStep を新しく作成します。 まず WizardStep を配置して構成してから、コードを見てみましょう。

CreateUserWizard コントロールのスマート タグから、[WizardStep の追加と削除] を選択すると、[WizardStep コレクション エディター] ダイアログが表示されます。 WizardStep を新しく追加し、その IDUserSettingsTitle を "設定"、StepTypeStep に設定します。 次に、図 23 に示すように、CreateUserWizardStep ("新しいアカウントにサインアップ") の後であり、CompleteWizardStep ("完了") の前になるように配置します。

Add a New WizardStep to the CreateUserWizard Control

図 23: CreateUserWizard コントロールに新規 WizardStep を追加する (クリックするとフルサイズの画像を表示します)

[OK] をクリックして、[WizardStep コレクション エディター] ダイアログを閉じます。 新しい WizardStep コードは、CreateUserWizard コントロールの更新された宣言型マークアップによって証明されます。

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
               Title="Your Settings">
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

新しい <asp:WizardStep> 要素に注意してください。 ここでは、新しいユーザーのホーム タウン、ホームページ、署名を収集するためのユーザー インターフェイスを追加する必要があります。 このコンテンツは、宣言構文またはデザイナーで入力できます。 デザイナーを使用するには、スマート タグのドロップダウン リストから [設定] ステップを選択して、デザイナーにステップを表示します。

Note

スマート タグのドロップダウン リストからステップを選択すると、開始ステップのインデックスを指定する CreateUserWizard コントロールの ActiveStepIndex プロパティが更新されます。 そのため、このドロップダウン リストを使用してデザイナーの [設定] ステップを編集する場合は、ユーザーが初めて EnhancedCreateUserWizard.aspx ページにアクセスしたときにこの手順が表示されるように、必ず "新しいアカウントにサインアップ" に戻してください。

"設定" ステップ内に、HomeTownHomepageUrlSignature という 3 つの TextBox コントロールを含むユーザー インターフェイスを作成します。 このインターフェイスを構築した後、CreateUserWizard の宣言型マークアップは次のようになります。

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
               Title="Your Settings">
               <p>
                    <b>Home Town:</b><br />
                    <asp:TextBox ID="HomeTown" runat="server"></asp:TextBox>
               </p>
               <p>
                    <b>Homepage URL:</b><br />
                    <asp:TextBox ID="HomepageUrl" Columns="40" runat="server"></asp:TextBox>
               </p>
               <p>
                    <b>Signature:</b><br />
                    <asp:TextBox ID="Signature" TextMode="MultiLine" Width="95%"
                         Rows="5" runat="server"></asp:TextBox>
               </p>
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

先に進み、ブラウザーからこのページにアクセスし、ホーム タウン、ホームページ、署名の値を指定して、新しいユーザー アカウントを作成します。 CreateUserWizardStep が完了すると、ユーザー アカウントがMembership フレームワークで作成され、CreatedUser イベント ハンドラーが実行され、UserProfiles に新しい行が追加されます。HomeTownHomepageUrlSignature のデータベース NULL 値が使用されます。 ホーム タウン、ホームページ、署名に入力された値は使用されません。 結果は、UserProfiles レコードを持つ新しいユーザー アカウントであり、フィールド HomeTownHomepageUrlSignature はまだ入力されていない状態です。

ユーザーが入力したホーム タウン、ホームページ、署名の値を取得し、適切な UserProfiles レコードを更新する "設定" ステップの後にコードを実行する必要があります。 ユーザーがウィザード コントロールのステップ間を移動するたびに、ウィザードの ActiveStepChanged イベント が発生します。 "設定" ステップが完了したら、このイベントのイベント ハンドラーを作成し、UserProfiles テーブルを更新できます。

CreateUserWizard の ActiveStepChanged イベント用イベント ハンドラーを追加し、次のコードを追加します。

protected void NewUserWizard_ActiveStepChanged(object sender, EventArgs e)
{
     // Have we JUST reached the Complete step?
     if (NewUserWizard.ActiveStep.Title == "Complete")
     {
          WizardStep UserSettings = NewUserWizard.FindControl("UserSettings") as
          WizardStep;
 
          // Programmatically reference the TextBox controls
          TextBox HomeTown = UserSettings.FindControl("HomeTown") as TextBox;
          TextBox HomepageUrl = UserSettings.FindControl("HomepageUrl") as TextBox;
          TextBox Signature = UserSettings.FindControl("Signature") as TextBox;
 
          // Update the UserProfiles record for this user
          // Get the UserId of the just-added user
          MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
          Guid newUserId = (Guid)newUser.ProviderUserKey;
 
          // Insert a new record into UserProfiles
          string connectionString = 
               ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
          string updateSql = "UPDATE UserProfiles SET HomeTown = @HomeTown, HomepageUrl
               = @HomepageUrl, Signature = @Signature WHERE UserId = @UserId";
 
          using (SqlConnection myConnection = new SqlConnection(connectionString))
          {
               myConnection.Open();
               SqlCommand myCommand = new SqlCommand(updateSql, myConnection);
               myCommand.Parameters.AddWithValue("@HomeTown", HomeTown.Text.Trim());
               myCommand.Parameters.AddWithValue("@HomepageUrl", HomepageUrl.Text.Trim());
               myCommand.Parameters.AddWithValue("@Signature", Signature.Text.Trim());
               myCommand.Parameters.AddWithValue("@UserId", newUserId);
               myCommand.ExecuteNonQuery();
               myConnection.Close();
          }
     }
}

上記のコードはまず、"完了" ステップに達したかどうかを判断します。 "設定" ステップの直後に "完了" ステップが行われるため、訪問者が "完了" ステップに達した場合、ユーザーは "設定" ステップを完了したことになります。

このような場合は、プログラムで UserSettings WizardStep 内の TextBox コントロールを参照する必要があります。 これを実現するには、最初に FindControl メソッドを使用してプログラムで UserSettings WizardStep を参照し、次にもう一度 WizardStep 内のテキストボックスを参照します。 テキストボックスが参照されたら、UPDATE ステートメントを実行する準備が整います。 UPDATE ステートメントには CreatedUser イベント ハンドラーの INSERT ステートメントと同じ数のパラメーターがありますが、ここでは、ユーザーが指定するホーム タウン、ホームページ、および署名の値を使用します。

このイベント ハンドラーが配置された状態で、ブラウザーから EnhancedCreateUserWizard.aspx ページにアクセスし、ホーム タウン、ホームページ、署名の値を指定して新しいユーザー アカウントを作成します。 新しいアカウントを作成すると、AdditionalUserInfo.aspx ページにリダイレクトされます。ここで、先ほど入力したホーム タウン、ホームページ、署名の情報が表示されます。

Note

ウェブサイトには現在、CreatingUserAccounts.aspxEnhancedCreateUserWizard.aspx の 2 つのページで訪問者が新しいアカウントを作成できます。 Web サイトマップとログイン ページは CreatingUserAccounts.aspx ページを指していますが、CreatingUserAccounts.aspx ページではユーザーのホーム タウン、ホームページ、署名の情報が求められず、UserProfiles に対応する行が追加されません。 したがって、この機能を提供するために CreatingUserAccounts.aspx ページを更新するか、CreatingUserAccounts.aspx ではなく EnhancedCreateUserWizard.aspx を参照するようにサイトマップおよびログイン ページを更新します。 後者のオプションを選択する場合は、匿名ユーザーが EnhancedCreateUserWizard.aspx ページにアクセスできるように、Membership フォルダーの Web.config ファイルを必ず更新してください。

まとめ

このチュートリアルでは、Membership フレームワーク内のユーザー アカウントに関連するデータをモデル化する手法について説明しました。 特に、ユーザー アカウントと一対多リレーションシップを共有するモデリング エンティティと、一対一リレーションシップを共有するデータについて説明しました。 さらに、SqlDataSource コントロールを使用した例や、ADO.NET コードを使用した他の例を使用して、この関連情報を表示、挿入、更新する方法を確認しました。

これで、ユーザー アカウントに目を向けたチュートリアルを終了します。 次のチュートリアルからは、ロールに注目します。 次のいくつかのチュートリアルでは、Roles フレームワーク、新しいロールの作成方法、ユーザーにロールを割り当てる方法、ユーザーが属するロールを判別する方法、ロールベースの承認を適用する方法について説明します。

プログラミングに満足!

もっと読む

この記事で説明したトピックの詳細については、次のリソースを参照してください。

作成者について

複数の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 Scott には、mitchell@4guysfromrolla.com または http://ScottOnWriting.NET のブログを介して連絡できます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 今後の MSDN の記事を確認することに関心がありますか? その場合は、mitchell@4GuysFromRolla.com までご一報ください。