次の方法で共有


メッセージインスペクター

MessageInspectors サンプルは、クライアントおよびサービス メッセージ インスペクターを実装および構成する方法を示しています。

メッセージ インスペクターは、サービス モデルのクライアント ランタイムとディスパッチ ランタイムでプログラムまたは構成を使用して使用でき、受信後または送信前にメッセージを検査および変更できる拡張オブジェクトです。

このサンプルでは、構成可能な一連の XML スキーマ ドキュメントに対して受信メッセージを検証する、基本的なクライアントおよびサービス メッセージ検証メカニズムを実装します。 このサンプルでは、各操作のメッセージは検証されないことに注意してください。 これは意図的な簡略化です。

Message Inspector

クライアント メッセージ インスペクターは IClientMessageInspector インターフェイスを実装し、サービス メッセージ インスペクターは IDispatchMessageInspector インターフェイスを実装します。 実装を 1 つのクラスに組み合わせて、両側で動作するメッセージ インスペクターを形成できます。 このサンプルでは、このような結合されたメッセージ インスペクターを実装します。 インスペクターは、受信メッセージと送信メッセージが検証されるスキーマのセットを渡して構築され、開発者は受信メッセージと送信メッセージのどちらを検証するか、インスペクターがディスパッチ モードかクライアント モードかを指定できます。これは、このトピックの後半で説明するようにエラー処理に影響します。

public class SchemaValidationMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
    XmlSchemaSet schemaSet;
    bool validateRequest;
    bool validateReply;
    bool isClientSide;
    [ThreadStatic]
    bool isRequest;

    public SchemaValidationMessageInspector(XmlSchemaSet schemaSet,
         bool validateRequest, bool validateReply, bool isClientSide)
    {
        this.schemaSet = schemaSet;
        this.validateReply = validateReply;
        this.validateRequest = validateRequest;
        this.isClientSide = isClientSide;
    }

サービス (ディスパッチャー) メッセージインスペクターは、AfterReceiveRequestBeforeSendReply(Message, Object)の 2 つのIDispatchMessageInspectorメソッドを実装する必要があります。

AfterReceiveRequest は、メッセージが受信され、チャネル スタックによって処理され、サービスに割り当てられたが、逆シリアル化されて操作にディスパッチされる前に、ディスパッチャーによって呼び出されます。 受信メッセージが暗号化されている場合、メッセージインスペクターに到達したときにメッセージは既に復号化されています。 このメソッドは、参照パラメーターとして渡された request メッセージを取得します。これにより、メッセージを必要に応じて検査、操作、または置き換えることができます。 戻り値は任意のオブジェクトにすることができ、サービスが現在のメッセージに応答を返すときに BeforeSendReply に渡される関連付け状態オブジェクトとして使用されます。 このサンプルでは、 AfterReceiveRequest はメッセージの検査 (検証) をプライベートなローカル メソッド ValidateMessageBody に委任し、関連付け状態オブジェクトを返しません。 このメソッドは、無効なメッセージがサービスに渡されないようにします。

object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
    if (validateRequest)
    {
        // inspect the message. If a validation error occurs,
        // the thrown fault exception bubbles up.
        ValidateMessageBody(ref request, true);
    }
    return null;
}

BeforeSendReply(Message, Object) は、応答をクライアントに送り返す準備ができたり、一方向メッセージの場合は受信メッセージが処理されたりするたびに呼び出されます。 これにより、MEP に関係なく、拡張機能を対称的に呼び出すことができます。 AfterReceiveRequestと同様に、メッセージは参照パラメーターとして渡され、検査、変更、または置換できます。 このサンプルで実行されるメッセージの検証は、 ValidMessageBody メソッドに再度委任されますが、この場合、検証エラーの処理は若干異なります。

サービスで検証エラーが発生した場合、 ValidateMessageBody メソッドは FaultException派生例外をスローします。 AfterReceiveRequestでは、これらの例外をサービス モデル インフラストラクチャに配置して、自動的に SOAP エラーに変換し、クライアントに中継することができます。 BeforeSendReplyでは、サービスによってスローされるエラー例外の変換はメッセージ インスペクターが呼び出される前に発生するため、FaultException例外をインフラストラクチャに配置しないでください。 したがって、次の実装は既知の ReplyValidationFault 例外をキャッチし、応答メッセージを明示的なエラー メッセージに置き換えます。 このメソッドは、サービス実装によって無効なメッセージが返されないようにします。

void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    if (validateReply)
    {
        // Inspect the reply, catch a possible validation error
        try
        {
            ValidateMessageBody(ref reply, false);
        }
        catch (ReplyValidationFault fault)
        {
            // if a validation error occurred, the message is replaced
            // with the validation fault.
            reply = Message.CreateMessage(reply.Version,
                    fault.CreateMessageFault(), reply.Headers.Action);
        }
    }

