WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

MVC4 インターネットアプリケーション

by WebSurfer 23. July 2014 17:11

世の中はすでに VS2013 + MVC5 の時代で、今さら VS2010 + MVC4 の話でもなさそうですが、せっかく先日自分の開発環境の VS2010 で MVC4 を使えるようしましたので、手始めに「インターネットアプリケーション」テンプレートを使って MVC4 Web アプリを作ってみました。下の画像がその初期画面 (Home/Index ページ) です。

ASP.NET MVC 4 インターネットアプリケーション

テンプレートメニューの「インターネットアプリケーション」というのは、フォーム認証を使用するインターネット向け ASP.NET MVC4 プロジェクトの骨格を自動生成するためのものです。

先の 記事 で書きましたように、MVC4 にはいろいろ新機能が追加されていますが、「インターネットアプリケーション」テンプレートで作る Web アプリはあまり関係なさそうです。

ただし、ASP.NET MVC 4 リリースノート に書かれている New Features の内、以下の 2 つの新機能は「インターネットアプリケーション」にも取り入れられてます。

その内、後者のフォーム認証関係についていろいろ調べて分かったことがありますので備忘録として書いておきます。前者の Bundling and Minification についてはまだ勉強中なので、後日改めて書く予定です。調べる気力が無くならなければですが・・・(笑)

なお、VS2013 + MVC5 では ASP.NET Identity という新しいシステムに変更になっており、それと Simple Membership を含む従来のメンバーシッププロバイダは互換性がないようです。

ということは、MVC4 で Simple Membership を使ったのは一時しのぎ的な処置で、この記事を書くために一生懸命(?)調べた以下のことは、あまり今後の役には立たないのかもしれません。(泣)

(1) ユーザー情報のストア

フォーム認証用のユーザー情報のストアとして使用されるデータベースは、Simple Membership(ローカルアカウント用)と OAuth / OpenID(Facebook, Google などの外部アカウント用)が共用できるものに変わりました。SQL Server Management Studio でそのデータベース見ると、以下のようになってます。

自動生成されたデータベース

MVC3 までは、VS2010 のメニューバー[プロジェクト(P)]⇒[ASP.NET 構成(T)]で起動できる[ASP.NET Web サイト管理ツール]を使用して、アプリケーションルート直下の App_Data フォルダに ASPNETDB.MDF という名前の SQL Server 用データベースファイルを作り、これに SQL Server Express の ユーザーインスタンス を利用して接続し、SqlMemberShipProvider 経由でアクセスしていました。(注:あくまで開発環境でのデフォルトの話です)

MVC4 では、データベースは Entity Framework Code First の機能と、WebSecurity クラスの機能を利用して自動生成されます。なので、MVC4 のデフォルトのフォーム認証システムを使う限り[ASP.NET Web サイト管理ツール]は使いません。(使うことは可能ですが)

自分の開発環境(Vista SP2, VS2010 Professional, ASP.NET 開発サーバー, SQL Server 2008 Express。 LocalDB は利用不可)で試してみました。

まず、VS2010 の「インターネットアプリケーション」テンプレートで MVC4 Web アプリを作った時点で、以下のような接続文字列がアプリケーションルート直下の web.config に自動的に設定されます。ちなみに、データベース名 (Initial Catalog) は aspnet-<アプリケーション名>-<作成日時> となるようです。

<connectionStrings>
  <add name="DefaultConnection" 
    connectionString="Data Source=.\SQLEXPRESS;
        Initial Catalog=aspnet-Mvc4App-20140609121802;
        Integrated Security=SSPI" 
    providerName="System.Data.SqlClient" />
</connectionStrings>

この時点では接続文字列が作られるだけで、まだデータベース本体は作られていません。

その後、Web アプリを ASP.NET 開発サーバー上で立ち上げてブラウザに表示し、画面の右上にある[登録]または[ログイン]ボタンをクリックすると、Web アプリが上記の接続文字列をベースに SQL Server Express のインスタンスに接続し、Instid\MSSQL\DATA フォルダにデータベースを作成してアタッチします。(上の接続文字列の通り、ユーザーインスタンスではなく、名前つきインスタンス .\SQLEXPRESS に接続して利用できるようになります)

データベースの自動生成のメカニズムは以下の通りです。

データベースの生成のためのコードは Filters フォルダの中の InitializeSimpleMembershipAttribute.cs クラスファイルに含まれています。そこにはアクションフィルターとして InitializeSimpleMembershipAttribute クラスが定義されいて、それがコントローラーの AccountController クラスの属性として付与されています。

