C#: SQL Server XML および ASP.NET ランタイム アプリケーション
Carl Nolan
Microsoft Corporation
2002 年 3 月
概要:Microsoft .NET Framework、Microsoft SQL Server 2000 XML 機能、XSLT ドキュメントを使用した Web ベースのカスタマー サービス ソリューションの構築について詳しく説明します。 Microsoft ASP.NET ランタイム のサポート、フォーム認証メカニズム、およびシステム XML クラスを使用すると、高パフォーマンスでスケーラブルな XML ベースの照会システムを作成することが大幅に簡素化されます。 (34ページ印刷)
Csharpsqlxmlhttpapplication.exeをダウンロード します 。
内容
はじめに
.NET Framework アプリケーション
プレゼンテーション層
XML データ レイヤー
データ アクセス層
セキュリティの実装
Http アプリケーション レイヤー
Web サービス拡張機能
まとめ
はじめに
何年も前に、私は Web ベースのカスタマー サービス ソリューションの設計に取り組む幸運を得ました。 Microsoft® SQL Server XML 機能と Microsoft .NET Frameworkの登場により、Microsoft .NET Framework、SQL Server 2000 XML 機能、XSLT ドキュメントを利用した同様のアプリケーションを開発するために、このソリューションにもう一度アクセスすることにしました。
アプリケーションの機能は、顧客がセキュリティで保護された環境で顧客、注文、注文の詳細情報を表示できるようにするための単純な機能でした。 セキュリティで保護された環境は、認証されたユーザーが定義済みの顧客セットに制限されるようにします。 最後に、一連の管理ユーザー (同じユーザー インターフェイスを介して汎用ユーザーを支援する目的) には、無制限のデータ アクセスが必要でした。
SQL SERVER URL クエリ アクセスに精通している場合は、テンプレート クエリと HTML レンダリング スタイルシートを使用するソリューション自体を提示する必要があります。 純粋なSQL Serverソリューションの制限要因は、承認システムが依存する柔軟な認証システムを提供できないことです。したがって、このアプリケーションの理由です。
.NET Framework アプリケーション
表示されるアプリケーションは、要求を処理するための Microsoft ASP.NET Http ハンドラー アーキテクチャ、承認チェックと HTML レンダリングを制御するための顧客要求クラス、XML から HTML への変換用の XSLT ドキュメント、顧客の注文と顧客のセキュリティ クラス、ストアド プロシージャを返す XML、物理データベース レイヤー (フォーム) 認証メカニズムで構成されます。
図 1. Northwind スキーマ
アプリケーションに使用されたデータ ストアは、サンプルの Northwind データベースでした。関連するテーブルの概要は図 1 に示されています。
プレゼンテーション層
アプリケーション設計の原動力はプレゼンテーションレイヤーでした。 開発されたソリューションの前提は、Northwind データベースからの HTML インターフェイス、顧客、注文、注文の詳細情報を使用して提示することです。 これは、管理ユーザーと汎用ユーザーの 2 種類のユーザーをサポートしていました。
アプリケーションの最初の手順は、ユーザーがシステムにログインするユーザー認証です。 これは、Http Cookie の形式でユーザー ログイン情報を保持する .NET フォーム認証メカニズムを使用して実現されます。 使用できるその他の認証ソリューションには、Microsoft Windows® と Microsoft .NET Passport があります。
ログイン後、プレゼンテーションパスはユーザーの種類に依存していました。 管理ユーザーは、すべての顧客情報を表示する機能を持つものとして定義されます。 このため、管理ユーザーのホーム ページは、顧客が存在する国の一覧です。一覧から国を選択すると、顧客の適切な一覧が表示されます。 汎用ユーザーは、定義済みの顧客リストにアクセスできるものとして定義されます。 このため、汎用ユーザーのホーム ページは、汎用ユーザーが承認を持つ顧客の一覧です。 顧客の一覧が表示されると、HTML ナビゲーションには、顧客の詳細ページ、顧客注文の一覧、注文の詳細の概要ページ、完全な顧客と注文の概要ページが表示されます。
表 1 プレゼンテーション フロー
ページの説明 | 操作 | XML ストアド プロシージャ スタイル シート |
---|---|---|
国一覧 | GetCustomersCountries | xml_customer_cty_list customercountry.xslt |
国のお客様 | GetCustomersByCountry | xml_customer_cty customerlistcty.xslt |
ユーザー向け顧客 | GetCustomersByUser | xml_customer_user customerlistuser.xslt |
顧客の詳細 | GetCustomerById | xml_customer_id customerheader.xslt |
注文一覧 | GetCustomerOrders | xml_customer_orders customerorders.xslt |
注文の詳細 | GetCustomerOrderDetails | xml_order_details customerorderdetails.xslt |
顧客と注文の概要 | GetCustomerSummById | xml_customer_summary customersummary.xslt |
アプリケーションは、XML を返すストアド プロシージャを使用して各 HTML ページを作成し、XSLT ドキュメントを使用して HTML に変換します。 表 1 は、各ページ、XML 取得をサポートする操作の名前、ストアド プロシージャを返す関連する XML の名前、および HTML が生成される対応する XSLT ドキュメントの概要を示しています。
XML データ レイヤー
前述のように、アプリケーションは 2000 SQL SERVER XML 機能を使用します。表 1 の各操作には、対応する XML を返すストアド プロシージャがあります。 これらのストアド プロシージャを設計する際に、結果として得られる XML の構造を考慮する必要があります。 このアプリケーションが示すように、いくつかの手法を使用できます。
データベースから XML 結果を直接取得するには、SELECT ステートメントの FOR XML 句が使用されます。RAW、AUTO、EXPLICIT のいずれかの 3 つのモードを使用します。
RAW モードは、結果の XML の各行に汎用行識別子があることを意味します。 AUTO モードでは、単純な入れ子になった XML ツリーでクエリ結果が返されます。 SELECT ステートメントにリストされている FROM 句の各テーブルは、同じ名前の XML 要素として表されます。 その後、SELECT 列は要素の属性としてマップされます。 階層は、SELECT ステートメントの列によって識別されるテーブルの順序に基づいて決定されます。 XML 識別子列を構造化するには、別名を使用する必要があります。
EXPLICIT モードでは、XML ツリーの形状を指定します。クエリは、ユニバーサル ツリーと呼ばれるものを生成するすべての情報を指定します。 つまり、クエリでは、必要なデータを指定するだけでなく、すべてのメタデータを指定する必要があります。 EXPLICIT モードの他の重要な利点は、列を属性またはサブ要素のいずれかに個別にマップでき、兄弟階層を生成できることです。
XML クエリを記述する場合、XSLT 変換を実行する際に重要な XML 識別子名では大文字と小文字が区別されることを覚えておく必要があります。 XSLT ドキュメント内に加えて、XML 識別子の前に属性の @ 記号を付ける必要もあります。
ビューを使用した XML 階層の変更
XML を作成する際の最初の課題は、XML 階層でフラット化が必要になったときでした。1 つの階層階層の情報は、複数のテーブルから派生します。 これを実現するために、XML クエリでは、基になるテーブルではなくビューを使用できます。 ビューは単一のテーブルと見なされ、結果の XML 階層をフラット化します。
これは、顧客の注文情報を提示するための要件でした。 この場合、顧客の注文情報を提示する必要があり、各注文要素には荷送人情報が含まれています。
SELECT Customer.CustomerID CustomerId,
Customer.CompanyName CompanyName, ContactName, OrderID OrderId,
CONVERT(CHAR(12),OrderDate, 107) OrderDate,
CONVERT(CHAR(12),ShippedDate, 107) ShipDate,
ShipName, Freight,
(SELECT COUNT(*) FROM [Order Details] OrderDets
WHERE OrderDets.OrderID = Orders.OrderID) ProductCount,
Orders.CompanyName ShipCompany, Orders.Phone ShipPhone
FROM Customers Customer
INNER JOIN dbo.view_orders Orders
ON Customer.CustomerID = Orders.CustomerID
WHERE Customer.CustomerID = @custid
FOR XML AUTO, ELEMENTS
クエリの VIEW は単純な INNER JOIN クエリです。
SELECT Orders.*, Shippers.*
FROM Orders
INNER JOIN Shippers ON Orders.ShipVia = Shippers.ShipperID
生成された XML では、ビューは 1 つの階層として扱われます。つまり Orders です。
FOR XML EXPLICIT Queries
より複雑な XML 構造を必要とするクエリの場合は、FOR XML EXPLICIT 句を使用できます。 EXPLICIT モードは、注文の詳細ページと顧客注文の概要ページに使用されます。 前述のように、XML 階層の構造は生成されたユニバーサル テーブルによって決定されるため、ビューの使用はクエリの簡略化にのみ関連します。 たとえば、注文の詳細情報のクエリを次に示します。
SELECT 1 Tag, NULL Parent,
Orders.CustomerID [CustomerOrder!1!CustomerId],
Customers.CompanyName [CustomerOrder!1!CompanyName!element],
Customers.ContactName [CustomerOrder!1!ContactName!element],
Orders.OrderID [CustomerOrder!1!OrderId],
CONVERT(CHAR(12),OrderDate, 107) [CustomerOrder!1!OrderDate!element],
CONVERT(CHAR(12),ShippedDate, 107) [CustomerOrder!1!ShipDate!element],
Orders.ShipName [CustomerOrder!1!ShipName!element],
Orders.Freight [CustomerOrder!1!Freight!element],
Orders.CompanyName [CustomerOrder!1!ShipCompany!element],
Orders.Phone [CustomerOrder!1!ShipPhone!element],
Orders.ShipAddress [CustomerOrder!1!ShipAddress!element],
Orders.ShipCity [CustomerOrder!1!ShipCity!element],
Orders.ShipPostalCode [CustomerOrder!1!ShipPostalCode!element],
Orders.ShipCountry [CustomerOrder!1!ShipCountry!element],
NULL [OrderDetails!2!ProductId],
NULL [OrderDetails!2!ProductName!element],
NULL [OrderDetails!2!UnitPrice!element],
NULL [OrderDetails!2!Quantity!element],
NULL [OrderDetails!2!DiscountPercent!element],
NULL [OrderDetails!2!ExtendedPrice!element]
FROM Customers
INNER JOIN dbo.view_orders Orders
ON Customers.CustomerID = Orders.CustomerID
WHERE Customers.CustomerID = @custid
AND Orders.OrderID = @order
UNION ALL
SELECT 2, 1,
Orders.CustomerID [CustomerOrder!1!CustomerId],
NULL [CustomerOrder!1!CompanyName!element],
NULL [CustomerOrder!1!ContactName!element],
Orders.OrderID [CustomerOrder!1!OrderId],
NULL [CustomerOrder!1!OrderDate!element],
NULL [CustomerOrder!1!ShipDate!element],
NULL [CustomerOrder!1!ShipName!element],
NULL [CustomerOrder!1!Freight!element],
NULL [CustomerOrder!1!ShipCompany!element],
NULL [CustomerOrder!1!ShipPhone!element],
NULL [CustomerOrder!1!ShipAddress!element],
NULL [CustomerOrder!1!ShipCity!element],
NULL [CustomerOrder!1!ShipPostalCode!element],
NULL [CustomerOrder!1!ShipCountry!element],
OrderDetails.ProductID [OrderDetails!2!ProductId],
OrderDetails.ProductName [OrderDetails!2!ProductName!element],
UnitPrice [OrderDetails!2!UnitPrice!element],
Quantity [OrderDetails!2!Quantity!element],
CAST((Discount*100) AS NUMERIC(7,2))
[OrderDetails!2!DiscountPercent!element],
(UnitPrice * Quantity * (1-Discount))
[OrderDetails!2!ExtendedPrice!element]
FROM dbo.view_orders Orders
INNER JOIN dbo.view_orderdetails OrderDetails
ON Orders.OrderID = OrderDetails.OrderID
WHERE Orders.CustomerID = @custid
AND Orders.OrderID = @order
ORDER BY [CustomerOrder!1!CustomerId],
[CustomerOrder!1!OrderId], [OrderDetails!2!ProductId]
FOR XML EXPLICIT
EXPLICT モードを使用するメインタスクの 1 つは、Tag プロパティと Parent プロパティが正しく設定されていることを確認することです。 したがって、この場合、UNION ALL を使用して、必要な顧客と注文情報を返します。 最終的な ORDER BY は、注文を適切な顧客と正しく関連付けるために重要です。 CAST 関数は、クエリから正しいデータ型 (float ではなく数値) が確実に返されるようにするために使用されます。
見てわかるように、EXPLICIT モードは詳細ですが、結果として得られるユニバーサル ツリーの生成の柔軟性が向上します。 ユニバーサル テーブルのこの概念について理解を深めるには、FOR XML EXPLICIT 句を使用せずに前のクエリを実行します。 出力はユニバーサル テーブルになります。
データ アクセス層
ほとんどのアプリケーションと同様に、データベースへのアクセスはデータ アクセス層によって制御されます。CustomerOrders クラス。 このクラスの目的は、ストアド プロシージャ呼び出しを抽象化し、XML を Http アプリケーションに提示することです。 選択した戻り値のデータ型は、XmlDocument ではなく XPathDocument です。 理由は 2 つあります。SQL Serverから取得される XML はドキュメント フラグメント (ルート ノードを持たない可能性があります) であるため、XmlDocument に直接読み込むことができず、次に XPathDocument が変換を実行するのに最適なパフォーマンスを提供します。
XML の取得と返し
クラスを使用した各メソッド呼び出しの構造は同じです。SQLCommand とそのパラメーターの書式設定、アクティブな接続の設定、XML リーダーの実行、構築された XPath ドキュメントの取得。 SQLCommand 設定のみがデータ アクセス メソッドの呼び出しを区別するように、XML 処理はプライベート メソッドによって管理されます。
private XPathDocument CommandToXPath(SqlCommand northwindCom)
{
// setup the local objects
SqlConnection northwindCon = null;
XmlReader xmlReader = null;
XPathDocument xpathDoc = null;
// set base command options
northwindCom.CommandType = CommandType.StoredProcedure;
northwindCom.CommandTimeout = 15;
// now execute the command
try
{
// setup the database connection
northwindCon = new SqlConnection(dbConnectionString);
northwindCon.Open();
northwindCom.Connection = northwindCon;
// execute the command and place into an Xpath document
xmlReader = northwindCom.ExecuteXmlReader();
xpathDoc = new XPathDocument(xmlReader, XmlSpace.Preserve);
}
catch (Exception ex)
{
throw new ApplicationException
("Cannot Execute SQL Command: " + ex.Message, ex);
}
finally
{
// dispose of open objects
if (xmlReader != null) xmlReader.Close();
if (northwindCon != null) northwindCon.Close();
}
return xpathDoc;
}
SqlCommand クラスの ExecuteXmlReader メソッドは、返された XML を表す XmlReader を返します。 このリーダーは、XPath ドキュメントを作成するために使用されます。 このメソッドを呼び出す前に、呼び出し元のメソッドによって新しい SqlCommand が作成され、実行するストアド プロシージャが指定され、適切なパラメーター コレクションが定義されます。
クラス メソッド
表 1 からもう一度わかるように、クラス メソッドは 1 から 1 に対応し、XML を返すストアド プロシージャと Web アプリケーション提示関数を使用します。 図 2 は、CustomerOrders クラスのパブリック メソッドとプライベート メソッドの概要を示しています。 クラス メソッドはすべて単純な get メソッドであり、必要な XML の XPath ドキュメント表現を返します。
図 2. CustomerOrders クラスの図
クラス メソッドの唯一の作業は、ストアド プロシージャと関連するパラメーターを定義することです。 前の手順で行われた承認チェックは実行されません。 顧客注文の詳細を返す メソッドを検討してください。
public XPathDocument GetCustomerOrderDetails
(string customerId, int orderId)
{
// setup the command object to return the XML Document Fragment
SqlCommand northwindCom = new SqlCommand("xml_order_details");
// set up the stored procedure parameters
SqlParameter customerParam = new SqlParameter
("@custid", SqlDbType.NChar, 5);
customerParam.Direction = ParameterDirection.Input;
customerParam.Value = customerId;
northwindCom.Parameters.Add(customerParam);
SqlParameter orderParam = new SqlParameter
("@order", SqlDbType.Int);
orderParam.Direction = ParameterDirection.Input;
orderParam.Value = orderId;
northwindCom.Parameters.Add(orderParam);
// return the XPath document
return CommandToXPath(northwindCom);
}
ご覧のように、すべての XML 作業は CommandToXPath メソッドで実行されます。
セキュリティの実装
この時点で、セキュリティ実装に関する単語が保証されます。 既に説明したように、CustomerOrders クラスはセキュリティに関する想定を行いません。Http アプリケーションに残されます。 セキュリティ メカニズムは、データベース エンティティ、ストアド プロシージャ、セキュリティ クラスと、ASP.NET フォーム認証メカニズムを通じてサポートされます。
表 2 セキュリティ運用
説明 | Operation |
---|---|
ユーザーを認証します。 [ログイン] ページで、ユーザー名とパスワードを検証するために使用します。 |
ValidateUserLogin |
ユーザー情報を取得します。 ユーザー名やその他の情報を表示するために、既定のページで使用されます。 |
GetUserInfo |
顧客のユーザーを承認します。 汎用ユーザー要求の場合は、要求された顧客への読み取りアクセスを検証します。 |
ValidateUserCustomer |
管理ユーザーの一覧を取得します。 Http アプリケーション内で、認証されたユーザーが管理者かどうかを判断するために使用されます。 |
GetAdminUsers |
認証とデータ承認をサポートするために必要な基本的な操作を表 2 に示します。
物理データ
このアプリケーション全体で使用されるデータ モデルは、Northwind データベースのデータ モデルです。 ただし、ユーザーを顧客にマッピングし、認証と承認をサポートする要件については、拡張機能が必要でした。
セキュリティ実装を分離するために、NWSecurity と呼ばれる個別のデータベースが開発されました。1 つ目は、管理ユーザーがビット フラグでマークされたユーザー テーブルです。
CREATE TABLE dbo.NWUser (
UserName NVARCHAR(64) NOT NULL,
EmailAddress NVARCHAR(128) NOT NULL,
UserPassword NVARCHAR(64) NOT NULL,
FirstName NVARCHAR(64),
LastName NVARCHAR(64),
LastAuth DATETIME DEFAULT NULL,
AdminUser BIT DEFAULT 0,
CONSTRAINT PK_NWUser PRIMARY KEY (UserName),
CONSTRAINT UQ_NWUser_Email UNIQUE (EmailAddress)
)
2 番目のテーブルは、顧客の割り当てを定義するユーザー顧客テーブルです。 有効な承認チェックの場合、ユーザーは管理者であるか、その顧客へのアクセスが許可されます。
CREATE TABLE dbo.NWCustomer (
UserName NVARCHAR(64) NOT NULL,
CustomerID NVARCHAR(5) NOT NULL,
LastViewed DATETIME DEFAULT NULL,
AllowAccess BIT DEFAULT 1,
CONSTRAINT PK_NWCustomer PRIMARY KEY (UserName, CustomerID),
CONSTRAINT FK_NWCustomer_NWUser FOREIGN KEY (UserName)
REFERENCES NWUser (UserName)
ON UPDATE CASCADE
ON DELETE CASCADE
)
NWSecurity データベースには、セキュリティ操作をサポートするいくつかのストアド プロシージャ (顧客情報にアクセスするためのユーザーの検証、ログイン ユーザー名とパスワードの検証、ユーザー情報の取得、管理ユーザー リストの取得) が含まれています。 表 3 に、呼び出し元のクラス メソッドに基づくこれらのストアド プロシージャの一覧を示します。
表 3 セキュリティ ストアド プロシージャ
Class メソッド | ストアド プロシージャ |
---|---|
ValidateUserLogin | usp_validate_user_login |
GetUserInfo | usp_get_user_info |
ValidateUserCustomer | usp_validate_user_customer_read |
GetAdminUsers | usp_admin_users |
ストアド プロシージャを設計する際に、システムのほとんどのユーザーが汎用ユーザーであると想定していました。 このため、テーブル NWCustomer の読み取りは、管理ユーザー チェックに使用される NWUser よりも優先して実行されます。 これは、汎用ユーザーのすべての要求の前に承認チェックが付いていると見なす場合に重要です。
セキュリティ クラス
もう一度、アプリケーション データ アクセス層と同様に、セキュリティ クラスのメソッドはストアド プロシージャと 1 対 1 に対応します。 図 3 で説明されているように、CustomerSecurity クラスは、アプリケーションと物理データの間の抽象化レイヤーとして機能します。
図 3: CustomerSecurity クラスの図
ValidateUserLogin の場合、戻り値は列挙体です。
public enum LoginReturnCode
{
NoAccess = 0,
AccessIncorrectPassword = 1,
AccessAuthenticated = 2
}
列挙の値は、対応するストアド プロシージャから返される値と一致します。 したがって、このシナリオでは、クエリ以外のストアド プロシージャの戻り値を列挙型にキャストできます。
securityCom.ExecuteNonQuery();
LoginReturnCode lrcValue = (LoginReturnCode)returnParam.Value;
GetAdminUsers メソッドは、管理ユーザー名の単純な配列を返すという点で、他のメソッドとは異なります。
public Array GetAdminUsers()
{
// define array list to hold user names
ArrayList userList = new ArrayList();
using (SqlConnection securityCon = GetDbConnection())
{
using (SqlCommand securityCom =
new SqlCommand("usp_admin_users", securityCon))
{
securityCom.CommandType = CommandType.StoredProcedure;
// execute the command to obtain the resultant dataset
SqlDataReader dataNW =
securityCom.ExecuteReader(CommandBehavior.CloseConnection);
// with the data reader parse values into a searchable array
while(dataNW.Read())
{
userList.Add((string)dataNW["UserName"]);
}
dataNW.Close();
}
}
// convert array list into an Array and return
Array userArray = userList.ToArray(typeof(String));
Array.Sort(userArray);
return userArray;
}
SqlConnection と SqlCommand (スコープ外になると破棄されるオブジェクト) を使用して、SqlDataReader が構築されます。 その後、データ リーダーが解析され、ユーザー名の一覧が取得され、その値が配列リストに配置されます。 その後、配列リストは、ユーザー名を含む文字列変数の配列として簡略化されます。
フォーム認証
認証の背後にあるテネットは、システムへのアクセスをユーザーに検証することです。 管理ユーザーと汎用ユーザーのいずれであっても、すべてのユーザーに対して、ユーザー データベース テーブルに対する認証が実行されます。
認証を強制するために、カスタム フォームの実装が実装されました。 Web アプリケーションWeb.configファイル内で、認証セクションと承認セクションが変更され、フォーム認証が強制され、匿名ユーザーへのアクセスが拒否されます。サポートされているその他のメカニズムは、Windows 認証と Passport 認証です。
<authentication mode="Forms">
<forms name="CustomerServiceApp" loginUrl="login.aspx"
protection="All" timeout="30" path="/" />
</authentication>
<authorization>
<deny users="?" />
</authorization>
次のタスクは、データベースに対してユーザーを検証するために login.aspx ページを作成していました。 このページの目的は、ユーザーがユーザー名とパスワードを入力し、データベースに対して検証し、後で呼び出すために Cookie 内にユーザー トークンを保持し、最後にユーザーを最初に要求されたページに戻せるようにすることです。 繰り返しますが、.NET Frameworkを使用すると、これらのすべてのタスクが驚くほど簡単になります。
ページの UI は非常にシンプルで、2 つのテキスト ボックス (1 つはユーザー名用、もう 1 つはパスワード用)、ユーザーがブラウザー セッション間で認証を保持するかどうかを決定するためのチェック ボックス、最後に [ログイン] ボタンです。これらはすべて、サーバーの RUNAT タグを含む標準の HTML タグです。 単純なサーバー側の Web フォーム コントロールを使用して、ユーザー名とパスワードのフィールドに値が確実に入力されるようにします。 さらに、サーバー側のラベルは、ユーザーへのフィードバックに使用されます。
認証の実際の作業はすべて、サーバー側のコードで実行されます。 ページの投稿時に、入力した資格情報は CustomerSecurity クラスの ValidateUserLogin メソッドを使用して検証されます。
// get required query string parameters
string userName = UserName.Value;
string userPassword = UserPassword.Value;
// create a customer security object and make call to validate the user
CustomerSecurity customerSecurity = new CustomerSecurity();
CustomerSecurity.LoginReturnCode loginReturn =
customerSecurity.ValidateUserLogin(userName, userPassword);
ここで、UserName と UserPassword は、初期化されたサーバー側コントロールです。 適切なログインリターンコードを受け取った場合、ユーザーは認証され、要求されたページにリダイレクトされます。
if (loginReturn == CustomerSecurity.LoginReturnCode.AccessAuthenticated)
{
FormsAuthentication.RedirectFromLoginPage
(UserName.Value, PersistForms.Checked);
}
静的な RedirectFromLoginPage メソッドは、認証チケットを発行した後、最初に要求されたページにユーザーをリダイレクトします。 2 番目のパラメーターは、永続的な Cookie が発行されるかどうかを指定するブール値を受け取ります。 これは、PersistForms HTML チェック ボックスから派生します。
認証メカニズムの最後の重要な部分は、認証されたユーザーを配置するロールを決定することです。 アプリケーション global.asax ファイルに含まれているのは、元のユーザー ID からユーザー プリンシパルを再定義できるようにする認証イベントです。 現時点では、ユーザー ロールを定義できます。
protected void Application_AuthenticateRequest
(Object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (!(context.User == null))
{
if (context.User.Identity.AuthenticationType == "Forms" )
{
string userName = context.User.Identity.Name;
string[] userRoles = new string[1];
// define the role based on locating a admin user
if (Array.BinarySearch(GetAdminUsers(Context), userName) >= 0)
{
userRoles[0] = "Admin";
}
else
{
userRoles[0] = "Generic";
}
// create the new generic principal
GenericPrincipal gp = new GenericPrincipal
(context.User.Identity, userRoles);
context.User = gp;
}
}
}
ユーザー ロールは、管理ロールまたは汎用ロールとして定義されます。 GetAdminUsers メソッドは、管理ユーザーの配列をキャッシュして返します。詳細については、以下を参照してください。
Http アプリケーション レイヤー
Http アプリケーションは、ASP.NET Http ランタイムのサポートを利用します。ISAPI 拡張機能とフィルター API の論理的な置き換えにより、Microsoft IIS Web サーバーの低レベルの要求および応答サービスと対話する手段が 1 つ提供されます。 アプリケーション内で使用される主要なインターフェイスを次に示します。
- IHttpHandler: 同期 Http 要求を処理するために実装されます。 カスタム URL の実行を提供するには、ProcessRequest メソッドを実装する必要があります。
- IHttpAsyncHandler: 非同期 Http 要求を処理するために実装されます。 ProcessRequest メソッドは、BeginProcessRequest メソッドと EndProcessRequest メソッドを使用して実装されます。
- IHttpHandlerFactory: 新しい IHttpHandler オブジェクトを作成するために実装されます。 唯一の目的は、IHttpHandler インターフェイスを実装する新しいハンドラー オブジェクトを動的に製造することです。
IHttpHandlerFactory 実装では、認証されたユーザーを調べることで各要求を処理します。 管理ユーザーの場合、CustomerAdmin という名前の IHttpHandler 実装が返されます。 汎用ユーザーの場合、CustomerGeneric という名前の IHttpAsyncHandler 実装が返されます。
ハンドラーの実装をサポートするために、図 4 で説明されているように CustomerRequest クラスを使用します。 このクラスには、ProcessRequest という名前のパブリックに公開されているメソッドがあります。 指定された HttpContext を使用して、Http 要求を確認し、必要な HTML を出力します。 このクラスは、データ アクセスとセキュリティ チェックのために CustomerOrders クラスと CustomerSecurity クラスを使用します。
図 4: CustomerRequest クラスの図
1 つの Http ハンドラー アプリケーションですべての要求が処理されるため、適切な関数とレンダリングされた HTML は、URL クエリ文字列で渡される Function パラメーターによって決定されます。関数が存在しない場合は、適切なユーザーのホーム ページが表示されます。 表 4 は、これらの関数コードと、サポートされている CustomerOrders メソッドの概要を示しています。
表 4 アプリケーション関数コード
管理機能 | 汎用ユーザー関数 | クラス メソッド |
---|---|---|
null | GetCustomersCountries | |
CC | GetCustomersByCountry | |
null | GetCustomersByUser | |
CH | CH | GetCustomerById |
OS | OS | GetCustomerOrders |
OD | OD | GetCustomerOrderDetails |
CS | CS | GetCustomerSummById |
この関数パラメーターに基づいて、適切なクラス メソッドが呼び出され、データの XML 表現が返されます。 その後、適切な XSLT ドキュメントを使用して HTML に変換され、Http 応答ストリームに渡されます。
IHttpHandler の実装
管理ユーザーの場合、IHttpHandler を実装する CustomerAdmin ハンドラー インスタンスが返されます。 汎用ユーザーの場合、IHttpHandlerAsync を実装する CustomerGeneric ハンドラーが返されます。
では、管理ユーザーはどのように決定されますか? ここで、ロールベースのプログラミングの概念が生まれます。 1 つは、以前に定義されたユーザー ロールを確認するだけで済みます。
if (context.User.IsInRole("Admin")) >= 0)
{
return (new CustomerAdmin());
}
else
{
return (new CustomerGeneric());
}
ハンドラーの実装について説明する前に、IHttpHandler インターフェイスについて説明します。
public interface IHttpHandler
{
void ProcessRequest(HttpContext context);
bool IsReusable {get;}
}
Http 要求ごとに 1 つのメソッド ProcessRequest が呼び出されます。 指定された HttpContext オブジェクトは、Http 要求の処理に使用される組み込みサーバー オブジェクトへの参照を提供します。要求、応答、セッション、サーバーなど。 IsReusable プロパティは、IHttpHandler インスタンスが再利用可能かどうかを示します。 どちらの実装でも、アプリケーション例外がスローされない限り、ハンドラーは常に再利用可能と見なされます。
例外処理では、ApplicationException 派生クラスが定義されます。 新しい Exception クラスの目的は、ハンドラーの実装でハンドラーがプールに残る必要があるかどうかを判断するために使用するプロパティ Terminated を提供することです。 true 値は、ハンドラー オブジェクトを処理プールから削除する必要がある処理例外を示します。
管理ユーザー CustomerAdmin の IHttpHandler 実装を次に示します。
public class CustomerAdmin: IHttpHandler
{
private bool reuseHandler;
private CustomerRequest customerRequest;
public CustomerAdmin()
{
// ensure object is to be pooled
reuseHandler = true;
// cache the user customer request object
customerRequest = new CustomerRequest();
}
// property to indicate class reuse state
public bool IsReusable
{
get
{
return reuseHandler;
}
}
// process the HTTP request called by the application process
public void ProcessRequest(HttpContext context)
{
try
{
customerRequest.ProcessRequest(context);
}
catch (CustomerRequestException ex)
{
// take handler out of the pool if the application error
reuseHandler = !ex.Terminated;
CustomerRequestUtilities.ProcessException(context, ex);
}
catch (Exception ex)
{
// take handler out of the pool and display an error page
reuseHandler = false;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", ex.Message);
CustomerRequestUtilities.ProcessException(context, ex);
}
}
}
ProcessRequest メソッドは CustomerRequest クラスのインスタンスを作成し、対応する ProcessRequest メソッドを呼び出して Http 要求を処理します。 CustomerRequest オブジェクトが CustomerRequestException をスローした場合、ハンドラーには、ハンドラーを実行プールに残す必要があるかどうかを決定するオプションがあります。
これに対し、汎用ユーザーのハンドラー実装では、IHttpAsyncHandler インターフェイスが実装されます。 このインターフェイスには、Http ハンドラーへの非同期呼び出しを開始する必要がある BeginProcessRequest メソッドがあります。 汎用ユーザー要求の場合、別のプロセスで承認チェックが実行されます。 このチェックは非同期的に実行されるため、汎用ユーザー実装からの非同期ハンドラーを示すのは理にかなっています。
非同期呼び出しを実行するために、ProcessRequest メソッドに対してデリゲートが定義されます。 コンパイラによって生成された BeginInvoke メソッドと EndInvoke メソッドは、CustomerRequest インスタンスの ProcessRequest メソッドを非同期的に呼び出すために使用されます。
internal delegate void ProcessRequestDelegate(HttpContext context);
// start the processing of the async HTTP request
public IAsyncResult BeginProcessRequest
(HttpContext hc, AsyncCallback cb, Object extraData)
{
// save the callback reference
callback = cb;
context = hc;
// start the async operation to handle the customer request
try
{
// create the delegate and reference the callback method
ProcessRequestDelegate processDelegate = new ProcessRequestDelegate
(customerRequest.ProcessRequest);
AsyncCallback processCallback = new AsyncCallback
(this.ProcessRequestResult);
// call the compiler created begin invoke method
IAsyncResult result = processDelegate.BeginInvoke
(context, processCallback, this);
}
catch (Exception ex)
{
// take handler out of the pool and display an error page
// cannot start the async process - infrastructure error
reuseHandler = false;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", ex.Message);
throw ex;
}
// return my async result indicating the calling status
processAsyncResult = new ProcessAsyncResult();
processAsyncResult.AsyncState = extraData;
return processAsyncResult;
}
// function to be called upon completion
internal void ProcessRequestResult(IAsyncResult result)
{
try
{
// obtain a reference to the original calling class
ProcessRequestDelegate processCallback = (ProcessRequestDelegate)
((AsyncResult)result).AsyncDelegate;
// call the end invoke capturing any runtime errors
processCallback.EndInvoke(result);
}
catch (CustomerRequestException ex)
{
// take handler out of the pool if the application error
reuseHandler = !ex.Terminated;
CustomerRequestUtilities.ProcessException(context, ex);
}
catch (Exception ex)
{
// take handler out of the pool and display an error page
reuseHandler = false;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", ex.Message);
CustomerRequestUtilities.ProcessException(context, ex);
}
finally
{
processAsyncResult.IsCompleted = true;
callback(processAsyncResult);
}
}
見てわかるように、Http 要求は 2 つの部分で処理されています。 BeginProcessRequest は、デリゲート BeginInvoke メソッドを介して非同期呼び出しを開始します。この段階のエラーはインフラストラクチャの種類です。 デリゲート オブジェクトとコールバック オブジェクトを介して、EndInvoke は非同期プロセスの完了時に呼び出され、戻りデータを取得します。例外を含む。
Http ハンドラーの状態は、ProcessAsyncResult と呼ばれる IAsyncResult インターフェイスの実装によって呼び出し元クラスに認識されます。
CustomerRequest クラスと HTML レンダリング
Http ハンドラー クラスの目的は、HTML レンダリングと承認のチェックを実行する CustomerRequest オブジェクトを管理することです。 CustomerOrders クラスを使用して XML 情報を取得し、適切な XSLT ドキュメントを使用してこれを HTML に変換します。 1 つの公開メソッド ProcessRequest が使用されます。
public void ProcessRequest(HttpContext context)
{
// define initial state of the object
validProcess = 0;
this.context = context;
userType = context.User.IsInRole("Admin")?
UserType.AdminUser : UserType.GenericUser;
// obtain function code as no security check is required for null
string functionCode = context.Request.QueryString["Function"];
// look to see if authorization is required and start the process
bool performAuthorization;
if (userType == UserType.GenericUser && functionCode != null)
{
performAuthorization = true;
// start the thread that performs the security validation
validSecurity = 1;
threadSecurity.Start();
}
else
{
performAuthorization = false;
// admin user or null funciton code so security always true
validSecurity = 0;
}
// get the customer XML data and associated stylesheet name
XPathDocument docCust;
XslTransform docStyle;
ReturnCustomerXml(out docCust, out docStyle);
// if performed a security check join with processing thread
if (performAuthorization)
{
// join with the security thread with a timeout of 5 seconds
if (!threadSecurity.Join(2500))
{
validProcess = 2;
CustomerRequestUtilities.WriteTraceOutput
(context, "Security", "Unable to Complete Security Check");
try
{
threadSecurity.Abort();
}
catch (Exception) {}
}
}
// if all process and security valid output the required HTML
if (validSecurity == 0 && validProcess == 0)
{
// output the required XML and performing the transformation
docStyle.Transform(docCust, null, context.Response.Output);
}
else
{
bool terminated = (validSecurity < 2 && validProcess < 2)?
false : true;
// on error throw exception (traces will have been written)
throw new CustomerRequestException
("Process or Security Error encountered.", terminated);
}
}
メソッドは、validProcess と validSecurity という 2 つのトライステート値を使用して、処理の状態を示します。0 は、すべて有効であることを示し、1 はスローされたエラーはスローされませんが要求を処理できないことを示し、2 は処理エラーが発生したことを示します。
これは、適切な CustomerOrders メソッドが呼び出されるプライベート ReturnCustomerXML メソッドにあります。XPathDocument と読み込まれた XslTransform が出力です。 XslTransform の Transform メソッドは、HTML を応答ストリームに出力します。
private void ReturnCustomerXml
(out XPathDocument docCust, out XslTransform docStyle)
{
// define the return values
docCust = null;
docStyle = null;
string styleName = "";
try
{
// define variables for function calls
string functionCode = context.Request.QueryString["Function"];
string userName, customerId;
string countryCode;
int orderNumber;
// construct the appropriate XPath Document from function
switch (functionCode)
{
case null:
if (userType == UserType.AdminUser)
{
// obtain a XPath Document of customer countries
docCust = customerOrder.GetCustomersCountries();
styleName = "customercountry.xslt";
}
else
{
// obtain a XPath Document of customer user listing
userName = context.User.Identity.Name;
docCust = customerOrder.GetCustomersByUser(userName);
styleName = "customerlistuser.xslt";
}
break;
case "cc":
if (userType == UserType.AdminUser)
{
// obtain a XPath Document of customer listing
countryCode = context.Request.QueryString["Country"];
docCust = customerOrder.GetCustomersByCountry
(countryCode);
styleName = "customerlistcty.xslt";
}
else
{
validProcess = 1;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", "Function cc not available");
}
break;
case "cs":
// obtain a XPath Document of customer summary
customerId = context.Request.QueryString["Customer"];
docCust = customerOrder.GetCustomerSummById(customerId);
styleName = "customersummary.xslt";
break;
case "ch":
// obtain a XPath Document of customer header information
customerId = context.Request.QueryString["Customer"];
docCust = customerOrder.GetCustomerById(customerId);
styleName = "customerheader.xslt";
break;
case "os":
// obtain a XPath Document of order summary information
customerId = context.Request.QueryString["Customer"];
docCust = customerOrder.GetCustomerOrders(customerId);
styleName = "customerorders.xslt";
break;
case "od":
// obtain a XPath Document of order detail information
customerId = context.Request.QueryString["Customer"];
orderNumber = Int32.Parse
(context.Request.QueryString["Order"]);
docCust = customerOrder.GetCustomerOrderDetails
(customerId, orderNumber);
styleName = "customerorderdetails.xslt";
break;
default:
validProcess = 1;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", "Unknown Function Code");
break;
}
}
catch (Exception ex)
{
validProcess = 2;
CustomerRequestUtilities.WriteTraceOutput
(context, "Process", "Error: " + ex.Message);
}
// load the appropriate stylesheet for the transform
if (validProcess == 0) docStyle = GetStyleSheet(context, styleName);
return;
}
ReturnCustomerXml メソッドの前提は単純です。要求された関数コードを使用して、XPathDocument を取得する適切な CustomerOrders メソッドを呼び出し、派生 XSLT ファイル名から必要な XslTransform を読み込みます。 処理を高速化するために、XSLT ドキュメントがプリロードされ、アプリケーション キャッシュに保持されます。
このクラス内では、WriteTraceOutput メソッドに気付きます。その目的は、トレース ファイルに情報を書き込むという目的です。 これにより、trace.axd ページを介した例外の処理に関する情報メッセージを表示するメカニズムが提供されます。
ユーザー承認
承認は、アプリケーションへのアクセスではなく、ユーザーが定義されたデータのサブセットに制限されていることを確認するために、すべての要求を検証するという点で認証とは異なります。 これは、NWCustomer テーブルで定義されているように、割り当てられた一連の顧客にユーザーを制限することによって実現されます。
汎用ユーザーの場合、ValidateCustomerSecurity メソッドを使用して承認チェックを実行するために専用の処理スレッドが使用されます。 チェックの有効性は、適切なトライステート値によって示されます。
private void ValidateCustomerSecurity()
{
// query string variables
string customerId = context.Request.QueryString["Customer"];
string userName = context.User.Identity.Name;
if (customerId != null && userName != null)
{
try
{
// check against the database for the return code
if (customerSecurity.ValidateUserCustomer(userName, customerId))
{
validSecurity = 0;
}
else
{
validSecurity = 1;
CustomerRequestUtilities.WriteTraceOutput
(context, "Security", "Customer/User not Valid");
}
}
catch (Exception ex)
{
validSecurity = 2;
CustomerRequestUtilities.WriteTraceOutput
(context, "Security", "Error: " + ex.Message);
}
}
else
{
validSecurity = 1;
CustomerRequestUtilities.WriteTraceOutput
(context, "Security", "Customer/User not Specified");
}
return;
}
Http 要求を処理すると、セキュリティ スレッドが開始され、後で Join メソッドを使用して処理スレッドと結合されます。 この時点で、HTML をレンダリングする前にセキュリティの状態が検証されます。
オブジェクト プールとアプリケーション キャッシュ
静的データが管理ユーザーの配列用にアプリケーション内にキャッシュされ、XSLT ドキュメントが読み込まれたことが 2 回言及されています。 データが要求ごとにではなく処理されると、パフォーマンスが大幅に向上します。
現在の HttpContext は、アプリケーション Cache オブジェクトへの参照を管理します。 このオブジェクト内にキャッシュされたデータは、定義された期間、絶対時間、ファイル変更通知イベントなど、さまざまなメカニズムによって無効とマークされることがあります。
Authentication イベントは、管理ユーザーの配列にコンテキスト キャッシュを使用します。配列は GetAdminUsers メソッドによって取得および読み込まれます。
private Array GetAdminUsers(HttpContext context)
{
string adminUsersFile = "adminusers.xml";
// obtain a reference to the admin users list from the cache
Array usersArray = (Array)context.Cache[adminUsersFile];
// the the cached items does not exist the load from the server
if (usersArray == null)
{
try
{
// get the current list of the admin users
CustomerSecurity customerSecurity = new CustomerSecurity();
usersArray = customerSecurity.GetAdminUsers();
}
catch (Exception ex)
{
// if an error just create a blank array
// so the cache will not try and load for each request
usersArray = new ArrayList().ToArray(typeof(String));
CustomerRequestUtilities.WriteTraceOutput
(context, "Security", ex.Message);
}
// place the admin users array in cache for 20 minutes
string adminUsersPath = context.Server.MapPath
("//CustService/adminusers.xml");
context.Cache.Insert(adminUsersFile, usersArray,
new CacheDependency(adminUsersPath),
DateTime.Now.AddMinutes(20), TimeSpan.Zero);
}
// return the array of admin users
return usersArray;
}
また、キャッシュには、20 分の有効期限に加えて XML ファイルへの依存関係があることにも注意する必要があります。 コードに含まれているのは、このファイルを生成するスクリプトです。 これを、管理ユーザーの一覧を変更したときにキャッシュを更新するメカニズムとして使用できます。
非常によく似た方法で、CustomerOrders クラスには、キャッシュから読み込まれた XSLT ドキュメントを返す GetStyleSheet メソッドがあります。 この場合のコードは、管理ユーザー配列の機能によく似ています。 最初にキャッシュ オブジェクトが取得され、XslTransform オブジェクトにキャストされます。
XslTransform docStyle = (XslTransform)context.Cache[styleName];
キャッシュが空の場合、読み込まれていない場合、または無効になっている場合は、適切な XSLT ドキュメントが読み込まれ、キャッシュに配置されます。 この状況のキャッシュは、XslTransform が読み込まれたファイルのみに依存します。
if (docStyle == null)
{
// from the style sheet name get the required style sheet path
string stylePath = context.Server.MapPath
("//CustService/" + styleName);
// load the required style sheet
docStyle = new XslTransform();
docStyle.Load(stylePath);
// place the style sheet into the cache
context.Cache.Insert(styleName, docStyle,
new CacheDependency(stylePath));
}
コンテキスト キャッシュを使用する代わりに、プールされたハンドラーまたはアプリケーション スコープ変数内のすべての適切なデータをキャッシュします。 コンテキスト キャッシュの利点は、キャッシュが無効とマークされ、自動的に再読み込みされる定義済みのメカニズムです。
アプリケーションでは、コンテキスト キャッシュに加えて、Http ハンドラーがプールされているという事実を使用しています。 これをサポートする各ハンドラー コンストラクターは CustomerRequest クラスのインスタンスを作成し、CustomerOrders クラスと CustomerSecurity クラスの インスタンスを作成します。 さらに、CustomerOrders クラスは、承認チェックに使用される Thread オブジェクトを作成します。
オブジェクト参照を保持するこのメソッドを使用する場合、ProcessRequest メソッドによって処理される各要求はステートレスであることが重要です。各 Http ハンドラー要求では、すべてのローカル処理変数を初期化する必要があります。 さらに、エラーが発生した場合、Http ハンドラー オブジェクトは再利用可能でないとマークされるため、新しいハンドラーと関連するすべてのオブジェクトが強制的に生成されます。
CustomerOrders クラスと CustomerSecurity クラスは、データベース接続ではなく、構成ファイルから読み取られた接続文字列のみを保持します。 これにより、OLEDB リソース プールでデータベース接続を管理できます。
Web サービス拡張機能
これまでのアプリケーションは、コンシューマーが Web アプリケーションを使用して顧客と注文の情報にアクセスすることを前提として機能してきました。 .NET Framework Web サービス インフラストラクチャが登場すると、もう 1 つのソリューションとして、Web サービス メソッドから XML を返せるようにすることで、コンシューマーがアプリケーションを顧客独自のシステムに統合できるようになります。
Web メソッドと属性
Web サービスの前提は単純です。顧客と注文の情報を返すコア クラス メソッドを公開します。 図 5 は、この要件をサポートする CustomerWebService クラス定義の概要を示しています。
図 5: Web サービス クラスの図
各クラス メソッドは CustomerOrder クラスを使用して結果の XML (XPathDocument の形式) を取得し、呼び出し元が必要に応じて使用できる XmlElement としてこれを返します。 ユーザーは常に認証され、既知であることが前提です。詳細については、以下を参照してください。 たとえば、GetCustomerOrderDetails メソッドを使用します。
[WebMethod(Description="Obtain the Customer Order Summary Information")]
[SoapHeader("soapCredentials",
Required=false, Direction=SoapHeaderDirection.In)]
[SoapHeader("soapTicket",
Required=true, Direction=SoapHeaderDirection.InOut)]
public XmlElement GetCustomerOrders(string CustomerID)
{
// first validate the user has access to the customer
ValidateCustomerSecurity(CustomerID);
// call the method to get the customer information
CustomerOrders customerOrders = new CustomerOrders();
XPathDocument xpathCustDoc =
customerOrders.GetCustomerOrders(CustomerID);
// return the string representation of the XML
return GetElement(xpathCustDoc);
}
SoapHeader 属性と ValidateCustomerSecurity はすべて、セキュリティ、ユーザーの認証、および顧客データに対する承認を処理します。 GetElement メソッドは、CustomerOrders クラスから XPathDocument を受け取り、これを必要な XmlElement に変換します。
private XmlElement GetElement(XPathDocument xpathCustDoc)
{
// load the required style sheet to transform XPath into XML
string xsltDocumentPath = Context.Request.PhysicalApplicationPath
+ "customerxmlnode.xslt";
XslTransform docStyle = new XslTransform();
docStyle.Load(xsltDocumentPath);
XmlDocument docXml = new XmlDocument();
// create a new string writer for the transform
StringWriter stringWriter = new StringWriter();
// perform and return the XmlDocument representation of the XML
// assuming the document has a root node
docStyle.Transform(xpathCustDoc, null, stringWriter);
docXml.LoadXml(stringWriter.ToString());
// return document element
return docXml.DocumentElement;
}
このメソッドは XPathDocument を受け取り、XSLT ドキュメントを使用して単純な変換を実行します。
<?xml version="1.0" encoding='UTF-8' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:template match="/ | @* | node()">
<xsl:copy-of select="@* | node()" />
</xsl:template>
</xsl:stylesheet>
この目的は、XmlDocument とそのルート要素を取得できる XpathDocument の文字列表現を取得することです。 この変換では、XPath ドキュメント フラグメントにルート ノードがあることを前提としています。 そうでない場合は、XSLT ドキュメントを再構築して含めます。
SOAP 認証
ご覧のように、Web サービスの実際の実装は簡単です。しかし、セキュリティはどうでしょうか? Web サービス内で認証と承認を処理するための多くのオプションがあります。 上記の実装では、カスタム SOAP ヘッダーが使用されます。 カスタム SOAP ヘッダーの 1 つの解決策は、SOAP ヘッダーを介してユーザーを認証する HttpModule です。 私が選んだテクニックは少し違いました。
Web サービスの一部として 2 つの SOAP ヘッダーを定義しました。 省略可能な SOAPCredentials ヘッダーは、ユーザー データベースに対して検証されるユーザー資格情報を渡すために使用されます。 認証が成功すると、認証トークンが作成され、有効期限を持つ暗号化されたバージョンのユーザー名になり、必要な SOAPTicket ヘッダーに配置されます。 このチケットは、後続の Web メソッド呼び出しで復号化および検証され、認証を実行するためにデータベースへのラウンドトリップを保存します。
まとめ
ここで示すソリューションは、2000 年の XML 機能を使用するアプリケーションを作成するための Microsoft .NET Frameworkの機能SQL Server示しています。 ASP.NET ランタイムのサポート、フォーム認証メカニズム、およびシステム XML クラスにより、高パフォーマンスでスケーラブルな XML ベースの照会システムの作成が大幅に簡素化されました。
さらに、Web サービス インフラストラクチャを含めることによって、新しい可能性が存在します。 以前に公開された Web ベースのアプリケーションを Web サービスとして簡単に公開できるようになりました。 これにより、コンシューマーは、提供された機能を独自のアプリケーションに簡単に統合できます。
リファレンス
XML のSQL ServerとSQL Server
MSDN の XML ホーム ページ
MSDN の SQLXML および XML マッピング テクノロジ
Microsoft SQL Server 2000 XML 機能に関するアンケート
SQL Server 2000: 新機能により、管理者とユーザーに比類のない使いやすさとスケーラビリティが提供されます
ASP.NET 開発
MSDN の .NET 開発ホーム ページ
MSDN の ASP.NET ページ
Carl Nolan は、北カリフォルニアのシリコン バレーの Microsoft テクノロジ センターで働いています。 このセンターでは、Microsoft Windows .NET プラットフォームを使用した Microsoft .NET ソリューションの開発に重点を置いています。