扩展 WCF - 第二部分






4.96/5 (38投票s)
一篇关于 WCF 数据压缩/解压缩的文章
引言
本文是我第一篇文章 扩展 WCF 第一部分的延续。在这篇文章中,我将通过扩展 WCF 框架,向您展示如何在服务端发送 WCF 数据之前对其进行压缩,以及如何在客户端接收数据之后对其进行解压缩。当服务返回大量数据,或者客户端的网络连接带宽较低时,压缩/解压缩非常有用。在这种情况下,您可能会发现服务操作本身花费的时间比传输数据的时间要短。最终,服务客户端需要更长时间来等待结果。IIS 提供了 HTTP 压缩 技术来解决这个问题。但是,在开启 HTTP 压缩后,您很有可能遇到 IIS 上托管的其他网站出现问题。我首先在 MSDN 上找到了解决方案。我使用了这里的代码,并进行了一些修改和修复。
背景
编码是将消息转换为字节序列的过程。解码是逆向过程。Windows Communication Foundation (WCF) 包含三种用于 SOAP 消息的编码类型:文本、二进制和消息传输优化机制 (MTOM)。有关更多信息,请参见 MSDN。
图 1 显示了我们将在 WCF 体系结构中进行扩展的位置。
典型的 wsHttpBinding
可能如下所示:
<bindings>
<wsHttpBinding>
<binding name="MyHttpBinding" openTimeout="00:05:00" sendTimeout="00:05:00"
messageEncoding="Mtom">
<readerQuotas maxDepth="999999"
maxArrayLength="999999" maxBytesPerRead="999999"
maxNameTableCharCount="999999" />
</binding>
</wsHttpBinding>
</bindings>
在上面的绑定示例中,我将 messageEncoding
选项设置为 "Mtom
"。默认选项是 "Text
"。
现在,让我们看看 WCF 框架中的绑定元素结构:
红色点线以下的元素是我们扩展的类。对于 WCF 框架提供的绑定,我们只能使用框架提供的 3 种编码选项。要使用自定义编码,我们需要使用配置编写自定义绑定。在 ExtendedMessageEncodingBindingElement
的重写方法 CreateMessageEncoderFactory()
中,我们返回 ExtendedMessageEncoderFactory
类。
//Main entry point into the encoder binding element.
//Called by WCF to get the factory that will create the
//message encoder
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new ExtendedMessageEncoderFactory
(innerBindingElement.CreateMessageEncoderFactory(), _encoderType);
}
在 ExtendedMessageEncoderFactory
的构造函数中,我们构建自定义消息编码器。
//The GZip encoder wraps an inner encoder
//We require a factory to be passed in that will create this inner encoder
public ExtendedMessageEncoderFactory
(MessageEncoderFactory messageEncoderFactory, string encoderType)
{
if (messageEncoderFactory == null)
throw new ArgumentNullException("messageEncoderFactory",
"A valid message encoder factory must be passed to the GZipEncoder");
var messageEncoderType = Type.GetType(encoderType);
encoder = (MessageEncoder)Activator.CreateInstance
(messageEncoderType, messageEncoderFactory.Encoder);
}
WcfExtensions.MessageEncodingBindingElementExtension
是我们扩展的类,它为我们提供了将自定义消息编码器集成到系统并正确配置的选项。GZipMessageEncoder
类使用 System.IO.Compression.GZipStream
来压缩和解压缩数据。
//Helper method to compress an array of bytes
static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer,
BufferManager bufferManager, int messageOffset)
{
//Display the Compressed and uncompressed sizes
Console.WriteLine("Original message is {0} bytes", buffer.Count);
var memoryStream = new MemoryStream();
memoryStream.Write(buffer.Array, 0, messageOffset);
using (var gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
{
gzStream.Write(buffer.Array, messageOffset, buffer.Count);
}
var compressedBytes = memoryStream.ToArray();
var bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);
Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
var byteArray = new ArraySegment<byte>
(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset);
Console.WriteLine("GZipCompressed message is {0} bytes", byteArray.Count);
return byteArray;
}
//Helper method to decompress an array of bytes
static ArraySegment<byte> DecompressBuffer
(ArraySegment<byte> buffer, BufferManager bufferManager)
{
Console.WriteLine(string.Format
("Compressed buffer size is {0} bytes", buffer.Count));
var memoryStream = new MemoryStream
(buffer.Array, buffer.Offset, buffer.Count - buffer.Offset);
var decompressedStream = new MemoryStream();
var totalRead = 0;
var blockSize = 1024;
var tempBuffer = bufferManager.TakeBuffer(blockSize);
using (var gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
while (true)
{
var bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
if (bytesRead == 0)
break;
decompressedStream.Write(tempBuffer, 0, bytesRead);
totalRead += bytesRead;
}
}
bufferManager.ReturnBuffer(tempBuffer);
var decompressedBytes = decompressedStream.ToArray();
var bufferManagerBuffer = bufferManager.TakeBuffer
(decompressedBytes.Length + buffer.Offset);
Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
Array.Copy(decompressedBytes, 0, bufferManagerBuffer,
buffer.Offset, decompressedBytes.Length);
var byteArray = new ArraySegment<byte>
(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
Console.WriteLine(string.Format
("Decompressed buffer size is {0} bytes", byteArray.Count));
return byteArray;
}
Using the Code
为简单起见,我将 WCF 服务托管在控制台。要运行示例代码并查看其工作效果,请按照以下步骤操作:
- 使用 VS 打开 "
ExtendingWCFPartII
" 解决方案后,右键单击ConsoleServices
项目,然后单击 Debug -- > Start new instance。这样做,您就确保了服务已在控制台主机中运行。 - 然后以调试模式运行
WpfClient
项目。
WcfExtentions.dll 是一个可重用组件,将同时用于 WCF 客户端和 WCF 服务。
在 WCF 服务中使用代码
首先,我将向您展示如何使用此组件来托管我们的 WCF 服务。您可以将 WCF 服务托管在 IIS、Windows 服务或控制台中,无论您在哪里托管它,请在您的 app.config 或 web.config 的 ServiceModel
部分添加以下配置。为您的服务主机应用程序添加对 WcfExtentions.dll 的引用。
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedServiceBehavior"
type="WcfExtensions.ServiceBehaviorExtension, WcfExtensions,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
<bindingElementExtensions>
<add name="customMessageEncoding"
type="WcfExtensions.MessageEncodingBindingElementExtension,
WcfExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</bindingElementExtensions>
</extensions>
</system.serviceModel>
WcfExtentions.ServiceBehaviorExtension
用于告知 WCF 框架在其 CreateBehavior()
方法中使用 ExtendedServiceBehavior
。而 WcfExtensions.MessageEncodingBindingElementExtension
用于告知 WCF 框架使用 WcfExtentions.ExtendedMessageEncodingBindingElement
进行消息编码和解码。
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ExtendedServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="16"
maxConcurrentSessions="16" />
<dataContractSerializer maxItemsInObjectGraph="999999"/>
<ExtendedServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
现在,像上面的示例一样创建一个服务行为。<ExtendedServiceBehavior />
确保此行为配置将使用 ExtendedServiceBehavior
。
<system.serviceModel>
<bindings>
<customBinding>
<binding name="ZipBinding" closeTimeout="00:10:00"
openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00">
<customMessageEncoding innerMessageEncoding="mtomMessageEncoding"
messageEncoderType="WcfExtensions.GZipMessageEncoder, WcfExtensions">
<readerQuotas maxDepth="999999999" maxStringContentLength="999999999"
maxArrayLength="999999999" maxBytesPerRead="999999999"
maxNameTableCharCount="999999999">
</readerQuotas>
</customMessageEncoding>
<httpTransport maxBufferSize="999999999"
maxReceivedMessageSize="999999999" authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous" useDefaultWebProxy="true"/>
</binding>
</customBinding>
</bindings>
</system.serviceModel>
在此自定义绑定中使用了绑定元素扩展 "customMessageEncoding
"。
<system.serviceModel>
<services>
<service behaviorConfiguration="ExtendedServiceBehavior"
name="ConsoleServices.FileService">
<endpoint address="FileService" binding="customBinding"
bindingConfiguration="ZipBinding" contract="Common.Services.IFileService" />
<endpoint address="FileService/mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
这就是托管 WCF 服务所需的所有操作,该服务将包含在 引言 中解释的压缩功能。
在 WCF 客户端中使用代码
在客户端应用程序中添加对 WcfExtentions.dll 的引用。现在创建外部配置文件。它可能如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedServiceBehavior"
type="WcfExtensions.ServiceBehaviorExtension, WcfExtensions,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
<bindingElementExtensions>
<add name="customMessageEncoding"
type="WcfExtensions.MessageEncodingBindingElementExtension,
WcfExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</bindingElementExtensions>
</extensions>
<bindings>
.. tips: provide the same binding which has been used at service
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="CustomServiceEndpointBehavior">
<ExtendedServiceBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://:8036/Services/CustomerService"
binding="customBinding" bindingConfiguration="ZipBinding"
behaviorConfiguration="CustomServiceEndpointBehavior"
contract="Common.Services.IFileService" name="FileServiceEndPoint" />
</client>
</system.serviceModel>
</configuration>
现在,将此外部配置文件保存在硬盘上的任何位置,并使用 .xml 或 .config 扩展名,例如 "C:\Temp\MyAppServices.xml"。
我提供了一个非常简单的接口 WcfExtentions.WcfClientHelper
来获取代理实例。
public static T GetProxy<T>(string externalConfigPath)
{
var channelFactory = new ExtendedChannelFactory<T>(externalConfigPath);
channelFactory.Open();
return channelFactory.CreateChannel();
}
在客户端代码中,使用此接口调用您的服务方法:
var externalConfigPath = @"C:\Temp\MyAppServices.xml";
var proxy = WcfClientHelper.GetProxy<IMyWCFService>(externalConfigPath);
proxy.CallServiceMethod();
关注点
可以使用您自己实现的 MessageEncoder
。首先,编写自己的扩展消息编码器,并在绑定配置中提供该类型,如下所示。我通过在 WcfExtensions.MessageEncodingBindingElementExtension
类中引入一个额外的配置属性 "MessageEncoderType
" 来实现这一点。
<system.serviceModel>
<bindings>
<customBinding>
<binding name="ZipBinding" closeTimeout="00:10:00"
openTimeout="00:10:00" receiveTimeout="00:10:00"
sendTimeout="00:10:00">
<customMessageEncoding innerMessageEncoding="mtomMessageEncoding"
messageEncoderType="YourAssemblyName.YourMessageEncoder,
WcfExtensions">
<readerQuotas maxDepth="999999999"
maxStringContentLength="999999999"
maxArrayLength="999999999" maxBytesPerRead="999999999"
maxNameTableCharCount="999999999">
</readerQuotas>
</customMessageEncoding>
<httpTransport maxBufferSize="999999999"
maxReceivedMessageSize="999999999"
authenticationScheme="Anonymous"
proxyAuthenticationScheme="Anonymous" useDefaultWebProxy="true"/>
</binding>
</customBinding>
</bindings>
</system.serviceModel>
历史
- 2010 年 1 月 20 日:初始版本