次の方法で共有


カスタム メッセージ エンコーダー: 圧縮エンコーダー

圧縮サンプルでは、Windows Communication Foundation (WCF) プラットフォームを使用してカスタム エンコーダーを実装する方法を示します。

サンプルの詳細

このサンプルは、クライアント コンソール プログラム (.exe)、セルフホステッド サービス コンソール プログラム (.exe) と圧縮メッセージ エンコーダー ライブラリ (.dll) で構成されています。 このサービスは、要求/応答通信パターンを定義するコントラクトを実装します。 コントラクトは、基本的な文字列エコー操作 (ISampleServerEcho) を公開するBigEcho インターフェイスによって定義されます。 クライアントは特定の操作に対して同期要求を行い、サービスはメッセージをクライアントに繰り返して応答します。 クライアントとサービスのアクティビティはコンソール ウィンドウに表示されます。 このサンプルの目的は、カスタム エンコーダーを記述する方法と、ネットワーク上のメッセージの圧縮の影響を示す方法を示することです。 圧縮メッセージ エンコーダーにインストルメンテーションを追加して、メッセージ サイズ、処理時間、またはその両方を計算できます。

.NET Framework 4 では、サーバーが圧縮された応答 (GZip や Deflate などのアルゴリズムを使用して作成) を送信している場合、WCF クライアントで自動展開が有効になっています。 サービスがインターネット インフォメーション サーバー (IIS) で Web ホストされている場合は、サービスが圧縮された応答を送信するように IIS を構成できます。 このサンプルは、クライアントとサービスの両方で圧縮と展開を行う必要がある場合、またはサービスがセルフホステッドである場合に使用できます。

このサンプルでは、カスタム メッセージ エンコーダーをビルドして WCF アプリケーションに統合する方法を示します。 ライブラリ GZipEncoder.dll は、クライアントとサービスの両方でデプロイされます。 このサンプルでは、メッセージの圧縮による影響も示します。 GZipEncoder.dll のコードは、次の例を示しています。

  • カスタム エンコーダーとエンコーダー ファクトリの構築。

  • カスタム エンコーダーのバインド要素の開発。

  • カスタム バインド要素を統合するためのカスタム バインド構成の使用。

  • カスタム バインド要素のファイル構成を許可するカスタム構成ハンドラーの開発。

前に示したように、カスタム エンコーダーには複数のレイヤーが実装されています。 これらの各レイヤー間の関係をよりよく説明するために、サービスのスタートアップに関するイベントの簡略化された順序を次の一覧に示します。

  1. サーバーが起動します。

  2. 構成情報が読み取られます。

    1. サービス構成では、カスタム構成ハンドラーが登録されます。

    2. サービス ホストが作成され、開かれます。

    3. カスタム構成要素は、カスタム バインド要素を作成して返します。

    4. カスタム バインド要素は、メッセージ エンコーダー ファクトリを作成して返します。

  3. メッセージが受信されます。

  4. メッセージ エンコーダー ファクトリは、メッセージを読み取って応答を書き出すメッセージ エンコーダーを返します。

  5. エンコーダー レイヤーはクラス ファクトリとして実装されます。 カスタム エンコーダーでは、エンコーダー クラス ファクトリのみをパブリックに公開する必要があります。 ファクトリ オブジェクトは、 ServiceHost または ChannelFactory<TChannel> オブジェクトの作成時にバインド要素によって返されます。 メッセージ エンコーダーは、バッファーモードまたはストリーミング モードで動作できます。 このサンプルでは、バッファー モードとストリーミング モードの両方を示します。

各モードには、抽象ReadMessage クラスに付随するWriteMessageメソッドとMessageEncoder メソッドがあります。 エンコード処理の大部分は、これらのメソッドで行われます。 このサンプルでは、既存のテキスト およびバイナリ メッセージ エンコーダーをラップします。 これにより、サンプルはメッセージのワイヤ表現の読み取りと書き込みを内部エンコーダーに委任し、圧縮エンコーダーで結果を圧縮または圧縮解除できます。 メッセージ エンコード用のパイプラインがないため、WCF で複数のエンコーダーを使用する唯一のモデルです。 メッセージが解凍されると、結果として得られたメッセージは、処理対象のチャネル スタックの上にスタックとして渡されます。 圧縮中に、結果として得られる圧縮メッセージは、提供されたストリームに直接書き込まれます。

このサンプルでは、ヘルパー メソッド (CompressBufferDecompressBuffer) を使用して、バッファーからストリームへの変換を実行して、 GZipStream クラスを使用します。

バッファーに格納された ReadMessage クラスと WriteMessage クラスは、 BufferManager クラスを使用します。 エンコーダーには、エンコーダー ファクトリを介してのみアクセスできます。 抽象 MessageEncoderFactory クラスは、現在のエンコーダーにアクセスするための Encoder という名前のプロパティと、セッションをサポートするエンコーダーを作成するための CreateSessionEncoder という名前のメソッドを提供します。 このようなエンコーダーは、チャネルがセッションをサポートし、順序付けされ、信頼性が高いシナリオで使用できます。 このシナリオでは、ネットワークに書き込まれるデータの各セッションで最適化が可能になります。 これが必要でない場合は、基本メソッドをオーバーロードしないでください。 Encoder プロパティはセッションレス エンコーダーにアクセスするためのメカニズムを提供し、CreateSessionEncoder メソッドの既定の実装はプロパティの値を返します。 サンプルは既存のエンコーダーをラップして圧縮を提供するため、 MessageEncoderFactory 実装では内部エンコーダー ファクトリを表す MessageEncoderFactory を受け入れます。

