标签:
原文:WCF中自定义消息编码器:压缩编码器的使用通过抓包知道WCF在提交、返回数据的时候大多使用XML进行数据交互,如果返回DataTable那么这些数据将变得很大,通过查询找到一个对数据压缩的方法:
http://msdn.microsoft.com/zh-cn/library/ms751458(v=vs.110).aspx
新增项目GZipEncoder,GzipEncoder中增加三个文件 :
GZipMessageEncoderFactory.cs
using System; using System.Collections.Generic; using System.Configuration; using System.Text; using System.IO; using System.IO.Compression; using System.Runtime.Serialization; using System.Runtime.InteropServices; using System.Security; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Configuration; using System.Xml; using System.ServiceModel.Security; using System.ServiceModel.Description; namespace GZipEncoder { //This class is used to create the custom encoder (GZipMessageEncoder) internal class GZipMessageEncoderFactory : MessageEncoderFactory { MessageEncoder encoder; //The GZip encoder wraps an inner encoder //We require a factory to be passed in that will create this inner encoder public GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory) { if (messageEncoderFactory == null) throw new ArgumentNullException("messageEncoderFactory", "A valid message encoder factory must be passed to the GZipEncoder"); encoder = new GZipMessageEncoder(messageEncoderFactory.Encoder); } //The service framework uses this property to obtain an encoder from this encoder factory public override MessageEncoder Encoder { get { return encoder; } } public override MessageVersion MessageVersion { get { return encoder.MessageVersion; } } //This is the actual GZip encoder class GZipMessageEncoder : MessageEncoder { static string GZipContentType = "application/x-gzip"; //This implementation wraps an inner encoder that actually converts a WCF Message //into textual XML, binary XML or some other format. This implementation then compresses the results. //The opposite happens when reading messages. //This member stores this inner encoder. MessageEncoder innerEncoder; //We require an inner encoder to be supplied (see comment above) internal GZipMessageEncoder(MessageEncoder messageEncoder) : base() { if (messageEncoder == null) throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder"); innerEncoder = messageEncoder; } //public override string CharSet //{ // get { return ""; } //} public override string ContentType { get { return GZipContentType; } } public override string MediaType { get { return GZipContentType; } } //SOAP version to use - we delegate to the inner encoder for this public override MessageVersion MessageVersion { get { return innerEncoder.MessageVersion; } } //Helper method to compress an array of bytes static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset) { MemoryStream memoryStream = new MemoryStream(); memoryStream.Write(buffer.Array, 0, messageOffset); using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { gzStream.Write(buffer.Array, messageOffset, buffer.Count); } byte[] compressedBytes = memoryStream.ToArray(); byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length); Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length); bufferManager.ReturnBuffer(buffer.Array); ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset); return byteArray; } //Helper method to decompress an array of bytes static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager) { MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count - buffer.Offset); MemoryStream decompressedStream = new MemoryStream(); int totalRead = 0; int blockSize = 1024; byte[] tempBuffer = bufferManager.TakeBuffer(blockSize); using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { while (true) { int bytesRead = gzStream.Read(tempBuffer, 0, blockSize); if (bytesRead == 0) break; decompressedStream.Write(tempBuffer, 0, bytesRead); totalRead += bytesRead; } } bufferManager.ReturnBuffer(tempBuffer); byte[] decompressedBytes = decompressedStream.ToArray(); byte[] 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); ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length); bufferManager.ReturnBuffer(buffer.Array); return byteArray; } //One of the two main entry points into the encoder. Called by WCF to decode a buffered byte array into a Message. public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType) { //Decompress the buffer ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager); //Use the inner encoder to decode the decompressed buffer Message returnMessage = innerEncoder.ReadMessage(decompressedBuffer, bufferManager); returnMessage.Properties.Encoder = this; return returnMessage; } //One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array. public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) { //Use the inner encoder to encode a Message into a buffered byte array ArraySegment<byte> buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset); //Compress the resulting byte array return CompressBuffer(buffer, bufferManager, messageOffset); } public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType) { GZipStream gzStream = new GZipStream(stream, CompressionMode.Decompress, true); return innerEncoder.ReadMessage(gzStream, maxSizeOfHeaders); } public override void WriteMessage(Message message, System.IO.Stream stream) { using (GZipStream gzStream = new GZipStream(stream, CompressionMode.Compress, true)) { innerEncoder.WriteMessage(message, gzStream); } // innerEncoder.WriteMessage(message, gzStream) depends on that it can flush data by flushing // the stream passed in, but the implementation of GZipStream.Flush will not flush underlying // stream, so we need to flush here. stream.Flush(); } } } }
GZipMessageEncodingBindingElement.cs
using System; using System.Xml; using System.ServiceModel; using System.Configuration; using System.ServiceModel.Channels; using System.ServiceModel.Configuration; using System.ServiceModel.Description; namespace GZipEncoder { // This is constants for GZip message encoding policy. static class GZipMessageEncodingPolicyConstants { public const string GZipEncodingName = "GZipEncoding"; public const string GZipEncodingNamespace = "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1"; public const string GZipEncodingPrefix = "gzip"; } //This is the binding element that, when plugged into a custom binding, will enable the GZip encoder public sealed class GZipMessageEncodingBindingElement : MessageEncodingBindingElement //BindingElement , IPolicyExportExtension { //We will 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 will create 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)); } } //This class is necessary to be able to plug in the GZip encoder binding element through //a configuration file 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; } } }
GZipMessageEncodingBindingElementImporter.cs
using System; using System.Xml; using System.ServiceModel.Description; using System.Xml.Schema; using System.Collections.ObjectModel; using System.Collections.Generic; using System.Text; namespace GZipEncoder { public class GZipMessageEncodingBindingElementImporter : IPolicyImportExtension { public GZipMessageEncodingBindingElementImporter() { } void IPolicyImportExtension.ImportPolicy(MetadataImporter importer, PolicyConversionContext context) { if (importer == null) { throw new ArgumentNullException("importer"); } if (context == null) { throw new ArgumentNullException("context"); } ICollection<XmlElement> assertions = context.GetBindingAssertions(); foreach (XmlElement assertion in assertions) { if ((assertion.NamespaceURI == GZipMessageEncodingPolicyConstants.GZipEncodingNamespace) && (assertion.LocalName == GZipMessageEncodingPolicyConstants.GZipEncodingName) ) { assertions.Remove(assertion); context.BindingElements.Add(new GZipMessageEncodingBindingElement()); break; } } } } }
WCF Server 和 Client 都引用项目 GZipEncoder,然后修改config文件
Server web.config 中增加:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <!-- 省略 --> </appSettings> <system.web> <compilation debug="true" targetFramework="4.0" /> <customErrors mode="Off"/> </system.web> <system.serviceModel> <!-- GZipEncoder 加密部分 Begin --> <services><!-- name:命名空间.类名 --> <service name="WcfService.action"> <endpoint address="" binding="customBinding" bindingConfiguration="BufferedHttpSampleServer" bindingName="BufferedHttpSampleServer" contract="WcfService.Iaction" /> </service> </services><!-- 注册 gzipMessageEncoding --> <extensions> <bindingElementExtensions> <add name="gzipMessageEncoding" type="GZipEncoder.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </bindingElementExtensions> </extensions> <bindings> <customBinding> <binding name="BufferedHttpSampleServer"><!-- 处理程序映射 --> <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" /> <httpTransport hostNameComparisonMode="StrongWildcard" manualAddressing="False" maxReceivedMessageSize="65536" authenticationScheme="Anonymous" bypassProxyOnLocal="False" realm="" useDefaultWebProxy="True" /> </binding> </customBinding> </bindings> <!-- GZipEncoder 加密部分 End --> <behaviors> <serviceBehaviors> <behavior> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 --> <serviceMetadata httpGetEnabled="true"/> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 --> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"/> </system.webServer> </configuration>
客户端的app.config 配置:
<?xml version="1.0"?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> <supportedRuntime version="v2.0.50727"/> </startup> <system.serviceModel> <bindings> <customBinding> <binding name="WSHttpBinding_SwfBuilderService"> <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" /> <httpTransport manualAddressing="false" authenticationScheme="Anonymous" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" proxyAuthenticationScheme="Anonymous" realm="" useDefaultWebProxy="true" /> </binding> </customBinding> </bindings> <client> <endpoint address="http://192.168.1.212/action.svc" binding="customBinding" bindingConfiguration="WSHttpBinding_SwfBuilderService" contract="WcfService.Iaction" name="WSHttpBinding_SwfBuilderService"> </endpoint> <metadata> <policyImporters> <extension type="GZipEncoder.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </policyImporters> </metadata> </client> <!-- gzip 扩展 --> <extensions> <bindingElementExtensions> <add name="gzipMessageEncoding" type="GZipEncoder.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </bindingElementExtensions> </extensions> </system.serviceModel> </configuration>
这样就完成了WCF的数据压缩,其他代码都不用动,直接可以使用
通过抓包看到Send和Recv的数据都为 application/x-gzip 格式
另外出现第一次调用WCF很慢的解决方法是将 useDefaultWebProxy 这个属性改成false,不让它找代理。这样就会在程序第一次调用WCF的时候快很多。
标签:
原文地址:http://www.cnblogs.com/lonelyxmas/p/4634936.html