クライアント メッセージ インスペクターは非常によく似ています。 IClientMessageInspectorから実装する必要がある 2 つのメソッドは、AfterReceiveReplyBeforeSendRequestです。

BeforeSendRequest は、クライアント アプリケーションまたは操作フォーマッタによってメッセージが構成されたときに呼び出されます。 ディスパッチャー・メッセージ検査と同様に、メッセージは検査することも、完全に置き換えることもできます。 このサンプルでは、インスペクターは、ディスパッチ メッセージ インスペクターにも使用される同じローカル ValidateMessageBody ヘルパー メソッドに委任します。

クライアントとサービスの検証 (コンストラクターで指定) の動作上の違いは、クライアントの検証では、ユーザー コードに配置されたローカル例外がスローされます。これは、サービスの障害のためではなく、ローカルで発生するためです。 一般に、サービス ディスパッチャーインスペクターはエラーをスローし、クライアントインスペクターは例外をスローするというルールです。

この BeforeSendRequest 実装により、無効なメッセージがサービスに送信されなくなります。

object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
    if (validateRequest)
    {
        ValidateMessageBody(ref request, true);
    }
    return null;
}

AfterReceiveReply実装では、サービスから受信した無効なメッセージがクライアント ユーザー コードに中継されないようにします。

void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    if (validateReply)
    {
        ValidateMessageBody(ref reply, false);
    }
}

この特定のメッセージインスペクターの中心は、 ValidateMessageBody メソッドです。 処理を実行するために、渡されたメッセージの本文コンテンツ サブツリーの周囲に検証 XmlReader がラップされます。 リーダーには、メッセージ インスペクターが保持するスキーマのセットが設定され、検証コールバックは、このメソッドと共に定義されている InspectionValidationHandler を参照するデリゲートに設定されます。 検証を実行するために、メッセージは読み取られ、メモリ ストリームベースの XmlDictionaryWriterにスプールされます。 プロセスで検証エラーまたは警告が発生した場合、コールバック メソッドが呼び出されます。

エラーが発生しない場合は、元のメッセージからプロパティとヘッダーをコピーし、メモリ ストリームで検証済みの情報セットを使用する新しいメッセージが作成されます。これは、 XmlDictionaryReader によってラップされ、置換メッセージに追加されます。

void ValidateMessageBody(ref System.ServiceModel.Channels.Message message, bool isRequest)
{
    if (!message.IsFault)
    {
        XmlDictionaryReaderQuotas quotas =
                new XmlDictionaryReaderQuotas();
        XmlReader bodyReader =
            message.GetReaderAtBodyContents().ReadSubtree();
        XmlReaderSettings wrapperSettings =
                              new XmlReaderSettings();
        wrapperSettings.CloseInput = true;
        wrapperSettings.Schemas = schemaSet;
        wrapperSettings.ValidationFlags =
                                XmlSchemaValidationFlags.None;
        wrapperSettings.ValidationType = ValidationType.Schema;
        wrapperSettings.ValidationEventHandler += new
           ValidationEventHandler(InspectionValidationHandler);
        XmlReader wrappedReader = XmlReader.Create(bodyReader,
                                            wrapperSettings);

        // pull body into a memory backed writer to validate
        this.isRequest = isRequest;
        MemoryStream memStream = new MemoryStream();
        XmlDictionaryWriter xdw =
              XmlDictionaryWriter.CreateBinaryWriter(memStream);
        xdw.WriteNode(wrappedReader, false);
        xdw.Flush(); memStream.Position = 0;
        XmlDictionaryReader xdr =
        XmlDictionaryReader.CreateBinaryReader(memStream, quotas);

        // reconstruct the message with the validated body
        Message replacedMessage =
            Message.CreateMessage(message.Version, null, xdr);
        replacedMessage.Headers.CopyHeadersFrom(message.Headers);
        replacedMessage.Properties.CopyProperties(message.Properties);
        message = replacedMessage;
    }
}

InspectionValidationHandler メソッドは、スキーマ検証エラーまたは警告が発生するたびに検証XmlReaderによって呼び出されます。 次の実装は、エラーでのみ機能し、すべての警告を無視します。

最初の考慮事項として、メッセージインスペクターを使用して検証 XmlReader をメッセージに挿入し、メッセージが処理され、メッセージをバッファリングせずに検証が行われるように見える場合があります。 ただし、これは、無効な XML ノードが検出されると、このコールバックによって検証例外がサービス モデル インフラストラクチャまたはユーザー コードのどこかにスローされ、予期しない動作が発生することを意味します。 バッファリングアプローチにより、ユーザー コードが無効なメッセージから完全に保護されます。

