共用方式為


自訂訊息編碼器:壓縮編碼器

壓縮範例示範如何使用 Windows Communication Foundation (WCF) 平台實作自定義編碼器。

範例詳情

此範例包含用戶端控制台程式(.exe)、自我裝載服務控制台程式(.exe)和壓縮訊息編碼器連結庫(.dll)。 服務會實作定義要求-回復通訊模式的合約。 合約是由 ISampleServer 介面所定義,其會公開基本字串回應作業 (EchoBigEcho)。 用戶端對指定的作業提出同步要求,服務則會將訊息重複回傳給用戶端。 主控台視窗中會顯示客戶端和服務活動。 此範例的意圖是示範如何撰寫自定義編碼器,並示範在線路上壓縮訊息的影響。 您可以將監控工具新增至壓縮訊息編碼器,以計算訊息大小、處理時間或兩者。

備註

在 .NET Framework 4 中,如果伺服器傳送壓縮的回應(使用 GZip 或 Deflate 等演算法建立),WCF 用戶端上就會啟用自動解壓縮。 如果服務是以 Internet Information Server(IIS)進行 Web 裝載,則可以設定 IIS 以便服務傳送壓縮的回應。 如果需求是在用戶端和服務上執行壓縮和解壓縮,或服務是自我裝載,則可以使用此範例。

此範例示範如何建置和整合自定義訊息編碼器至 WCF 應用程式。 程式庫 GZipEncoder.dll 同時部署於用戶端和服務端。 此範例也會示範壓縮訊息的影響。 GZipEncoder.dll 中的程式代碼示範下列各項:

  • 建置自定義編碼器和編碼器處理站。

  • 開發自定義編碼器的綁定項。

  • 使用自定義系結組態來整合自定義綁定項。

  • 開發自定義組態處理程式,以允許自定義綁定項的檔案組態。

如先前所述,自定義編碼器中會實作數個圖層。 為了更清楚地說明這兩個層之間的關聯性,服務啟動事件的簡化順序如下:

  1. 伺服器隨即啟動。

  2. 會讀取組態資訊。

    1. 服務組態會註冊自定義組態處理程式。

    2. 服務主機隨即建立並開啟。

    3. 自定義組態專案會建立並傳回自定義綁定項。

    4. 自定義綁定項會建立並傳回訊息編碼器處理站。

  3. 收到訊息。

  4. 訊息編碼器處理站會傳回訊息編碼器,以便讀取訊息並寫出回應。

  5. 編碼器層會實作為類別處理站。 只有編碼器類別工廠必須公開用於自定義編碼器。 ServiceHostChannelFactory<TChannel> 物件建立時,綁定元素會傳回 Factory 物件。 訊息編碼器可以在緩衝或串流模式中運作。 此範例示範緩衝模式和串流模式。

對於每個模式,抽象ReadMessage類上都有附帶的WriteMessageMessageEncoder方法。 大部分的編碼工作都發生在這些方法中。 此範例會包裝現有的文字和二進位訊息編碼器。 這讓此範例可以將訊息的線路表示的讀取與寫入委派給內部編碼器,同時壓縮編碼器則能對結果進行壓縮或解壓縮。 由於訊息編碼沒有管線,因此這是在 WCF 中使用多個編碼器的唯一模型。 一旦訊息被解壓縮之後,產生的訊息就會往上傳遞至堆疊,以供通道堆疊處理。 在壓縮期間,產生的壓縮訊息會直接寫入提供的數據流。

此範例會使用協助程式方法 (CompressBufferDecompressBuffer) 來執行從緩衝區到數據流的轉換,以使用 GZipStream 類別。

緩衝處理 ReadMessageWriteMessage 類別會使用 BufferManager 類別。 編碼器只能透過編碼器處理站存取。 抽象 MessageEncoderFactory 類提供名為 Encoder 的屬性,用來存取目前的編碼器,還有一個方法 CreateSessionEncoder,用以建立支援會話的編碼器。 這類編碼器可以在通道支援會話、排序且可靠的情況下使用。 此場景允許在每次網路數據寫入的會話中進行優化。 如果不需要,則基底方法不應該重載。 屬性 Encoder 提供存取無會話編碼器的機制,而方法的預設實作 CreateSessionEncoder 會傳回 屬性的值。 由於範例會包裝現有的編碼器以提供壓縮,因此實 MessageEncoderFactory 作會接受 MessageEncoderFactory ,代表內部編碼器處理站。

現在已定義編碼器和編碼器處理站,就可以與 WCF 用戶端和服務搭配使用。 不過,這些編碼器必須新增至通道堆疊。 您可以從 ServiceHostChannelFactory<TChannel> 類別衍生類別,並覆寫 OnInitialize 方法來手動新增此編碼器工廠。 您也可以透過自定義繫結元素公開編碼器工廠。

若要建立新的自定義綁定項,請從 BindingElement 類別衍生類別。 不過,有數種類型的綁定項。 若要確保自訂綁定項已辨識為訊息編碼綁定項,您也必須實作 MessageEncodingBindingElementMessageEncodingBindingElement會公開方法來建立新的訊息編碼器處理站 (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 範例 中的指示。