WCF中自定义消息编码器:压缩编码器的使用

原文: WCF中自定义消息编码器:压缩编码器的使用

通过抓包知道WCF在提交、返回数据的时候大多使用XML进行数据交互,如果返回DataTable那么这些数据将变得很大,通过查询找到一个对数据压缩的方法:

http://msdn.microsoft.com/zh-cn/library/ms751458(v=vs.110).aspx

新增项目GZipEncoder,GzipEncoder中增加三个文件 :

WCF中自定义消息编码器:压缩编码器的使用

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中自定义消息编码器:压缩编码器的使用

 

      WCF中自定义消息编码器:压缩编码器的使用

 

 另外出现第一次调用WCF很慢的解决方法是将 useDefaultWebProxy 这个属性改成false,不让它找代理。这样就会在程序第一次调用WCF的时候快很多。

WCF中自定义消息编码器:压缩编码器的使用

 

 

 

你可能感兴趣的:(WCF)