前に説明したように、ハンドラーによってスローされる例外は、クライアントとサービスによって異なります。 サービスでは、例外は FaultException から派生します。クライアントでは、例外は通常のカスタム例外です。

        void InspectionValidationHandler(object sender, ValidationEventArgs e)
{
    if (e.Severity == XmlSeverityType.Error)
    {
        // We are treating client and service side validation errors
        // differently here. Client side errors cause exceptions
        // and are thrown straight up to the user code. Service side
        // validations cause faults.
        if (isClientSide)
        {
            if (isRequest)
            {
                throw new RequestClientValidationException(e.Message);
            }
            else
            {
                throw new ReplyClientValidationException(e.Message);
            }
        }
        else
        {
            if (isRequest)
            {
                // this fault is caught by the ServiceModel
                // infrastructure and turned into a fault reply.
                throw new RequestValidationFault(e.Message);
             }
             else
             {
                // this fault is caught and turned into a fault message
                // in BeforeSendReply in this class
                throw new ReplyValidationFault(e.Message);
              }
          }
      }
    }

行動

メッセージ インスペクターは、クライアント ランタイムまたはディスパッチ ランタイムの拡張機能です。 このような拡張機能は、動作を使用して構成 されます。 動作は、既定の構成を変更するか、拡張機能 (メッセージ インスペクターなど) を追加することによってサービス モデル ランタイムの動作を変更するクラスです。

次の SchemaValidationBehavior クラスは、このサンプルのメッセージ インスペクターをクライアントまたはディスパッチ ランタイムに追加するために使用される動作です。 どちらの場合も実装はかなり基本的です。 ApplyClientBehaviorApplyDispatchBehaviorでは、メッセージ インスペクターが作成され、それぞれのランタイムのMessageInspectors コレクションに追加されます。

public class SchemaValidationBehavior : IEndpointBehavior
{
    XmlSchemaSet schemaSet;
    bool validateRequest;
    bool validateReply;

    public SchemaValidationBehavior(XmlSchemaSet schemaSet, bool
                           inspectRequest, bool inspectReply)
    {
        this.schemaSet = schemaSet;
        this.validateReply = inspectReply;
        this.validateRequest = inspectRequest;
    }
    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint,
       System.ServiceModel.Channels.BindingParameterCollection
                                            bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint,
            System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        SchemaValidationMessageInspector inspector =
           new SchemaValidationMessageInspector(schemaSet,
                      validateRequest, validateReply, true);
            clientRuntime.MessageInspectors.Add(inspector);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
         System.ServiceModel.Dispatcher.EndpointDispatcher
                                          endpointDispatcher)
    {
        SchemaValidationMessageInspector inspector =
           new SchemaValidationMessageInspector(schemaSet,
                        validateRequest, validateReply, false);
   endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

   public void Validate(ServiceEndpoint endpoint)
   {
   }

    #endregion
}

この特定の動作は属性として機能しないため、サービス型のコントラクト型に宣言的に追加することはできません。 これは、スキーマ コレクションを属性宣言に読み込むことができず、この属性の追加の構成場所 (アプリケーション設定など) を参照することは、サービス モデル構成の残りの部分と一致しない構成要素を作成することを意味するため、設計上の決定です。 したがって、この動作は、コードとサービス モデル構成拡張機能を通じてのみ命令的に追加できます。

構成を使用したメッセージ インスペクターの追加

アプリケーション構成ファイル内のエンドポイントでカスタム動作を構成する場合、サービス モデルでは、実装者に対して、BehaviorExtensionElementから派生したクラスによって表される構成拡張要素を作成する必要があります。 この拡張機能は、このセクションで説明する次の拡張機能に示すように、拡張機能のサービス モデルの構成セクションに追加する必要があります。

<system.serviceModel>
…
   <extensions>
      <behaviorExtensions>
        <add name="schemaValidator" type="Microsoft.ServiceModel.Samples.SchemaValidationBehaviorExtensionElement, MessageInspectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
…
</system.serviceModel>

拡張機能は、最も一般的な選択であるアプリケーションまたは ASP.NET 構成ファイル、またはコンピューター構成ファイルに追加できます。

拡張機能を構成スコープに追加すると、次のコードに示すように、動作を動作構成に追加できます。 動作の構成は、必要に応じて複数のエンドポイントに適用できる再利用可能な要素です。 ここで構成する特定の動作は IEndpointBehaviorを実装するため、構成ファイルのそれぞれの構成セクションでのみ有効です。

<system.serviceModel>
   <behaviors>
      …
     <endpointBehaviors>
        <behavior name="HelloServiceEndpointBehavior">
          <schemaValidator validateRequest="True" validateReply="True">
            <schemas>
              <add ___location="messages.xsd" />
            </schemas>
          </schemaValidator>
        </behavior>
      </endpointBehaviors>
      …
    </behaviors>
</system.serviceModel>

メッセージ インスペクターを構成する <schemaValidator> 要素は、 SchemaValidationBehaviorExtensionElement クラスによってサポートされます。 このクラスは、 ValidateRequestValidateReply という名前の 2 つのブール型パブリック プロパティを公開します。 これらの両方が ConfigurationPropertyAttributeでマークされます。 この属性は、前の XML 構成要素で確認できるコード プロパティと XML 属性の間のリンクを構成します。 また、このクラスには、プロパティ Schemas があり、 ConfigurationCollectionAttribute で追加でマークされ、 SchemaCollection型です。これはこのサンプルの一部でもありますが、簡潔にするためにこのドキュメントから省略されています。 このプロパティは、前の構成スニペットの<schemas>要素をSchemaConfigElementコレクションおよびコレクション要素クラスと共に返し、スキーマのコレクションを検証セットに追加できるようにします。

オーバーライドされた CreateBehavior メソッドは、ランタイムがクライアントまたはエンドポイントを構築するときに構成データを評価するときに、構成データを動作オブジェクトに変換します。

public class SchemaValidationBehaviorExtensionElement : BehaviorExtensionElement
{
    public SchemaValidationBehaviorExtensionElement()
    {
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(SchemaValidationBehavior);
        }
    }

    protected override object CreateBehavior()
    {
        XmlSchemaSet schemaSet = new XmlSchemaSet();
        foreach (SchemaConfigElement schemaCfg in this.Schemas)
        {
            Uri baseSchema = new
                Uri(AppDomain.CurrentDomain.BaseDirectory);
            string ___location = new
                Uri(baseSchema,schemaCfg.Location).ToString();
            XmlSchema schema =
                XmlSchema.Read(new XmlTextReader(___location), null);
            schemaSet.Add(schema);
        }
     return new
     SchemaValidationBehavior(schemaSet,ValidateRequest,ValidateReply);
    }

[ConfigurationProperty("validateRequest",DefaultValue=false,IsRequired=false)]
public bool ValidateRequest
{
    get { return (bool)base["validateRequest"]; }
    set { base["validateRequest"] = value; }
}

[ConfigurationProperty("validateReply", DefaultValue = false, IsRequired = false)]
        public bool ValidateReply
        {
            get { return (bool)base["validateReply"]; }
            set { base["validateReply"] = value; }
        }

     //Declare the Schema collection property.
     //Note: the "IsDefaultCollection = false" instructs
     //.NET Framework to build a nested section of
     //the kind <Schema> ...</Schema>.
    [ConfigurationProperty("schemas", IsDefaultCollection = true)]
    [ConfigurationCollection(typeof(SchemasCollection),
        AddItemName = "add",
        ClearItemsName = "clear",
        RemoveItemName = "remove")]
    public SchemasCollection Schemas
    {
        get
        {
            SchemasCollection SchemasCollection =
            (SchemasCollection)base["schemas"];
            return SchemasCollection;
        }
    }
}

メッセージインスペクターを命令型に追加する

属性 (前述の理由でこのサンプルではサポートされていません) と構成を除き、命令型コードを使用して動作をクライアントとサービス ランタイムに簡単に追加できます。 このサンプルでは、クライアント アプリケーションでクライアント メッセージ インスペクターをテストします。 GenericClient クラスは、エンドポイント構成をユーザー コードに公開するClientBase<TChannel>から派生します。 クライアントが暗黙的に開かれる前に、次のコードに示すように動作を追加するなどして、エンドポイント構成を変更できます。 サービスでの動作の追加は、ここで示すクライアント手法とほとんど同じであり、サービス ホストを開く前に実行する必要があります。

try
{
    Console.WriteLine("*** Call 'Hello' with generic client, with client behavior");
    GenericClient client = new GenericClient();

    // Configure client programmatically, adding behavior
    XmlSchema schema = XmlSchema.Read(new StreamReader("messages.xsd"),
                                                          null);
    XmlSchemaSet schemaSet = new XmlSchemaSet();
    schemaSet.Add(schema);
    client.Endpoint.Behaviors.Add(new
                SchemaValidationBehavior(schemaSet, true, true));

    Console.WriteLine("--- Sending valid client request:");
    GenericCallValid(client, helloAction);
    Console.WriteLine("--- Sending invalid client request:");
    GenericCallInvalid(client, helloAction);

    client.Close();
}
catch (Exception e)
{
    DumpException(e);
}

サンプルを設定、ビルド、実行するには

  1. Windows Communication Foundation サンプル One-Time セットアップ手順を実行していることを確認します。

  2. ソリューションをビルドするには、「 Windows Communication Foundation サンプルのビルド」の手順に従います。

  3. 単一または複数のコンピューター間の構成でサンプルを実行するには、「Windows Communication Foundation Samplesの実行」の手順に従います。