エンコーダーとエンコーダー ファクトリが定義されたので、WCF クライアントとサービスで使用できます。 ただし、これらのエンコーダーはチャネル スタックに追加する必要があります。 ServiceHostクラスとChannelFactory<TChannel> クラスからクラスを派生させ、OnInitialize メソッドをオーバーライドして、このエンコーダー ファクトリを手動で追加できます。 カスタム バインド要素を使用してエンコーダー ファクトリを公開することもできます。

新しいカスタム バインド要素を作成するには、 BindingElement クラスからクラスを派生させます。 ただし、バインド要素にはいくつかの種類があります。 カスタム バインド要素がメッセージ エンコード バインド要素として認識されるようにするには、 MessageEncodingBindingElementも実装する必要があります。 MessageEncodingBindingElementは、新しいメッセージ エンコーダー ファクトリ (CreateMessageEncoderFactory) を作成するためのメソッドを公開します。このファクトリは、一致するメッセージ エンコーダー ファクトリのインスタンスを返すために実装されます。 さらに、 MessageEncodingBindingElement には、アドレス指定バージョンを示すプロパティがあります。 このサンプルは既存のエンコーダーをラップするため、サンプル実装では既存のエンコーダー バインド要素もラップされ、内部エンコーダー バインド要素がコンストラクターのパラメーターとして受け取られ、プロパティを介して公開されます。 次のサンプル コードは、 GZipMessageEncodingBindingElement クラスの実装を示しています。

public sealed class GZipMessageEncodingBindingElement
                        : MessageEncodingBindingElement //BindingElement
                        , IPolicyExportExtension
{

    //We use an inner binding element to store information
    //required for the inner encoder.
    MessageEncodingBindingElement innerBindingElement;

        //By default, use the default text encoder as the inner encoder.
        public GZipMessageEncodingBindingElement()
            : this(new TextMessageEncodingBindingElement()) { }

    public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
    {
        this.innerBindingElement = messageEncoderBindingElement;
    }

    public MessageEncodingBindingElement InnerMessageEncodingBindingElement
    {
        get { return innerBindingElement; }
        set { innerBindingElement = value; }
    }

    //Main entry point into the encoder binding element.
    // Called by WCF to get the factory that creates the
    //message encoder.
    public override MessageEncoderFactory CreateMessageEncoderFactory()
    {
        return new
GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
    }

    public override MessageVersion MessageVersion
    {
        get { return innerBindingElement.MessageVersion; }
        set { innerBindingElement.MessageVersion = value; }
    }

    public override BindingElement Clone()
    {
        return new
        GZipMessageEncodingBindingElement(this.innerBindingElement);
    }

    public override T GetProperty<T>(BindingContext context)
    {
        if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
        {
            return innerBindingElement.GetProperty<T>(context);
        }
        else
        {
            return base.GetProperty<T>(context);
        }
    }

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelFactory<TChannel>();
    }

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelListener<TChannel>();
    }

    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.CanBuildInnerChannelListener<TChannel>();
    }

    void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
    {
        if (policyContext == null)
        {
            throw new ArgumentNullException("policyContext");
        }
       XmlDocument document = new XmlDocument();
       policyContext.GetBindingAssertions().Add(document.CreateElement(
            GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
            GZipMessageEncodingPolicyConstants.GZipEncodingName,
            GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
    }
}

次の例に示すように、 GZipMessageEncodingBindingElement クラスは IPolicyExportExtension インターフェイスを実装しているため、このバインド要素をポリシーとしてメタデータにエクスポートできます。

<wsp:Policy wsu:Id="BufferedHttpSampleServer_ISampleServer_policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <gzip:text xmlns:gzip=
        "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1" />
       <wsaw:UsingAddressing />
     </wsp:All>
   </wsp:ExactlyOne>
</wsp:Policy>

GZipMessageEncodingBindingElementImporter クラスはIPolicyImportExtension インターフェイスを実装し、このクラスはGZipMessageEncodingBindingElementのポリシーをインポートします。 Svcutil.exe ツールを使用して、ポリシーを構成ファイルにインポートし、 GZipMessageEncodingBindingElementを処理するには、次を Svcutil.exe.configに追加する必要があります。

<configuration>
  <system.serviceModel>
    <extensions>
      <bindingElementExtensions>
        <add name="gzipMessageEncoding"
          type=
            "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bindingElementExtensions>
    </extensions>
    <client>
      <metadata>
        <policyImporters>
          <remove type=
"System.ServiceModel.Channels.MessageEncodingBindingElementImporter, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          <extension type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>

圧縮エンコーダーの一致するバインド要素が用意されたので、次のサンプル コードに示すように、新しいカスタム バインド オブジェクトを構築し、カスタム バインド要素を追加することで、サービスまたはクライアントにプログラムでフックできます。

ICollection<BindingElement> bindingElements = new List<BindingElement>();
HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement ();
bindingElements.Add(compBindingElement);
bindingElements.Add(httpBindingElement);
CustomBinding binding = new CustomBinding(bindingElements);
binding.Name = "SampleBinding";
binding.Namespace = "http://tempuri.org/bindings";

これはほとんどのユーザー シナリオで十分ですが、サービスを Web ホストする場合は、ファイル構成のサポートが重要です。 Web でホストされるシナリオをサポートするには、カスタム バインド要素をファイル内で構成できるように、カスタム構成ハンドラーを開発する必要があります。

構成システムの上にバインド要素の構成ハンドラーを構築できます。 バインド要素の構成ハンドラーは、 BindingElementExtensionElement クラスから派生する必要があります。 BindingElementExtensionElement.BindingElementTypeは、このセクション用に作成するバインド要素の種類を構成システムに通知します。 設定できる BindingElement のすべての側面は、 BindingElementExtensionElement 派生クラスのプロパティとして公開する必要があります。 ConfigurationPropertyAttributeは、構成要素の属性をプロパティにマッピングし、属性がない場合は既定値を設定するのに役立ちます。 構成の値が読み込まれ、プロパティに適用されると、 BindingElementExtensionElement.CreateBindingElement メソッドが呼び出され、プロパティがバインド要素の具体的なインスタンスに変換されます。 BindingElementExtensionElement.ApplyConfiguration メソッドは、BindingElementExtensionElement派生クラスのプロパティを、新しく作成されたバインド要素に設定する値に変換するために使用されます。

次のサンプル コードは、 GZipMessageEncodingElementの実装を示しています。

public class GZipMessageEncodingElement : BindingElementExtensionElement
{
    public GZipMessageEncodingElement()
    {
    }

//Called by the WCF to discover the type of binding element this
//config section enables
    public override Type BindingElementType
    {
        get { return typeof(GZipMessageEncodingBindingElement); }
    }

    //The only property we need to configure for our binding element is
    //the type of inner encoder to use. Here, we support text and
    //binary.
    [ConfigurationProperty("innerMessageEncoding",
                         DefaultValue = "textMessageEncoding")]
    public string InnerMessageEncoding
    {
        get { return (string)base["innerMessageEncoding"]; }
        set { base["innerMessageEncoding"] = value; }
    }

    //Called by the WCF to apply the configuration settings (the
    //property above) to the binding element
    public override void ApplyConfiguration(BindingElement bindingElement)
    {
        GZipMessageEncodingBindingElement binding =
                (GZipMessageEncodingBindingElement)bindingElement;
        PropertyInformationCollection propertyInfo =
                    this.ElementInformation.Properties;
        if (propertyInfo["innerMessageEncoding"].ValueOrigin !=
                                     PropertyValueOrigin.Default)
        {
            switch (this.InnerMessageEncoding)
            {
                case "textMessageEncoding":
                    binding.InnerMessageEncodingBindingElement =
                      new TextMessageEncodingBindingElement();
                    break;
                case "binaryMessageEncoding":
                    binding.InnerMessageEncodingBindingElement =
                         new BinaryMessageEncodingBindingElement();
                    break;
            }
        }
    }

    //Called by the WCF to create the binding element
    protected override BindingElement CreateBindingElement()
    {
        GZipMessageEncodingBindingElement bindingElement =
                new GZipMessageEncodingBindingElement();
        this.ApplyConfiguration(bindingElement);
        return bindingElement;
    }
}

この構成ハンドラーは、サービスまたはクライアントの App.config または Web.config の次の表現にマップされます。

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

この構成ハンドラーを使用するには、次のサンプル構成に示すように、<system.serviceModel> 要素内に登録する必要があります。

<extensions>
    <bindingElementExtensions>
       <add
           name="gzipMessageEncoding"
           type=
           "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
           GZipEncoder, Version=1.0.0.0, Culture=neutral,
           PublicKeyToken=null" />
      </bindingElementExtensions>
</extensions>

サーバーを実行すると、操作の要求と応答がコンソール ウィンドウに表示されます。 ウィンドウで Enter キーを押して、サーバーをシャットダウンします。

Press Enter key to Exit.

        Server Echo(string input) called:
        Client message: Simple hello

        Server BigEcho(string[] input) called:
        64 client messages

クライアントを実行すると、操作の要求と応答がコンソール ウィンドウに表示されます。 クライアント ウィンドウで Enter キーを押して、クライアントをシャットダウンします。

Calling Echo(string):
Server responds: Simple hello Simple hello

Calling BigEcho(string[]):
Server responds: Hello 0

Press <ENTER> to terminate client.

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

  1. 次のコマンド ASP.NET 使用して 4.0 をインストールします。

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Windows Communication Foundation サンプル One-Time セットアップ手順を実行していることを確認します。

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

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