当内置WCF编码器无法满足我们的要求的时候,可以根据本文介绍的方法创建一个定制的编码器,然后将其作为插件使用即可。
对于现代的程序设计来说,压缩处理几乎已经变成一个完整的设计步骤,因为串行化数据的压缩能够很好为数据瘦身,从而降低网络流量导致的花销。可以进行无缝压缩的方法有很多,不过都很难上手,并且很难成功运行,这些方法包括:
·建立在客户端和服务端进行数据数据压缩和解压缩的代理工厂。
·在服务器端添加代码,以便在实体串行化之后压缩成字节数组,然后再将其发送出去。
·在客户端进行逆过程,即添加用于解压缩的代码,以便将压缩后的字节数组转换成预定义类型。
·定制代码以便在客户端试图对响应进行解压之前判断服务器返回的是否为压缩后的响应,代码如下所示:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->Stream responseStream = WebResponse.GetResponseStream();
if (WebResponse.ContentEncoding.ToLower().Contains("gzip"))
responseStream = new GZipStream(responseStream,
CompressionMode.Decompress);
else if(WebResponse.ContentEncoding.ToLower().Contains("deflate"))
responseStream = new DeflateStream(
responseStream,CompressionMode.Decompress);
1.术语和类
下面,我们首先对下文中用到的一些处理消息和编码的术语进行简单的解释:
·消息:即包含请求和响应数据的对象。
·捆绑技术:定义数据跨端点传输方式的一个规范。它包含协议、消息编码器和端点。
·端点:接收/发送端口。
·消息编码器:消息编码器的作用是把一个消息实例串行化为一系列字节。特定消息应该使用定义在消息编码捆绑元素中的消息编码器。
WCF自身包含三个消息编码器,分别为Binary、Text 和MTOM(Message Transmission Optimization Mechanism ,消息传输优化机制)。 消息编码器首先对出站的消息进行序列化处理,然后将序列化的消息传递给传输层。另一端的消息编码器从传输层收到序列化的消息后,执行定制的行为,然后将解序列化的消息传递给协议层/发起调用的应用程序。
虽然消息编码器位于传输层,但是却无法为所有的客户端所用。幸运的是,我们可以使用WCF的端点来很轻松地克服这个问题。 我们可以设计多个端点来满足不同的客户端的需求。因此,我们可以使用多个编码器(有或者没有压缩),并让客户端根据自身情况来决定使用哪些编码器。
一个理想的解决方案是,在WCF通信双方的信道中,拿出一个信道用以识别数据是否被压缩了,如果是的话,就对压缩后的数据进行解压处理,这样就能够进行无缝的数据传输了。
有趣的是,这种定制的编码处理对于核心WCF服务开发人员来说是透明的,我们只想把定制的编码器简单挂接到客户和服务器端即可。
以下捆绑元素来自于MessageEncodingBindingElement类,这个类提供了Binary、Text和MTOM编码:
·TextMessageEncodingBindingElement:这个捆绑元素很少用到,因为对于消息来说,其编码效果是最差的。尤其是当传输大量的文本时,其效率极差,会耗费大量的网络和时间资源。
·BinaryMessageEncodingBindingElement:这个捆绑元素用得较广,因为它不仅支持消息版本控制,同时还能对基于二进制的消息进行编码。
·MTOMMessageEncodingBindingElement:这个捆绑最适合于二进制信息数据的传输。
这些捆绑元素会创建一个MessageEncoderFactory,随后MessageEncoderFactory创建单个MessageEncoder实例来把消息序列化为字节。
若要使用消息编码器,我们需要调用ReadMessage和WriteMessage方法来读写流或者字节数组。因此,若要编写一个定制的编码器,要么扩展现有消息编码器,或者为一些Message基本类如MessageEncoder、MessageEncoderFactory和MessageEncodingBindingElement提供一个定制的实现:
·MessageEncoder使用WriteMessage方法将传输的消息序列化,并使用ReadMessage方法将入站的消息解序列化。为了插入一个定制的实现,我们必须将这两个方法覆盖掉。 一般地,它们位于传输信道中,不过也可以用于信道堆栈的其他地方。
·MessageEncoderFactory规定用于配置这些服务的信息。我们需要覆盖这个抽象类中的CreateMessageEncoderFactory 方法。
·MessageEncodingBindingElement支持向捆绑元素添加编码。
为了把定制的编码器完全地集成到WCF服务中,我们还必须确保能够完全控制编码器本身,包括:
·一个开关,用于启用/停用对WCF请求的压缩,最好放到客户端配置中。
·一个开关,用于启用/禁用对WCF响应的压缩,最好放到服务器端配置中。
·一个键,用于指示定制的压缩编码器。
·扩展WCF以支持各种各样的基于SOAP的交互。例如,我们可能需要指出各种客户端,包括仍然使用.NET框架1.1的客户端。
·一个简单机制,用以确定入站的数据是否是压缩后的,以便快速响应并避免错误。
2.创建一个定制的编码器
首先,我们需要创建一个定制的MessageEncoderFactory,它能创建我们的定制的编码器对象,它需要:
·一个被覆盖的编码器对象
·一个被覆盖的消息版本
包含一个从CustomMessageEncoder工厂创建的名为CustomEncoderFactory的样本类。我们需要将该编码器标记为一个单独的CustomMessageEncoder工厂对象。
为了创建一个CustomEncoderFactory实例,需要传入两个新的东西:一个EncodeMode枚举值以及一个EnableCompression变量:
·EncodeMode是一个可根据配置值动态改变编码格式,并且无需知道任何特殊的编码器的深入知识就可以编写压缩/解压缩逻辑的枚举值。它支持各种压缩类型,包括None、Deflate、Gzip,同时,我们还可以添加更多定制的压缩编码器格式:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> /// <summary>
/// Compression Encoder formats. Add custom encoders such as
/// ICSharpLib, 7z, rar
/// </summary>
public enum CompressionEncoder
{
None,
Deflate,
GZip
}
·EnableCompression是一个布尔开关值,通过它可以启用或者禁用压缩处理。
接下来,我们需要创建一个CustomEncoder,以实现抽象类MessageEncoder,具体代码如清单2所示。清单2的示例代码实现了IsDataCompressed方法,用以确定数据是否已经压缩。对于Gzip,可以使用“幻码”值来确定数据是否经过压缩处理。
就像前面提到的那样,这个定制的编码器的编码过程是在ReadMessage和WriteMessage方法中进行的。所以,我们还需要覆盖ContentType属性来交付不同的内容类型。枚举类型的CompressionEncoder变量值决定了运行时的内容类型。
然后,我们需要创建一个CustomMessageEncodingBinding元素,以便规定可配置的定制属性,在本例中它包含EnableCompression、CompressionEncoder 和捆绑元素。
最后,我们需要创建一个CustomMessageEncodingElement元素,它派生自BindingElementExtensionElement类
从配置文件读取这些值之后,CreateBindingElement方法充当一个入口点,并将这些值转换成定制的捆绑元素的适当的属性。
我们需要注意的事项如下所示:CreateBindingElement方法,它充当一个入口点。
注意,我们可以通过配置修改捆绑元素的messageversion属性,但是为简单起见,我们在此不对此加以讨论。
·ApplyConfiguration方法,它使我们可以显式指定属性。
·ReaderQuotas,它用来给CustomMesssageEncodingBindingElement指定属性。
·值ReaderQuotas.MaxArrayLength可以控制请求大小。因为这个例子使用定制的捆绑,所以需要将其设为捆绑元素。
最后,需要实现CustomBindingElement的配置部分。对于客户端,配置看上去是这样的:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="customMessageEncoding" type="
Infrastructure.CustomEncoder.CustomMessageEncodingElement,
assemblyname" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="myBinding">
<customMessageEncoding innerMessageEncoding=
"textMessageEncoding" enableCompression="false"
compressionEncode="gzip">
<readerQuotas maxArrayLength="62914560" ></readerQuotas>
</customMessageEncoding >
<httpTransport maxBufferSize="62914560"
maxReceivedMessageSize="62914560"
authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous"
useDefaultWebProxy="true"
/>
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://127.0.0.1/mywcf.services/service1.svc"
binding="customBinding" bindingConfiguration="myBinding"
contract="IService" name="Service1">
</endpoint>
</client>
</system.serviceModel>
上面创建的定制的捆绑使用新的CustomMessageEncoding。请求通常不需要压缩,因为它们通常很小;事实上,压缩它们反而会增加请求的尺寸。因此,以上显示的客户端配置文件了enableCompression ="false"的设置。服务器配置看起来象这样:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="customMessageEncoding" type="
Infrastructure.CustomEncoder.CustomMessageEncodingElement,
assemblyname" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="myBinding">
<customMessageEncoding
innerMessageEncoding="textMessageEncoding"
enableCompression="true"
compressionEncode="gzip">
<readerQuotas
maxArrayLength="62914560" >
</readerQuotas>
</customMessageEncoding >
<httpTransport maxBufferSize="62914560"
maxReceivedMessageSize="62914560"
authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous"
useDefaultWebProxy="true" />
</binding>
</customBinding>
<bindings>
<services>
<service behaviorConfiguration="Host.Behavior"
name="Host.Service">
endpoint address=""
binding="customBinding"
bindingConfiguration="myBinding"
contract="ServiceContracts.IService" />
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
通过阅读本文,您会发现向我们的WCF消息应用程序添加定制的编码不仅简单,而且还是透明的。我们的示例代码不仅包含了文中描述的class属性的详尽的源代码,而且还提供了所需的配置章节
当内置WCF编码器无法满足我们的要求的时候,可以根据本文介绍的方法创建一个定制的编码器,然后将其作为插件使用即可。
对于现代的程序设计来说,压缩处理几乎已经变成一个完整的设计步骤,因为串行化数据的压缩能够很好为数据瘦身,从而降低网络流量导致的花销。可以进行无缝压缩的方法有很多,不过都很难上手,并且很难成功运行,这些方法包括:
·建立在客户端和服务端进行数据数据压缩和解压缩的代理工厂。
·在服务器端添加代码,以便在实体串行化之后压缩成字节数组,然后再将其发送出去。
·在客户端进行逆过程,即添加用于解压缩的代码,以便将压缩后的字节数组转换成预定义类型。
·定制代码以便在客户端试图对响应进行解压之前判断服务器返回的是否为压缩后的响应,代码如下所示:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->Stream responseStream = WebResponse.GetResponseStream();
if (WebResponse.ContentEncoding.ToLower().Contains("gzip"))
responseStream = new GZipStream(responseStream,
CompressionMode.Decompress);
else if(WebResponse.ContentEncoding.ToLower().Contains("deflate"))
responseStream = new DeflateStream(
responseStream,CompressionMode.Decompress);
1.术语和类
下面,我们首先对下文中用到的一些处理消息和编码的术语进行简单的解释:
·消息:即包含请求和响应数据的对象。
·捆绑技术:定义数据跨端点传输方式的一个规范。它包含协议、消息编码器和端点。
·端点:接收/发送端口。
·消息编码器:消息编码器的作用是把一个消息实例串行化为一系列字节。特定消息应该使用定义在消息编码捆绑元素中的消息编码器。
WCF自身包含三个消息编码器,分别为Binary、Text 和MTOM(Message Transmission Optimization Mechanism ,消息传输优化机制)。 消息编码器首先对出站的消息进行序列化处理,然后将序列化的消息传递给传输层。另一端的消息编码器从传输层收到序列化的消息后,执行定制的行为,然后将解序列化的消息传递给协议层/发起调用的应用程序。
虽然消息编码器位于传输层,但是却无法为所有的客户端所用。幸运的是,我们可以使用WCF的端点来很轻松地克服这个问题。 我们可以设计多个端点来满足不同的客户端的需求。因此,我们可以使用多个编码器(有或者没有压缩),并让客户端根据自身情况来决定使用哪些编码器。
一个理想的解决方案是,在WCF通信双方的信道中,拿出一个信道用以识别数据是否被压缩了,如果是的话,就对压缩后的数据进行解压处理,这样就能够进行无缝的数据传输了。
有趣的是,这种定制的编码处理对于核心WCF服务开发人员来说是透明的,我们只想把定制的编码器简单挂接到客户和服务器端即可。
以下捆绑元素来自于MessageEncodingBindingElement类,这个类提供了Binary、Text和MTOM编码:
·TextMessageEncodingBindingElement:这个捆绑元素很少用到,因为对于消息来说,其编码效果是最差的。尤其是当传输大量的文本时,其效率极差,会耗费大量的网络和时间资源。
·BinaryMessageEncodingBindingElement:这个捆绑元素用得较广,因为它不仅支持消息版本控制,同时还能对基于二进制的消息进行编码。
·MTOMMessageEncodingBindingElement:这个捆绑最适合于二进制信息数据的传输。
这些捆绑元素会创建一个MessageEncoderFactory,随后MessageEncoderFactory创建单个MessageEncoder实例来把消息序列化为字节。
若要使用消息编码器,我们需要调用ReadMessage和WriteMessage方法来读写流或者字节数组。因此,若要编写一个定制的编码器,要么扩展现有消息编码器,或者为一些Message基本类如MessageEncoder、MessageEncoderFactory和MessageEncodingBindingElement提供一个定制的实现:
·MessageEncoder使用WriteMessage方法将传输的消息序列化,并使用ReadMessage方法将入站的消息解序列化。为了插入一个定制的实现,我们必须将这两个方法覆盖掉。 一般地,它们位于传输信道中,不过也可以用于信道堆栈的其他地方。
·MessageEncoderFactory规定用于配置这些服务的信息。我们需要覆盖这个抽象类中的CreateMessageEncoderFactory 方法。
·MessageEncodingBindingElement支持向捆绑元素添加编码。
为了把定制的编码器完全地集成到WCF服务中,我们还必须确保能够完全控制编码器本身,包括:
·一个开关,用于启用/停用对WCF请求的压缩,最好放到客户端配置中。
·一个开关,用于启用/禁用对WCF响应的压缩,最好放到服务器端配置中。
·一个键,用于指示定制的压缩编码器。
·扩展WCF以支持各种各样的基于SOAP的交互。例如,我们可能需要指出各种客户端,包括仍然使用.NET框架1.1的客户端。
·一个简单机制,用以确定入站的数据是否是压缩后的,以便快速响应并避免错误。
2.创建一个定制的编码器
首先,我们需要创建一个定制的MessageEncoderFactory,它能创建我们的定制的编码器对象,它需要:
·一个被覆盖的编码器对象
·一个被覆盖的消息版本
包含一个从CustomMessageEncoder工厂创建的名为CustomEncoderFactory的样本类。我们需要将该编码器标记为一个单独的CustomMessageEncoder工厂对象。
为了创建一个CustomEncoderFactory实例,需要传入两个新的东西:一个EncodeMode枚举值以及一个EnableCompression变量:
·EncodeMode是一个可根据配置值动态改变编码格式,并且无需知道任何特殊的编码器的深入知识就可以编写压缩/解压缩逻辑的枚举值。它支持各种压缩类型,包括None、Deflate、Gzip,同时,我们还可以添加更多定制的压缩编码器格式:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> /// <summary>
/// Compression Encoder formats. Add custom encoders such as
/// ICSharpLib, 7z, rar
/// </summary>
public enum CompressionEncoder
{
None,
Deflate,
GZip
}
·EnableCompression是一个布尔开关值,通过它可以启用或者禁用压缩处理。
接下来,我们需要创建一个CustomEncoder,以实现抽象类MessageEncoder,具体代码如清单2所示。清单2的示例代码实现了IsDataCompressed方法,用以确定数据是否已经压缩。对于Gzip,可以使用“幻码”值来确定数据是否经过压缩处理。
就像前面提到的那样,这个定制的编码器的编码过程是在ReadMessage和WriteMessage方法中进行的。所以,我们还需要覆盖ContentType属性来交付不同的内容类型。枚举类型的CompressionEncoder变量值决定了运行时的内容类型。
然后,我们需要创建一个CustomMessageEncodingBinding元素,以便规定可配置的定制属性,在本例中它包含EnableCompression、CompressionEncoder 和捆绑元素。
最后,我们需要创建一个CustomMessageEncodingElement元素,它派生自BindingElementExtensionElement类
从配置文件读取这些值之后,CreateBindingElement方法充当一个入口点,并将这些值转换成定制的捆绑元素的适当的属性。
我们需要注意的事项如下所示:CreateBindingElement方法,它充当一个入口点。
注意,我们可以通过配置修改捆绑元素的messageversion属性,但是为简单起见,我们在此不对此加以讨论。
·ApplyConfiguration方法,它使我们可以显式指定属性。
·ReaderQuotas,它用来给CustomMesssageEncodingBindingElement指定属性。
·值ReaderQuotas.MaxArrayLength可以控制请求大小。因为这个例子使用定制的捆绑,所以需要将其设为捆绑元素。
最后,需要实现CustomBindingElement的配置部分。对于客户端,配置看上去是这样的:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="customMessageEncoding" type="
Infrastructure.CustomEncoder.CustomMessageEncodingElement,
assemblyname" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="myBinding">
<customMessageEncoding innerMessageEncoding=
"textMessageEncoding" enableCompression="false"
compressionEncode="gzip">
<readerQuotas maxArrayLength="62914560" ></readerQuotas>
</customMessageEncoding >
<httpTransport maxBufferSize="62914560"
maxReceivedMessageSize="62914560"
authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous"
useDefaultWebProxy="true"
/>
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://127.0.0.1/mywcf.services/service1.svc"
binding="customBinding" bindingConfiguration="myBinding"
contract="IService" name="Service1">
</endpoint>
</client>
</system.serviceModel>
上面创建的定制的捆绑使用新的CustomMessageEncoding。请求通常不需要压缩,因为它们通常很小;事实上,压缩它们反而会增加请求的尺寸。因此,以上显示的客户端配置文件了enableCompression ="false"的设置。服务器配置看起来象这样:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="customMessageEncoding" type="
Infrastructure.CustomEncoder.CustomMessageEncodingElement,
assemblyname" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="myBinding">
<customMessageEncoding
innerMessageEncoding="textMessageEncoding"
enableCompression="true"
compressionEncode="gzip">
<readerQuotas
maxArrayLength="62914560" >
</readerQuotas>
</customMessageEncoding >
<httpTransport maxBufferSize="62914560"
maxReceivedMessageSize="62914560"
authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous"
useDefaultWebProxy="true" />
</binding>
</customBinding>
<bindings>
<services>
<service behaviorConfiguration="Host.Behavior"
name="Host.Service">
endpoint address=""
binding="customBinding"
bindingConfiguration="myBinding"
contract="ServiceContracts.IService" />
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
通过阅读本文,您会发现向我们的WCF消息应用程序添加定制的编码不仅简单,而且还是透明的。我们的示例代码不仅包含了文中描述的class属性的详尽的源代码,而且还提供了所需的配置章节