自定义消息编码器:压缩编码器

压缩示例演示如何使用 Windows Communication Foundation (WCF) 平台实现自定义编码器。

示例详细信息

此示例由客户端控制台程序(.exe)、自承载服务控制台程序(.exe)和压缩消息编码器库(.dll)组成。 该服务实现定义请求-回复通信模式的协定。 合同由 ISampleServer 接口规定,该接口公开基本字符串回显操作(EchoBigEcho)。 客户端向给定操作发出同步请求,服务通过重复将消息返回给客户端。 客户端和服务活动在控制台窗口中可见。 此示例的目的是说明如何编写自定义编码器并演示消息在网络上压缩的影响。 可以为压缩消息编码器添加计算消息大小和/或处理时间的方法。

注释

在 .NET Framework 4 中,如果服务器发送压缩响应(使用 GZip 或 Deflate 等算法创建),则已在 WCF 客户端上启用自动解压缩。 如果服务在 Internet 信息服务器(IIS)上托管,则可以配置 IIS,使其服务发送压缩响应。 如果要求对客户端和服务执行压缩和解压缩,或者该服务是自承载的,则可以使用此示例。

此示例演示如何生成自定义消息编码器并将其集成到 WCF 应用程序中。 库 GZipEncoder.dll 随客户端和服务一起部署。 此示例还演示了压缩消息的影响。 GZipEncoder.dll 中的代码演示了以下内容:

  • 构建自定义编码器和编码器工厂。

  • 为自定义编码器开发绑定元素。

  • 使用自定义绑定配置来集成自定义绑定元素。

  • 开发自定义配置处理程序以允许自定义绑定元素的文件配置。

如前所述,在自定义编码器中实现了多个层。 为了更好地说明这些层之间的关系,服务启动事件的简化顺序如下:

  1. 服务器启动。

  2. 读取配置信息。

    1. 服务配置对自定义配置处理程序进行注册。

    2. 创建并打开服务主机。

    3. 自定义配置元素创建并返回自定义绑定元素。

    4. 自定义绑定元素创建并返回消息编码器工厂。

  3. 接收消息。

  4. 消息编码器工厂返回消息编码器,用于在消息中读取和写出响应。

  5. 编码器层作为类工厂实现。 对于自定义编码器,只有编码器类工厂才是必须公开的。 在ServiceHostChannelFactory<TChannel>对象被创建时,绑定元素将返回工厂对象。 消息编码器可以在缓冲模式或流式处理模式下运行。 此示例演示缓冲模式和流式处理模式。

对于每个模式,抽象ReadMessage类上都有一个与之对应的WriteMessageMessageEncoder方法。 大多数编码工作都发生在这些方法中。 该示例包装现有文本和二进制消息编码器。 这允许该示例将对消息网络表示形式的读写操作委托给内部编码器,并允许压缩编码器对结果进行压缩或解压缩。 由于没有消息编码管道,因此这是在 WCF 中使用多个编码器的唯一模型。 解压缩消息后,生成的消息会向上传递堆栈,供通道堆栈处理。 在压缩期间,生成的压缩消息将直接写入提供的流。

此示例使用帮助程序方法 (CompressBufferDecompressBuffer) 执行从缓冲区到流的转换以使用该 GZipStream 类。

缓冲ReadMessage类和WriteMessage类利用BufferManager类。 编码器只能通过编码器工厂进行访问。 抽象 MessageEncoderFactory 类提供一个用于 Encoder 访问当前编码器的属性,并提供一个用于 CreateSessionEncoder 创建支持会话的编码器的方法。 可以在通道支持会话、排序且可靠的情况下使用此类编码器。 对于写入网络中的数据,此方案允许在每个会话中进行优化。 如果不需要,则不应重载基方法。 该 Encoder 属性提供用于访问无会话编码器的机制,该方法的默认实现 CreateSessionEncoder 返回属性的值。 由于此示例通过包装现有的编码器来提供压缩,因此 MessageEncoderFactory 实现接受一个表示内部编码器工厂的 MessageEncoderFactory

现在,编码器和编码器工厂已定义,它们可与 WCF 客户端和服务一起使用。 但是,必须将这些编码器添加到通道堆栈。 您可以从 ServiceHostChannelFactory<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 示例中的说明进行操作。