WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Server.Transfer と fragment 識別子

by WebSurfer 2014年8月3日 13:42

Server.Transfer メソッド を使って別のページに遷移する場合、fragment 識別子を使用して指定する場所にジャンプさせることができるでしょうか?

結論から言うとダメです。でも、それでは記事にならないので、何故ダメなのかの理由と、ジャンプさせるための別の方法を書きます。

(1) Server.Transfer で fragment 識別子を使用できない理由

html の a 要素や、ASP.NET でよく使われる Response.Redirect メソッド を使用する場合は問題ありません。

例えば、Response.Redirect メソッドを使う場合、以下のように fragment 識別子(#positionToMove の部分)を設定すれば、リダイレクト先の default2.aspx ページで html 要素の id または name 属性に positionToMove が指定されていれば(例: id="positionToMove")、その場所にジャンプします。

protected void Button1_Click(object sender, EventArgs e)
{
  Response.Redirect("~/default2.aspx#positionToMove", false);
}

これを Server.Transfer("~/default2.aspx#positionToMove") のようにすると HttpException がスローされ、"要求の種類 'POST' の http ハンドラーが見つかりませんでした。" というエラーメッセージが出ます。これは、.aspx#positionToMove という拡張子のファイルを処理して応答を返す HTTP ハンドラが見つからないということを言ってます。

何故このような違いが出るかは、Response.Redirect と Server.Transfer のサーバー側での処理の違いと、ブラウザでの fragment 識別子の扱いが分かると理解できると思います。

まず、Response.Redirect と Server.Transfer の違いですが、これは @IT の記事 第16回 ASP.NETにおけるページの遷移 (1/4) が参考になると思いますので見てください。

具体的には、上に書いたコードの例で言うと、(a) Response.Redirect はサーバーからブラウザがリダイレクト指示(HTTP 302 応答)を受け、ブラウザがサーバーに default2.aspx を GET 要求に行く、(b) Server.Transfer はサーバー側だけで処理が行われ default2.aspx#positionToMove を返そうとする・・・ところが大きな違いです。

上記 (a) で、HTTP 302 応答のヘッダには Location: /default2.aspx#positionToMove が含まれますが、それを受けたブラウザは #positionToMove を取り除いて GET /default2.aspx という要求をサーバーに出します。サーバーには .aspx ファイルを扱うハンドラはもちろん定義されていますので、HTTP ハンドラが見つからないなどという問題はなく、default2.aspx が処理されて応答が返ってきます。

ブラウザはジャンプ先の情報 #positionToMove を保持していますので、default2.aspx の応答が返ってきた後でその位置にジャンプすると言う仕組みになってます。

というわけで、そもそも Server.Transfer("~/default2.aspx#positionToMove") などどするのは間違いのようです。

と言うよりは、その前に、本当に Server.Transfer を使う必要があるのかということをよく考えるべきと思います。

普通は Server.Transfer を使う必要はないはずです。個人的には使うべきではないと思っています。使うケースがあるとすると、自分が知る限りですが、エラーページに Transfer して、そこで遷移元で起こった例外の情報を取得する場合ぐらいでしょうか。

(2) Server.Transfer で遷移して指定の場所にジャンプする方法

MSDN ライブラリの Transfer メソッドの説明によると "QueryString コレクションおよび Form コレクションを保持します" ということです。

なので、クエリ文字列でジャンプ先のアンカー名を渡し、遷移先のページでアンカー名を取得し、JavaScript を使ってアンカー名に指定された位置にジャンプさせることが可能です。

遷移元のページでは以下のよう Server.Transfer メソッドを記述します。

protected void Button2_Click(object sender, EventArgs e)
{
  // クエリ文字列でジャンプ先のアンカー名を渡す。
  Server.Transfer("~/default2.aspx?anchorName=positionToMove");
}

遷移先のページに移動する位置を示すアンカー(フラグメント)を設定します。例えば、アンカー名が上記のコードのように positionToMove とすると以下のような感じです。html 要素は p 要素でなくても、div でも span でも可です。古いブラウザでは name 属性に指定しないと認識しないものもあるようですので、id と name 属性の両方にアンカー名を指定しておいた方がいいと思います。

<p id="positionToMove" name="positionToMove">
    ジャンプする場所
</p>

さらに、遷移先のページで、クエリ文字列の有無を調べて、アンカー名の指定があればその位置にジャンプさせる JavaScript のコードを登録します。以下のような感じです。

protected void Page_Load(object sender, EventArgs e)
{
  string anchorName = Request.QueryString["anchorName"];

  // null チェックおよび適切性の検証。
  if (anchorName != null && IsAnchorNameValid(anchorName))
  {
    String csname = "FragmentScript";
    Type cstype = this.GetType();

    ClientScriptManager cs = Page.ClientScript;

    if (!cs.IsStartupScriptRegistered(cstype, csname))
    {
      String cstext = 
          "window.location.hash = '" + anchorName + "';";
      cs.RegisterStartupScript(cstype, csname, cstext, true);
    }
  }
}

// anchorName として適切かどうかを検証。
protected bool IsAnchorNameValid(string anchorName)
{
  // パターンは実際に合わせて適宜変更してください。
  string pattern = "^positionToMove$";

  // anchorName が null の場合 ArgumentNullException
  // がスローされるので注意。
  return Regex.IsMatch(anchorName, pattern);
}

上記のコードには、悪意のあるユーザーによるスクリプトの注入に対応するため、クエリ文字列がアンカー名として適切かどうかを検証する例を含めています。アンカー名が固定で良ければ、クエリ文字列でスクリプトを組み立てない方がいいと思います。

Tags: ,

ASP.NET

Session_Start ハンドラの影響

by WebSurfer 2014年7月26日 17:03

Visual Studio 2010 のテンプレートを使って ASP.NET Web Forms アプリケーション(MVC ではなくて)を新規に作成すると、以下の画像のように、デフォルトで Global.asax ファイルの中に空の Session_Start ハンドラが生成されます。Session State を使わないのであれば、このコードは削除しておいた方がよさそうという話を書きます。

なお、以下の話で Session Cookie という言葉が出てきますが、ブラウザ / サーバー間のセッション ID の授受にクッキーを利用する(即ち、sessionState 要素 の cookieless 属性がデフォルトの UseCookies)ということが前提ですのでご注意ください。

Session_Start ハンドラ

つい先日、MSDN Forum でのやりとりを通じて、Global.asax に Session_Start ハンドラを追加するだけで、サーバーは Session State 情報用のストレージを割り当てて Session Cookie を発行することを知りました。

実は、明示的に Session Sate 情報を使用しない限り(即ち、 Session["Data"] = xxxx; のようなコードを書かない限り)Session Cookie が発行されることはないと思い込んでました。

自分では Session["Data"] = xxxx; のようなコードは書いた覚えはないのに、Fiddler2 などで HTTP ヘッダを見ると Session Cookie がやりとりされていたのが不思議だったのですが、Visual Studio が勝手に追加した Session_Start ハンドラが原因だと知ってやっと謎が解けました。(笑)

調べてみると、そのことは自分が持っている「プログラミング ASP.NET 4」という本の 17.3.1 章に書いてありました。せっかく 9,000 円も出して買った本なのに、読んでなかったです。(汗)

その本によると、"Session_Start ハンドラを使用する場合は、セッション状態は空であっても常に(セッション状態プロバイダーに)保存されます" とのことです。さらに、"Session_Start ハンドラはどうしても必要な場合にのみ細心の注意を払って定義してください" とも書いてありました。

というわけで、Visual Studio のテンプレートで自動生成される空の Session_Start ハンドラは、Session State を使わなければ、例え中身が空であってもアプリケーションに不要な負担をかけるだけなので、削除した方がよさそうです。

自分的には、Session Sate 情報を使用することはあっても、Session_Start ハンドラで何か処置を行う必要があるケースは考え付かないのです。なので、何故テンプレートでこのようなコードが自動的に追加されるのか謎です。


以下は余談ですが、今回の件で Session State に関して調べた情報を書いておきます。

サーバーに接続してくるクライアントごとに Session State をセットアップするのは、ASP.NET の既定の HTTP モジュール SessionStateModule です。

SessionStateModule は、クライアントからの要求を処置する HttpApplication オブジェクトのセットアップ時に呼び出され、一意な セッション ID 文字列の生成または取得と、セッション状態プロバイダ(Session State 情報のストレージ)に対するデータの格納と取得を請け負います。

セッションが開始される時、即ち、要求ヘッダに Session Cookie が含まれない状態でサーバーが初回の要求を受けると、SessionStateModule がセッション ID を生成します。

なお、2 回目の要求以降でも、ブラウザからの要求に Session Cookie が含まれてない場合は、SessionStateModule は要求のたびに新しいセッション ID を生成しますので注意してください。

ちなみに、セッション ID の文字列は、SessionID プロパティ から取得して調べることができます。ブラウザから Session Cookie が送られてこなければ、SessionID の値は要求のたび異なります。

Web アプリケーションの Global.asax に Session_Start ハンドラがあるか、Session State 情報を利用するコード(例えば、Session["Data"] = xxxx;)があると、サーバーは "ASP.NET_SessionId" という名前の Session Cookie を生成し、応答ヘッダに含めてブラウザに送信します。以下のような感じです。

Set-Cookie: ASP.NET_SessionId=d4rbf5nhk1xpo0qouuciwxgy; path=/; HttpOnly

上記の通り期限は設定されてないので、一旦クッキーを受け取るとブラウザを閉じない限りクッキーが消滅することはなく、ユーザーが同じドメイン内で閲覧するときには、ブラウザーはクッキーをサーバーに送信し続けます。

それはセッションがタイムアウト(sessionState 構成要素の timeout 属性に設定した時間が過ぎたことを意味します。デフォルトで 20 分)しても同じです。タイムアウトするとストレージに保存されていたセッションデータは破棄されますが、セッション ID は書き換えられず、同じ ID が使用され続けます。即ち、タイムアウトしてもブラウザとサーバ間のセッションは維持されます。

従って、2 回目以降の要求では毎回 Session Cookie がブラウザからサーバーに送られるので、それに含まれるセッション ID をベースにストレージからデータを取得するなどして HttpSessionState オブジェクトを生成することができます。HttpSessionState オブジェクトには Page の Session プロパティを通じてアクセスできます。

なお、2 回目以降の要求に Session Cookie が送られて来る場合は新しいセッションではないと判断され、Session_Start イベントは発生しないというのがデフォルトの動きになるようです。

なお、上にも書きましたが、Session_Start ハンドラがないもしくは Session State 情報を利用するコードがない場合は、サーバーからは Session Cookie は発行されず、上記とは異なった動きとなりますので注意してください。

以下は上にリンクを張った以外に参考にしたページの URL です。後で探さなくて済むよう貼っておきました。

Tags:

ASP.NET

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

by WebSurfer 2014年7月23日 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月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar