コンテキストの問題
スーザン・ウォーレン
Microsoft Corporation
2002 年 1 月 14 日
Web アプリケーションの記述に関する最も一般的な問題の 1 つは、コードが実行されているコンテキストをコードに知らせることです。 この問題を示す簡単な例 (ページの個人用設定) を見てみましょう。
サインインしてください。
対
スーザンへようこそ!
十分にシンプルに見えますが、この小さな Web UI でも、ページが要求されるたびに異なるいくつかの情報が必要です。 私は知る必要があります:
- ユーザーはサインインしていますか?
- ユーザーの表示名は何ですか?
より一般的には、ページが要求されるたびに一意のコンテキストは何ですか? また、この情報を考慮に入れるようにコードを記述するにはどうすればよいですか?
実際、HTTP のステートレスな性質により、Web アプリケーションが追跡する必要があるさまざまなコンテキストがあります。ユーザーが Web アプリケーションと対話すると、ブラウザーは一連の独立した HTTP 要求を Web サーバーに送信します。 アプリケーション自体は、ユーザーにとって快適なエクスペリエンスにこれらの要求を編み込む作業を行う必要があり、要求のコンテキストを知ることは重要です。
ASP では、HTTP 要求 のコンテキストを追跡するのに役立つ Request や Application などの組み込みオブジェクトがいくつか導入されました。 ASP.NET は次の手順を実行し、これらのオブジェクトに加えて、いくつかの追加のコンテキスト関連オブジェクトを Context と呼ばれる非常に便利な組み込みオブジェクトにバンドル します。
Context は System.Web.HttpContext 型のオブジェクトです。 ASP.NET Page クラスのプロパティとして公開されます。 また、ユーザー コントロールとビジネス オブジェクトからも使用できます (詳細については後で説明します)。 HttpContext によってロールアップされたオブジェクトの一部の一覧を次に示します。
Object | 説明 |
---|---|
Application | アプリケーションのすべてのユーザーがアクセスできる値のキーと値のペアのコレクション。 アプリケーションの種類は System.Web.HttpApplicationState です。 |
ApplicationInstance | 実行中の実際のアプリケーション。要求処理イベントの一部を公開します。 これらのイベントは、Global.asax、または HttpHandler または HttpModule で処理されます。 |
Cache | キャッシュへのプログラムによるアクセスを提供する ASP.NET Cache オブジェクト。 Rob Howard の [ASP.NET キャッシュ] 列 には、キャッシュの概要が示されています。 |
エラー | ページの処理中に最初のエラー (存在する場合) が発生しました。 詳細については 、「規則に対するロブの例外、パート 1 」を参照してください。 |
項目 | 1 つの要求の処理に関与するすべてのコンポーネント間で情報を渡すために使用できるキーと値のペアのコレクション。 項目の種類は System.Collections.IDictionary です。 |
Request | フォームまたはクエリ文字列で渡されるブラウザー情報、Cookie、値など、HTTP 要求に関する情報。 要求の種類は System.Web.HttpRequest です。 |
Response | HTTP 応答を作成するための設定とコンテンツ。 要求の種類は System.Web.HttpResponse です。 |
サーバー | Server は、Server.Execute()、Server.MapPath()、Server.HtmlEncode()など、いくつかの便利なヘルパー メソッドを持つユーティリティ クラスです。 Server は System.Web.HttpServerUtility 型のオブジェクトです。 |
セッション | アプリケーションの 1 人のユーザーがアクセスできる値のキーと値のペアのコレクション。 アプリケーションの種類は System.Web.HttpSessionState です。 |
トレース | トレース機能へのアクセスを提供する ASP.NET Trace オブジェクト。 詳細については、Rob の トレースに 関する記事を参照してください。 |
User | 認証されている場合は、現在のユーザーのセキュリティ コンテキスト。 Context.User.Identity はユーザーの名前です。 User は System.Security.Principal.IPrincipal 型のオブジェクトです。 |
ASP 開発者の場合、上記のオブジェクトの一部は非常に使い慣れたものになります。 いくつかの機能強化がありますが、ほとんどの場合、ASP とまったく同じように動作 ASP.NET。
コンテキストの基本
Context の一部のオブジェクトは、Page の最上位オブジェクトとしても昇格されます。 たとえば、 Page.Context.Response と Page.Response は同じオブジェクトを参照するため、次のコードは同等です。
[Visual Basic® Web フォーム]
Response.Write ("Hello ")
Context.Response.Write ("There")
[C# Web フォーム]
Response.Write ("Hello ");
Context.Response.Write ("There");
ビジネス オブジェクトから Context オブジェクトを使用することもできます。 HttpContext.Current は、現在の要求のコンテキストを簡単に返す静的プロパティです。 これはあらゆる種類の方法で役立ちますが、ビジネス クラスのキャッシュから項目を取得する簡単な例を次に示します。
[Visual Basic]
' get the request context
Dim _context As HttpContext = HttpContext.Current
' get dataset from the cache
Dim _data As DataSet = _context.Cache("MyDataSet")
[C#]
// get the request context
HttpContext _context = HttpContext.Current;
// get dataset from cache
DataSet _data = _context.Cache("MyDataSet");
動作中のコンテキスト
Context オブジェクトは、いくつかの一般的な ASP.NET "How Do I ...?" の質問に対する回答を提供します。 おそらく、この宝石がどれほど価値あるかを伝える最善の方法は、それを実際に見せる方法です。 ここでは、私が知っている最高の コンテキスト トリックのいくつかを紹介します。
ビジネス クラスから ASP.NET トレース ステートメントを出力するにはどうすればよいですか?
答え: 簡単! HttpContext.Current を使用して Context オブジェクトを取得し、Context.Trace.Write() を呼び出します。
[Visual Basic]
Imports System
Imports System.Web
Namespace Context
' Demonstrates emitting an ASP.NET trace statement from a
' business class.
Public Class TraceEmit
Public Sub SomeMethod()
' get the request context
Dim _context As HttpContext = HttpContext.Current
' use context to write the trace statement
_context.Trace.Write("in TraceEmit.SomeMethod")
End Sub
End Class
End Namespace
[C#]
using System;
using System.Web;
namespace Context
{
// Demonstrates emitting an ASP.NET trace statement from a
// business class.
public class TraceEmit
{
public void SomeMethod() {
// get the request context
HttpContext _context = HttpContext.Current;
// use context to write the trace statement
_context.Trace.Write("in TraceEmit.SomeMethod");
}
}
}
ビジネス クラスからセッション状態の値にアクセスするにはどうすればよいですか?
答え: 簡単! HttpContext.Current を使用して Context オブジェクトを取得し、Context.Session にアクセスします。
[Visual Basic]
Imports System
Imports System.Web
Namespace Context
' Demonstrates accessing the ASP.NET Session intrinsic
' from a business class.
Public Class UseSession
Public Sub SomeMethod()
' get the request context
Dim _context As HttpContext = HttpContext.Current
' access the Session intrinsic
Dim _value As Object = _context.Session("TheValue")
End Sub
End Class
End Namespace
[C#]
using System;
using System.Web;
namespace Context
{
// Demonstrates accessing the ASP.NET Session intrinsic
// from a business class.
public class UseSession
{
public void SomeMethod() {
// get the request context
HttpContext _context = HttpContext.Current;
// access the Session intrinsic
object _value = _context.Session["TheValue"];
}
}
}
アプリケーションのすべてのページに標準のヘッダーとフッターを追加するにはどうすればよいですか?
答え: アプリケーションの BeginRequest イベントと EndRequest イベントを 処理し、 Context.Response.Write を 使用してヘッダーとフッターの HTML を出力します。
技術的には、HttpModule または Global.asax を使用して BeginRequest などのアプリケーション イベントを処理できます。 HttpModule は少し書きにくいため、この例のように、通常は 1 つのアプリケーションで使用される機能には使用されません。 そのため、代わりにアプリケーション スコープの Global.asax ファイルを使用します。
ASP ページと同様に、いくつかの ASP.NET コンテキスト組み込みが、Global.asax を表すクラスが継承する HttpApplication クラスのプロパティに昇格されます。 Context オブジェクトへの参照を取得するために HttpContext.Current を使用する必要はありません。Global.asax で既に使用できます。
この例では、 タグと <body>
タグと水平ルールを<html>
ヘッダー セクションに配置し、もう 1 つの水平ルールにこれらの終了タグをフッター セクションに追加します。 フッターには著作権メッセージも含まれています。 結果は次の図のようになります。
図 1. ブラウザーでレンダリングされる標準のヘッダーとフッターの例
これは簡単な例ですが、これを簡単に拡張して標準のヘッダーとナビゲーションを含めたり、-- #include --->ステートメントを<出力したりできます。 1 つの注意点です。ヘッダーまたはフッターに対話型コンテンツを含める場合は、代わりにユーザー コントロール ASP.NET 使用することを検討する必要があります。
[SomePage.aspx ソース - サンプル コンテンツ]
<FONT face="Arial" color="#cc66cc" size="5">
Normal Page Content
</FONT>
[Visual Basic Global.asax]
<%@ Application Language="VB" %>
<script runat="server">
Sub Application_BeginRequest(sender As Object, e As EventArgs)
' emit page header
Context.Response.Write("<html>" + ControlChars.Lf + _
"<body bgcolor=#efefef>" + ControlChars.Lf + "<hr>" + _ ControlChars.Lf)
End Sub
Sub Application_EndRequest(sender As Object, e As EventArgs)
' emit page footer
Context.Response.Write("<hr>" + ControlChars.Lf + _
"Copyright 2002 Microsoft Corporation" + _
ControlChars.Lf + "</body>" + ControlChars.Lf + "</html>")
End Sub
</script>
[C# Global.asax]
<%@ Application Language="C#" %>
<script runat="server">
void Application_BeginRequest(Object sender, EventArgs e) {
// emit page header
Context.Response.Write("<html>\n<body bgcolor=#efefef>\n<hr>\n");
}
void Application_EndRequest(Object sender, EventArgs e) {
// emit page footer
Context.Response.Write("<hr>\nCopyright 2002 Microsoft Corporation\n");
Context.Response.Write("</body>\n</html>");
}
</script>
ユーザーが認証されたときにウェルカム メッセージを表示するにはどうすればよいですか?
答え:User コンテキスト オブジェクトをテストして、ユーザーが認証されているかどうかを確認します。 その場合は、 User オブジェクトからもユーザーの名前を取得します。 これはもちろん、記事の先頭からの例です。
[Visual Basic]
<script language="VB" runat="server">
Sub Page_Load(sender As Object, e As EventArgs) {
If User.Identity.IsAuthenticated Then
welcome.Text = "Welcome " + User.Identity.Name
Else
' not signed in yet, add a link to signin page
welcome.Text = "please sign in!"
welcome.NavigateUrl = "signin.aspx"
End If
End Sub
</script>
<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>
[C#]
<script language="C#" runat="server">
void Page_Load(object sender, EventArgs e) {
if (User.Identity.IsAuthenticated) {
welcome.Text = "Welcome " + User.Identity.Name;
}
else {
// not signed in yet, add a link to signin page
welcome.Text = "please sign in!";
welcome.NavigateUrl = "signin.aspx";
}
}
</script>
<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>
そして今、本当に素晴らしいもののために: Context.Items
上記の例では、少しのコンテキスト情報を使用して Web アプリケーションを記述する方がどれだけ簡単であるかを示すことを願っています。 同じ方法 でアプリケーションに 固有のコンテキストにアクセスできることは素晴らしいことですか?
これは Context.Items コレクションの目的です。 要求の処理に参加するコードのすべての部分で使用できる方法で、アプリケーションの要求固有の値が保持されます。 たとえば、Global.asax、ASPX ページ、ページ内のユーザー コントロール、およびページが呼び出すビジネス ロジックで、同じ情報を使用できます。
IBuySpy ポータルのサンプル アプリケーションについて考えてみましょう。 1 つのメイン ページ (DesktopDefault.aspx) を使用して、ポータルのコンテンツを表示します。 表示されるコンテンツは、選択されているタブと、ユーザーのロール (認証されている場合) によって異なります。
図 2. IbuySpy ホーム ページ
querystring には、要求されるタブの TabIndex パラメーターと TabId パラメーターが含まれています。 この情報は、要求の処理全体を通じて使用され、ユーザーに表示されるデータをフィルター処理します。 http://www.ibuyspy.com/portal/DesktopDefault.aspx?tabindex=1&tabid=2
querystring 値を使用するには、まずそれが有効な値であることを確認し、そうでない場合は少しエラー処理を行う必要があります。 多くのコードではありませんが、値を使用するすべてのページとコンポーネントで複製しますか? もちろん違います。 ポータルのサンプルでは、TabId がわかったら事前に読み込むことができる他の情報があるため、さらに複雑になります。
ポータルでは、querystring 値をパラメーターとして使用して新しい "PortalSettings" オブジェクトを構築し、Global.asax の BeginRequest イベントの Context.Items に追加します。 開始要求は各要求の先頭で実行されるため、これにより、アプリケーション内のすべてのページとコンポーネントでタブ関連の値を使用できるようになります。 要求が完了すると、オブジェクトは自動的に破棄され、非常に整然とします。
[Visual Basic Global.asax]
Sub Application_BeginRequest(sender As [Object], e As EventArgs)
Dim tabIndex As Integer = 0
Dim tabId As Integer = 0
' Get TabIndex from querystring
If Not (Request.Params("tabindex") Is Nothing) Then
tabIndex = Int32.Parse(Request.Params("tabindex"))
End If
' Get TabID from querystring
If Not (Request.Params("tabid") Is Nothing) Then
tabId = Int32.Parse(Request.Params("tabid"))
End If
Context.Items.Add("PortalSettings", _
New PortalSettings(tabIndex, tabId))
End Sub
[C# Global.asax]
void Application_BeginRequest(Object sender, EventArgs e) {
int tabIndex = 0;
int tabId = 0;
// Get TabIndex from querystring
if (Request.Params["tabindex"] != null) {
tabIndex = Int32.Parse(Request.Params["tabindex"]);
}
// Get TabID from querystring
if (Request.Params["tabid"] != null) {
tabId = Int32.Parse(Request.Params["tabid"]);
}
Context.Items.Add("PortalSettings",
new PortalSettings(tabIndex, tabId));
}
DesktopPortalBanner.ascx ユーザー コントロールは、PortalSetting のオブジェクトを Context からプルして、ポータルの名前とセキュリティ設定にアクセスします。 実際、この 1 つのモジュールは、 コンテキスト の動作に関する優れた一例です。 この点を説明するために、コードを少し簡略化し、HTTP またはアプリケーション固有の コンテキスト が太字でアクセスされるすべての場所をマークしました。
[C# DesktopPortalBanner.ascx]
<%@ Import Namespace="ASPNetPortal" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script language="C#" runat="server">
public int tabIndex;
public bool ShowTabs = true;
protected String LogoffLink = "";
void Page_Load(Object sender, EventArgs e) {
// Obtain PortalSettings from Current Context
PortalSettings portalSettings =
(PortalSettings) Context.Items["PortalSettings"];
// Dynamically Populate the Portal Site Name
siteName.Text = portalSettings.PortalName;
// If user logged in, customize welcome message
if (Request.IsAuthenticated == true) {
WelcomeMessage.Text = "Welcome " +
Context.User.Identity.Name + "! <" +
"span class=Accent" + ">|<" + "/span" + ">";
// if authentication mode is Cookie, provide a logoff link
if (Context.User.Identity.AuthenticationType == "Forms") {
LogoffLink = "<" + "span class=\"Accent\">|</span>\n" +
"<a href=" + Request.ApplicationPath +
"/Admin/Logoff.aspx class=SiteLink> Logoff" +
"</a>";
}
}
// Dynamically render portal tab strip
if (ShowTabs == true) {
tabIndex = portalSettings.ActiveTab.TabIndex;
// Build list of tabs to be shown to user
ArrayList authorizedTabs = new ArrayList();
int addedTabs = 0;
for (int i=0; i < portalSettings.DesktopTabs.Count; i++) {
TabStripDetails tab =
(TabStripDetails)portalSettings.DesktopTabs[i];
if (PortalSecurity.IsInRoles(tab.AuthorizedRoles)) {
authorizedTabs.Add(tab);
}
if (addedTabs == tabIndex) {
tabs.SelectedIndex = addedTabs;
}
addedTabs++;
}
// Populate Tab List at Top of the Page with authorized
// tabs
tabs.DataSource = authorizedTabs;
tabs.DataBind();
}
}
</script>
<table width="100%" cellspacing="0" class="HeadBg" border="0">
<tr valign="top">
<td colspan="3" align="right">
<asp:label id="WelcomeMessage" runat="server" />
<a href="<%= Request.ApplicationPath %>">Portal Home</a>
<span class="Accent"> |</span>
<a href="<%= Request.ApplicationPath %>/Docs/Docs.htm">
Portal Documentation</a>
<%= LogoffLink %>
</td>
</tr>
<tr>
<td width="10" rowspan="2">
</td>
<td height="40">
<asp:label id="siteName" runat="server" />
</td>
<td align="center" rowspan="2">
</td>
</tr>
<tr>
<td>
<asp:datalist id="tabs" runat="server">
<ItemTemplate>
<a href='<%= Request.ApplicationPath %>
/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=
<%# ((TabStripDetails) Container.DataItem).TabId %>'>
<%# ((TabStripDetails) Container.DataItem).TabName %>
</a>
</ItemTemplate>
<SelectedItemTemplate>
<span class="SelectedTab">
<%# ((TabStripDetails) Container.DataItem).TabName %>
</span>
</SelectedItemTemplate>
</asp:datalist>
</td>
</tr>
</table>
Visual Basic と C# の両方で、IBuySpy ポータルの完全なソースをオンラインで参照して実行することも、ダウンロードして自分で http://www.ibuyspy.com実行することもできます。
まとめ
コンテキスト は、"ASP.NET でさらに良いものが得られる" 機能のもう 1 つです。 ASP の既に優れたコンテキスト サポートを拡張して、両方のフックを ASP.NET の新しいランタイム機能に追加します。 さらに、非常に有効期間の短い値の新しい状態メカニズムとして Context.Items が追加されます。 しかし、開発者としての最終的な利点は、よりコンパクトで、コードの保守が容易であり、それは私たちがすべて遅れ得るコンテキストです。