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 ページ

ASP.NET アーキテクチャ

認証と承認

簡易フォーム認証

Carl Nolan は、北カリフォルニアのシリコン バレーの Microsoft テクノロジ センターで働いています。 このセンターでは、Microsoft Windows .NET プラットフォームを使用した Microsoft .NET ソリューションの開発に重点を置いています。