65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 WCF - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (38投票s)

2010 年 1 月 21 日

CPOL

4分钟阅读

viewsIcon

119961

downloadIcon

2512

一篇关于 WCF 数据压缩/解压缩的文章

引言

本文是我第一篇文章 扩展 WCF 第一部分的延续。在这篇文章中,我将通过扩展 WCF 框架,向您展示如何在服务端发送 WCF 数据之前对其进行压缩,以及如何在客户端接收数据之后对其进行解压缩。当服务返回大量数据,或者客户端的网络连接带宽较低时,压缩/解压缩非常有用。在这种情况下,您可能会发现服务操作本身花费的时间比传输数据的时间要短。最终,服务客户端需要更长时间来等待结果。IIS 提供了 HTTP 压缩 技术来解决这个问题。但是,在开启 HTTP 压缩后,您很有可能遇到 IIS 上托管的其他网站出现问题。我首先在 MSDN 上找到了解决方案。我使用了这里的代码,并进行了一些修改和修复。

背景

编码是将消息转换为字节序列的过程。解码是逆向过程。Windows Communication Foundation (WCF) 包含三种用于 SOAP 消息的编码类型:文本、二进制和消息传输优化机制 (MTOM)。有关更多信息,请参见 MSDN

图 1

图 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 框架中的绑定元素结构:

图 2

红色点线以下的元素是我们扩展的类。对于 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 服务托管在控制台。要运行示例代码并查看其工作效果,请按照以下步骤操作:

  1. 使用 VS 打开 "ExtendingWCFPartII" 解决方案后,右键单击 ConsoleServices 项目,然后单击 Debug -- > Start new instance。这样做,您就确保了服务已在控制台主机中运行。
  2. 然后以调试模式运行 WpfClient 项目。
    WcfExtentions.dll 是一个可重用组件,将同时用于 WCF 客户端和 WCF 服务。

在 WCF 服务中使用代码

首先,我将向您展示如何使用此组件来托管我们的 WCF 服务。您可以将 WCF 服务托管在 IIS、Windows 服务或控制台中,无论您在哪里托管它,请在您的 app.configweb.configServiceModel 部分添加以下配置。为您的服务主机应用程序添加对 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 日:初始版本
© . All rights reserved.