Here is short description and set of links about custom serialization in WCF.
http://msdn.microsoft.com/en-us/magazine/cc163302.aspx
http://sharpserializer.codeplex.com/
http://wcfpro.wordpress.com/2011/03/03/idispatchmessageformatter/
http://wcfpro.wordpress.com
http://www.codeproject.com/KB/WCF/extension-wcf-behavior.aspx?display=Print
The problem was following. We are not satisfied with DataContractSerializer performance and would like to use own custom serializar.
For the first glance, this seems strangem because DataContractSerializer is main feature of WCF services. It's very fast and easy to use. It's true.
But when you need to send quite big collection of large objects from client to server, you meet the problem of large size of messages.
Reason of it, that result of data contract serializer is xml. And node names are equal to names of properties.
Besides that, null values present in the xml.
It's not very efficient.
Standard BinaryMessage encoding has practically the same message size. You can see object property names in the output.
Solution - using of custom serializer.
The only issue that there is not much information about this.
Solution is following.
According to the msdn article Extending WCF with Custom Behaviors, the only place for custom serializer is dispatcher. MSDN offers to implement own IDispatchMessageFormatter. It will deserialize requests from clients and serialize responces.
The same on the client - IClientMessageFormatter.
Here is what I have implemented for my case:
I used SharpSerializer.
As you can see, I still use DataContractSerializer here. The aim is to create prototype WCF Message. With all headers. I just don't know how to implement it myself.
Next step is to apply this for your WCF service. As wcfpro.wordpress advices, I have implemented IOperationBehavior interface. But with some small changes.
This will allow you to turn you serializer on with following way:
Have you noticed CustomOperationBehavior attribute?
It's practically everything. No one change in the DataContracts.
The last thing is client tuning.
This is really all. Custom serializer will work. And you'll see in the Fiddler different message, not xml.
Of course you can use any serializer you like. The tricky part is implementing of formetters.
You should be sure, that serializer works correctly.
You should control all process of creation WCF message, control headers, Action property and so on. This is the infinite source of erros.
And with custom serializer you may not receive FaultExceptions. So you should control messages using some web debugger. For example, Fiddler.
Restrictions of the current solution - client and server should have shared library with data contracts. But this restriction refers to the current serializer.
http://msdn.microsoft.com/en-us/magazine/cc163302.aspx
http://sharpserializer.codeplex.com/
http://wcfpro.wordpress.com/2011/03/03/idispatchmessageformatter/
http://wcfpro.wordpress.com
http://www.codeproject.com/KB/WCF/extension-wcf-behavior.aspx?display=Print
The problem was following. We are not satisfied with DataContractSerializer performance and would like to use own custom serializar.
For the first glance, this seems strangem because DataContractSerializer is main feature of WCF services. It's very fast and easy to use. It's true.
But when you need to send quite big collection of large objects from client to server, you meet the problem of large size of messages.
Reason of it, that result of data contract serializer is xml. And node names are equal to names of properties.
Besides that, null values present in the xml.
It's not very efficient.
Standard BinaryMessage encoding has practically the same message size. You can see object property names in the output.
Solution - using of custom serializer.
The only issue that there is not much information about this.
Solution is following.
According to the msdn article Extending WCF with Custom Behaviors, the only place for custom serializer is dispatcher. MSDN offers to implement own IDispatchMessageFormatter. It will deserialize requests from clients and serialize responces.
The same on the client - IClientMessageFormatter.
Here is what I have implemented for my case:
Custom message formatter
- using System;
- using System.IO;
- using System.Xml;
- using System.ServiceModel.Dispatcher;
- using System.ServiceModel.Channels;
- using System.IO.Compression;
- namespace SharedLibrary
- {
- public class CustomMessageFormatter : IDispatchMessageFormatter, IClientMessageFormatter
- {
- IClientMessageFormatter InnerClientFormatter;
- IDispatchMessageFormatter InnerDispatchFormatter;
- private const string ROOT = "MessageData";
- public CustomMessageFormatter(IClientMessageFormatter innerClientFormatter)
- {
- InnerClientFormatter = innerClientFormatter;
- }
- public CustomMessageFormatter(IDispatchMessageFormatter innerDispatchFormatter)
- {
- InnerDispatchFormatter = innerDispatchFormatter;
- }
- #region Stream Compression
- private Stream CompressStream(Stream inStream)
- {
- var outStream = new MemoryStream();
- CompressStream(inStream, outStream);
- outStream.Position = 0;
- return outStream;
- }
- /// <summary>
- /// Compress 'inStream' and write all data into 'outStream'
- /// </summary>
- private void CompressStream(Stream inStream, Stream outStream)
- {
- using (var gzipStream = new GZipStream(outStream, CompressionMode.Compress, true))
- {
- gzipStream.Flush();
- inStream.CopyTo(gzipStream);
- }
- }
- /// <summary>
- /// Get decompression stream. Not dispose 'inStream' while not end work with stream.
- /// </summary>
- private Stream DecompressStream(Stream inStream, bool leaveOpenInStream = false)
- {
- return new GZipStream(inStream, CompressionMode.Decompress, leaveOpenInStream);
- }
- #endregion
- #region IDispatchMessageFormatter
- public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
- {
- Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
- using (var inStream = GetBodyInnerContentStream(message))
- {
- for (int i = 0; i < parameters.Length; i++)
- {
- parameters[i] = serializer.Deserialize(inStream);
- }
- }
- }
- public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
- {
- Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
- //stream will be closed in the ContentStreamProvider
- MemoryStream outStream = new MemoryStream();
- serializer.Serialize(result, outStream);
- outStream.Position = 0;
- return Message.CreateMessage(messageVersion, null, new ContentStreamProvider(CompressStream(outStream), ROOT));
- }
- #endregion
- /// <summary>
- /// Create a new message bases on other one and with body writer stream.
- /// </summary>
- /// <param name="prototype"></param>
- /// <param name="sourceStream">Stream of body content</param>
- /// <param name="tagName"></param>
- private static Message CreateMessage(Message prototype, Stream contentStream, string tagName = null)
- {
- Message msg = Message.CreateMessage(prototype.Version, null, new ContentStreamProvider(contentStream, tagName));
- msg.Headers.CopyHeadersFrom(prototype);
- msg.Properties.CopyProperties(prototype.Properties);
- return msg;
- }
- #region IClientMessageFormatter
- public object DeserializeReply(Message message, object[] parameters)
- {
- Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
- using (var inStream = GetBodyInnerContentStream(message))
- {
- return serializer.Deserialize(DecompressStream(inStream, true));
- }
- }
- public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
- {
- Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
- //stream will be closed in the ContentStreamProvider
- MemoryStream outStream = new MemoryStream();
- foreach (var parameter in parameters)
- {
- serializer.Serialize(parameter, outStream);
- }
- //check
- outStream.Position = 0;
- Message prototype = null;
- if (InnerClientFormatter != null)
- {
- prototype = InnerClientFormatter.SerializeRequest(messageVersion, parameters);
- messageVersion = prototype.Version;
- }
- return CreateMessage(prototype, outStream, ROOT);
- }
- #endregion
- /// <summary>
- /// Gets stream of inner element content of the message body.
- /// </summary>
- private Stream GetBodyInnerContentStream(Message message)
- {
- using (var bodyStream = new MemoryStream())
- {
- using (XmlDictionaryWriter bodyWriter = XmlDictionaryWriter.CreateBinaryWriter(bodyStream))
- {
- message.WriteBodyContents(bodyWriter);
- bodyWriter.Flush();
- bodyStream.Position = 0;
- using (var bodyReader = XmlDictionaryReader.CreateBinaryReader(bodyStream, XmlDictionaryReaderQuotas.Max))
- {
- bodyReader.MoveToStartElement();
- var contentStream = new MemoryStream(bodyReader.ReadElementContentAsBase64());
- contentStream.Position = 0;
- return contentStream;
- }
- }
- }
- }
- }
- }
Content stream provider code
- using System;
- using System.IO;
- using System.Xml;
- using System.ServiceModel.Channels;
- namespace SharedLibrary
- {
- internal sealed class ContentStreamProvider : BodyWriter, IStreamProvider
- {
- private readonly Stream _innerStream;
- private readonly string _tagName;
- public ContentStreamProvider(Stream stream, string tagName = null)
- : base(false)
- {
- _innerStream = stream;
- _tagName = tagName;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- if (!string.IsNullOrEmpty(_tagName))
- {
- writer.WriteStartElement(_tagName);
- }
- writer.WriteValue((IStreamProvider)this);
- if (!string.IsNullOrEmpty(_tagName))
- {
- writer.WriteEndElement();
- }
- }
- Stream IStreamProvider.GetStream()
- {
- return _innerStream;
- }
- void IStreamProvider.ReleaseStream(Stream stream)
- {
- if (stream != null)
- {
- stream.Dispose();
- }
- }
- }
- }
I used SharpSerializer.
As you can see, I still use DataContractSerializer here. The aim is to create prototype WCF Message. With all headers. I just don't know how to implement it myself.
Next step is to apply this for your WCF service. As wcfpro.wordpress advices, I have implemented IOperationBehavior interface. But with some small changes.
CustomOperationBehavior
- using System;
- using System.ServiceModel.Description;
- using System.ServiceModel.Dispatcher;
- namespace SharedLibrary
- {
- public class CustomOperationBehavior : Attribute, IOperationBehavior
- {
- public CustomOperationBehavior()
- {
- }
- IOperationBehavior innerFormatterBehavior;
- internal IOperationBehavior InnerFormatterBehavior
- {
- get { return innerFormatterBehavior; }
- set { innerFormatterBehavior = value; }
- }
- #region IOperationBehavior Members
- public void AddBindingParameters(OperationDescription description, System.ServiceModel.Channels.BindingParameterCollection parameters)
- {
- if (innerFormatterBehavior != null)
- {
- innerFormatterBehavior.AddBindingParameters(description, parameters);
- }
- }
- public void ApplyClientBehavior(OperationDescription description, ClientOperation runtime)
- {
- if (innerFormatterBehavior != null && runtime.Formatter == null)
- {
- // no formatter has been applied yet
- innerFormatterBehavior.ApplyClientBehavior(description, runtime);
- }
- runtime.Formatter = new CustomMessageFormatter(runtime.Formatter);
- }
- public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation runtime)
- {
- if (innerFormatterBehavior != null && runtime.Formatter == null)
- {
- // no formatter has been applied yet
- innerFormatterBehavior.ApplyDispatchBehavior(description, runtime);
- }
- runtime.Formatter = new CustomMessageFormatter(runtime.Formatter);
- }
- public void Validate(OperationDescription description)
- {
- if (innerFormatterBehavior != null)
- {
- innerFormatterBehavior.Validate(description);
- }
- }
- #endregion
- }
- }
This will allow you to turn you serializer on with following way:
Code Snippet
- using System;
- using System.ServiceModel;
- using System.Runtime.Serialization;
- using SharedLibrary;
- namespace Test.ProxyServer
- {
- [ServiceContract]
- public interface IService
- {
- [OperationContract]
- string GetData(int value);
- [OperationContract]
- [CustomOperationBehavior]
- CompositeType[] GetDataUsingDataContract(CompositeType composite);
- }
- }
Have you noticed CustomOperationBehavior attribute?
It's practically everything. No one change in the DataContracts.
The last thing is client tuning.
Client settings:
- using (ServiceReference.ServiceClient client = new ServiceReference.ServiceClient())
- {
- // Apply query string formatter
- foreach (OperationDescription operationDescription in client.Endpoint.Contract.Operations)
- {
- operationDescription.Behaviors.Add(new CustomOperationBehavior() as IOperationBehavior);
- }
- var deals = client.GetDataUsingDataContract(new CompositeType() { BoolValue = true, DecimalValue = 5, StringValue1 = "1", StringValue2 = "2", StringValue3 = "3" });
- }
This is really all. Custom serializer will work. And you'll see in the Fiddler different message, not xml.
Of course you can use any serializer you like. The tricky part is implementing of formetters.
You should be sure, that serializer works correctly.
You should control all process of creation WCF message, control headers, Action property and so on. This is the infinite source of erros.
And with custom serializer you may not receive FaultExceptions. So you should control messages using some web debugger. For example, Fiddler.
Restrictions of the current solution - client and server should have shared library with data contracts. But this restriction refers to the current serializer.