そのアクションフィルターにより、AccountController クラスのアクションメソッドの実行前に LazyInitializer.EnsureInitialized メソッド が呼び出され、それによって以下の SimpleMembershipInitializer クラスが初期化されるようになっています。

private class SimpleMembershipInitializer
{
  public SimpleMembershipInitializer()
  {
    Database.SetInitializer<UsersContext>(null);

    try
    {
      using (var context = new UsersContext())
      {
        if (!context.Database.Exists())
        {
          // Create the SimpleMembership database without Entity 
          // Framework migration schema
          ((IObjectContextAdapter)context).ObjectContext.
              CreateDatabase();
        }
      }

      WebSecurity.InitializeDatabaseConnection(
                        "DefaultConnection", 
                        "UserProfile", 
                        "UserId", 
                        "UserName", 
                        autoCreateTables: true);
    }
    catch (Exception ex)
    {
      throw new InvalidOperationException("The ASP.NET ...", ex);
    }
  }
}

従って、ブラウザの画面で[登録]や[ログイン」がクリックされると、この時点で SimpleMembershipInitializer がまだ初期化されていなければ、上記コードのコンストラクタを使って初期化を行います。

その際、接続文字列で指定される SQL Server のインスタンスにデー��ベースやテーブルが存在しない場合は必要に応じて作成します。まず ObjectContext.CreateDatabase メソッドがデータベースと UserProfile テーブルを、次に WebSecurity.InitializeDatabaseConnection メソッド が webpages_Membership, webpages_OAuthMembership, webpages_Roles, webpages_UsersInRoles テーブルを作成します。(実際に動かしてその順序で生成されることを確認しました)

Microsoft のチュートリアル Entity Framework での新しいデータベース向けの Code First によると、"ローカルの SQL Server Express インスタンスを使用できる場合、Code First ではそのインスタンス上にデータベースが作成されます。SQL Server Express を使用できない場合、Code First では LocalDb の使用を試みます。" とのことです。

ということは、VS2010 が開発環境における SQL Server Express インスタンスの有無を判定して、そのインスタンス上にデータベースを作成できるように接続文字列を生成したということのようです。

開発環境に SQL Server Express がなく、LocalDB が使用できる場合は、LocalDB 用に .mdf ファイルが自動生成されて既定のフォルダに置かれ、接続文字列も LocalDB 用の設定になるそうです・・・が、自分は検証できる環境を持ってないので、その確認はできていませんけど。

(2) Simple Membership

ローカルアカウント(FaceBook, Google などの外部アカウントでない方)のフォーム認証に使用するメンバーシッププロバイダには、MVC3 まででデフォルトで使用されていた SqlMembershipProvider ではなく、SimpleMembershipProvider が使用されます。

想像ですが、Simple Membership を使った理由は、OAuth / OpenId による認証と併用するのに、シンプルな分相性が良いためではないかと思います。

もともと、SimpleMembershipProvider は、WebMatrix という開発環境を利用して作る Web Pages アプリに用いられるフォーム認証用のメンバーシッププロバイダで、SqlMembershipProvider など従来のメンバーシッププロバイダの簡易版ということのようです。

従来のメンバーシッププロバイダと同様に MembershipProvider 抽象クラス を継承しており、Membership クラス 経由でアクセスできますが、MembershipProvider クラスに定義されている全てのプロパティ、メソッドが実装されているわけではなく、実装されていないメンバーにアクセスすると例外がスローされるそうです。

なので、Web アプリでは直接 SimpleMembershipProvider を操作する(実際には Membership クラス経由の操作になりますが)のではなく、WebSecurity ヘルパークラスを使用することが推奨されています。

実際、テンプレートで自動生成されるコードでは WebSecurity ヘルパークラスを使用して、ユーザーの登録、ログイン、ログオフに必要なアクションメソッドとビューが実装されています。

(3) ロール

ロール(例:マネージャー、ゲスト、管理者、メンバーなどのグループ分け)による承認・アクセスコントロールも、もちろん利用できます。

ただし、テンプレートで作成する Web アプリには、ロールの作成・削除、ユーザーのロールへの割当・削除を行うためのアクションメソッドやビューは実装されていません。なので、ロールを利用するためには必要なコードを自力で書いて、Web アプリに追加する必要があります。

上に書いた WebSecurity クラス、SimpleMembershipProvider にはロールを操作する機能は含まれません。

ロールの操作は、SimpleRoleProvider クラス を利用してデータベースにアクセスして行います。ただし、SimpleRoleProvider に直接アクセスするのではなく、Roles クラス を使用します。

具体的なコード例は Working with Roles in ASP.NET MVC 4+ が参考になると思います。

そこには、Roles.GetAllRoles, Roles.CreateRole, Roles.DeleteRole, Roles.IsUserInRole, Roles.AddUserToRole, Roles.GetRolesForUser, Roles.RemoveUserFromRole というメソッドを使って、ロールの作成・削除、ユーザーのロールへの割当・削除を行うためのアクションメソッドやビューのサンプルが記載されています。

自分でもそのページを参考にロールの操作のためのアクションメソッドとビューを実装してみました。下の画像は、Admin, Member, Moderator という 3 つのロールを作成した結果を示します。

設定されたロール

作成したロールは、ローカルアカウントのユーザーはもちろん、FaceBook, Google などの外部アカウントのユーザーにもアサインできます。アサインした結果は webpages_UsersInRoles テーブルに保存されます。

上記のアクションメソッドやビューを操作しなくても、Web サイトを起動するときに、管理者ロールとそれに属するユーザーを自動的に作ってしまう方法もあります。その具体例は Seeding & Customizing ASP.NET MVC SimpleMembership が参考になると思います。

(4) プロファイル

プロファイルとは、例えば登録済みユーザーの名前、住所、メールアドレスといった情報を格納・管理するための機能です。MVC4 のフォーム認証システムでも利用できるようになっています。

ただし、従来のように SqlProfileProvider を使って Web アプリケーションプロジェクトでプロファイル 情報を管理するのとはかなりメカニズムが異なります。

MVC4 の「インターネットアプリケーション」テンプレートで作る Web アプリでは、プロファイル情報の格納には、自動生成されるデータベース(詳しは、上の「(1) ユーザー情報のストア」のセクションを参照ください)の UserProfile テーブルを使用できます。

UserProfile テーブルが生成された時点では UserId と UserName というフィールドしかありませんが(この記事の上から 2 番目の画像を見てください)、これに必要な情報を追加できます。

例えば、ユーザーの email 情報をプロファイルに追加するとします。

それにはまず、Models フォルダの AccountModels.cs クラスファイルの中の UserProfile クラスのコードに Email プロパティの定義を追加します。以下のような感じです。

[Table("UserProfile")]
public class UserProfile
{
  [Key]
  [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
  public int UserId { get; set; }
  public string UserName { get; set; }

  // Email プロパティを追加。
  public string Email { get; set; }
}

次に、データベース内の UserProfile テーブルに Email 列を追加します。Entity Framework Code First Migrations 機能を使うと SQL Server のデータベースを直接操作しなくても列の追加ができるそうです。

ただし、自分は Migrations 機能の使い方が分からなかったので、SQL Server Management Studio で直接 UserProfile テーブルにアクセスし、Email 列を追加しました。(笑) それだけで Email 列にアクセスできるようになります。(EDM の再構築が必要かと思ってましたが、自動的に処置されるようで、自分では何もする必要はなかったです)

データには以下のようなコードでアクセスできます。

using (UsersContext context = new UsersContext())
{
  string username = User.Identity.Name;
  UserProfile user = context.UserProfiles.SingleOrDefault(
                             u => u.UserName == username);
  string email = user.Email;
}

Facebook などの外部アカウントを使用する場合、プロバイダによって提供されるユーザー情報 が異なるのが問題です。なので、UserProfile テーブルを外部アカウントのユーザー情報のストアに共用するのは難しそうです。

外部アカウントのユーザー情報は UserProfile テーブルとは別のテーブルを作ってそこに格納するという方法もあります。具体的な方法は、Facebook の例ですが、Microsoft ASP.NET サイトの記事 Using OAuth Providers with MVC 4 が参考になると思います。

(5) OAuth / OpenId

MVC4 には、OAuth 認証または OpenId 認証を利用して、Facebook, Twitter, Google, Microsoft のアカウントを使ってログインできる機能が追加されています。

詳しい手順は以下のページが参考になると思います:

自分でも上のページを参考に Facebook と Google で試してみました。

Google の場合は登録やキーの取得など事前準備は何も必要ありません。Facebook の場合は開発者としての登録、アプリケーションの登録、キーの入手の入手が必要です。MSDN Blog で紹介されている内容(古いようです)とはかなり画面が異なりますが基本は同じで、特に難しいことはないと思います。

Facebook からキーが入手できたら AuthConfig.cs ファイルの AuthConfig クラスの定義に以下のように設定してやります。Google の方はコメントアウトされた OAuthWebSecurity.RegisterGoogleClient(); を有効するだけで OK です。

namespace Mvc4App
{
  public static class AuthConfig
  {
    public static void RegisterAuth()
    {
      //OAuthWebSecurity.RegisterMicrosoftClient(
      //    clientId: "",
      //    clientSecret: "");

      //OAuthWebSecurity.RegisterTwitterClient(
      //    consumerKey: "",
      //    consumerSecret: "");

      OAuthWebSecurity.RegisterFacebookClient(
        appId: "14632444xxxxxxxx",
        appSecret: "bcc92eee6910218f0646ae3fxxxxxxxx");

      OAuthWebSecurity.RegisterGoogleClient();
    }
  }
}

この後、Visual Studio から Web アプリを起動して、初期画面の右上にある[ログイン]ボタンをクリックすると、以下のようなログイン画面が表示され、Facebook と Google のアカウントを使ってのログインが有効になっているのが分かります。

ログイン画面

上の画面で[Facebook]または[Google]ボタンをクリックすると、そのプロバイダのログイン画面が現れます。Facebook の場合は下の画像のようになります。(以下、すべて Facebook の例です)

Facebook のログイン画面

上記の画面でユーザーが ID(メールアドレス)とパスワードを入力してログインし、外部プロバイダの認証が通ると、Facebook から Web アプリにユーザー情報を渡して良いか否かを確認するためのページが表示されます。下の画面のようになります。

ユーザー情報提供の確認画面

上の画面で[OK]をクリックすると、Facebook からユーザー情報が Web アプリに渡され、ユーザー情報を Web アプリに登録するため、下の画面が表示されます。

ユーザー情報の登録画面

上の画像で、[ユーザー名]テクストボックスに Web アプリで使いたい任意の名前を入力し、[登録]ボタンをクリックすると、Web アプリのデータベースの UserProfile, webpages_OAuthMembership テーブルにユーザー情報が登録されます(初回のみ)。

一旦登録されれば、次回からはプロバイダのログイン画面でログインすれば、上に書いた確認や登録のページは飛ばして、Web アプリ上でユーザー認証が完了します。

(6) Database First の EDM との共存

上に書きましたように、ユーザー情報のストアに使われるデータベースは Entity Framework Code First の機能を利用して作られます。これと Database First で作られた EDM は同じアセンブリ内で共存できないと言う問題があります。(少なくとも Ver. 4 では。最新版は不明です)

例えば、10 行でズバリ!! ASP.NET MVC におけるデータの取得から画面表​示までの流れ (C#) のような感じで、既存のデータベースのデータを表示、更新するため Entity Framework を用いて Database First で EDM を作成して追加したとし���す。

その後、ログインしようとすると "System.ArgumentException: 'Mvc4App.Models.Address' の概念モデルの型が見つかりませんでした。" というエラーが出ます。Mvc4App.Models.Address は Database First で作ったモデルです。

エラー画面

上の画像にあるように、例外がスローされた場所は AccountController です。

Mvc4App.Models.Address とは何ら関係ないのに、何故そのようなエラーがそんな場所で出るのかまったく分かず、半日ぐらいハマってしまいました。(笑)

解決策は、別アセンブリに分ける、即ち Database First で作る EDM は別プロジェクトにすることです。

その際注意しなければならないのは、Web アプリがその EDM 経由でデータベースに接続に行くということです。例えば、ビューで foreach (var item in Model) というようにすると、そこでデーターベースに接続に行きます。

それゆえ、Web アプリの web.config に接続に用いる接続文字列が必要です。これの解決にも半日ぐらいハマってしまいました。(笑)

ちなみに、接続文字列がないと "指定された名前付き接続は、構成内に見つからないか、EntityClient プロバイダーと併用することを意図していないか、または無効です。" というエラーになります。

web.config に接続文字列があっても、それに指定された .mdf ファイルの場所が違っている場合は "ファイル ...\App_Data\AdventureWorksLT_Data.mdf の自動的に名前が付けられたデータベースをアタッチできませんでした。同じ名前のデータベースが既に存在するか、指定されたファイルを開けないか、UNC 共有に配置されています。" というエラーになります。(このケースでは、ユーザーインスタンスへの接続になっています)

Tags: ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2019  >>
MoTuWeThFrSaSu
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

View posts in large calendar