среда, 5 октября 2011 г.

Custom serialization in WCF

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:


Custom message formatter
  1. using System;
  2. using System.IO;
  3. using System.Xml;
  4. using System.ServiceModel.Dispatcher;
  5. using System.ServiceModel.Channels;
  6. using System.IO.Compression;
  7.  
  8. namespace SharedLibrary
  9. {
  10.     public class CustomMessageFormatter : IDispatchMessageFormatter, IClientMessageFormatter
  11.     {
  12.         IClientMessageFormatter InnerClientFormatter;
  13.         IDispatchMessageFormatter InnerDispatchFormatter;
  14.  
  15.         private const string ROOT = "MessageData";
  16.  
  17.         public CustomMessageFormatter(IClientMessageFormatter innerClientFormatter)
  18.         {
  19.             InnerClientFormatter = innerClientFormatter;
  20.         }
  21.  
  22.         public CustomMessageFormatter(IDispatchMessageFormatter innerDispatchFormatter)
  23.         {
  24.             InnerDispatchFormatter = innerDispatchFormatter;
  25.         }
  26.  
  27.         #region Stream Compression
  28.  
  29.         private Stream CompressStream(Stream inStream)
  30.         {
  31.             var outStream = new MemoryStream();
  32.             CompressStream(inStream, outStream);
  33.             outStream.Position = 0;
  34.             return outStream;
  35.         }
  36.         /// <summary>
  37.         /// Compress 'inStream' and write all data into 'outStream'
  38.         /// </summary>
  39.         private void CompressStream(Stream inStream, Stream outStream)
  40.         {
  41.             using (var gzipStream = new GZipStream(outStream, CompressionMode.Compress, true))
  42.             {
  43.                 gzipStream.Flush();
  44.                 inStream.CopyTo(gzipStream);
  45.             }
  46.         }
  47.         /// <summary>
  48.         /// Get decompression stream. Not dispose 'inStream' while not end work with stream.
  49.         /// </summary>    
  50.         private Stream DecompressStream(Stream inStream, bool leaveOpenInStream = false)
  51.         {
  52.             return new GZipStream(inStream, CompressionMode.Decompress, leaveOpenInStream);
  53.         }
  54.  
  55.         #endregion
  56.  
  57.         #region IDispatchMessageFormatter
  58.  
  59.         public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
  60.         {
  61.             Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
  62.             using (var inStream = GetBodyInnerContentStream(message))
  63.             {
  64.                 for (int i = 0; i < parameters.Length; i++)
  65.                 {
  66.                     parameters[i] = serializer.Deserialize(inStream);
  67.                 }
  68.             }
  69.         }
  70.  
  71.         public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
  72.         {
  73.             Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
  74.  
  75.             //stream will be closed in the ContentStreamProvider
  76.             MemoryStream outStream = new MemoryStream();
  77.             serializer.Serialize(result, outStream);
  78.  
  79.             outStream.Position = 0;
  80.  
  81.             return Message.CreateMessage(messageVersion, null, new ContentStreamProvider(CompressStream(outStream), ROOT));
  82.         }
  83.  
  84.         #endregion
  85.  
  86.         /// <summary>
  87.         /// Create a new message bases on other one and with body writer stream.
  88.         /// </summary>
  89.         /// <param name="prototype"></param>
  90.         /// <param name="sourceStream">Stream of body content</param>
  91.         /// <param name="tagName"></param>
  92.         private static Message CreateMessage(Message prototype, Stream contentStream, string tagName = null)
  93.         {
  94.             Message msg = Message.CreateMessage(prototype.Version, null, new ContentStreamProvider(contentStream, tagName));
  95.             msg.Headers.CopyHeadersFrom(prototype);
  96.             msg.Properties.CopyProperties(prototype.Properties);
  97.             return msg;
  98.         }
  99.  
  100.         #region IClientMessageFormatter
  101.  
  102.         public object DeserializeReply(Message message, object[] parameters)
  103.         {
  104.             Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
  105.  
  106.             using (var inStream = GetBodyInnerContentStream(message))
  107.             {
  108.                 return serializer.Deserialize(DecompressStream(inStream, true));
  109.             }
  110.         }
  111.  
  112.         public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
  113.         {
  114.             Polenter.Serialization.SharpSerializer serializer = new Polenter.Serialization.SharpSerializer(true);
  115.  
  116.             //stream will be closed in the ContentStreamProvider
  117.             MemoryStream outStream = new MemoryStream();
  118.  
  119.             foreach (var parameter in parameters)
  120.             {
  121.                 serializer.Serialize(parameter, outStream);
  122.             }
  123.  
  124.             //check
  125.             outStream.Position = 0;
  126.  
  127.             Message prototype = null;
  128.             if (InnerClientFormatter != null)
  129.             {
  130.                 prototype = InnerClientFormatter.SerializeRequest(messageVersion, parameters);
  131.                 messageVersion = prototype.Version;
  132.             }
  133.  
  134.             return CreateMessage(prototype, outStream, ROOT);
  135.         }
  136.  
  137.         #endregion
  138.  
  139.         /// <summary>
  140.         /// Gets stream of inner element content of the message body.
  141.         /// </summary>
  142.         private Stream GetBodyInnerContentStream(Message message)
  143.         {
  144.             using (var bodyStream = new MemoryStream())
  145.             {
  146.                 using (XmlDictionaryWriter bodyWriter = XmlDictionaryWriter.CreateBinaryWriter(bodyStream))
  147.                 {
  148.                     message.WriteBodyContents(bodyWriter);
  149.                     bodyWriter.Flush();
  150.                     bodyStream.Position = 0;
  151.  
  152.                     using (var bodyReader = XmlDictionaryReader.CreateBinaryReader(bodyStream, XmlDictionaryReaderQuotas.Max))
  153.                     {
  154.                         bodyReader.MoveToStartElement();
  155.                         var contentStream = new MemoryStream(bodyReader.ReadElementContentAsBase64());
  156.                         contentStream.Position = 0;
  157.                         return contentStream;
  158.                     }
  159.                 }
  160.             }
  161.         }
  162.     }
  163. }


Content stream provider code
  1. using System;
  2. using System.IO;
  3. using System.Xml;
  4. using System.ServiceModel.Channels;
  5.  
  6. namespace SharedLibrary
  7. {
  8.     internal sealed class ContentStreamProvider : BodyWriter, IStreamProvider
  9.     {
  10.         private readonly Stream _innerStream;
  11.         private readonly string _tagName;
  12.  
  13.         public ContentStreamProvider(Stream stream, string tagName = null)
  14.             : base(false)
  15.         {
  16.             _innerStream = stream;
  17.             _tagName = tagName;
  18.         }
  19.  
  20.         protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
  21.         {
  22.             if (!string.IsNullOrEmpty(_tagName))
  23.             {
  24.                 writer.WriteStartElement(_tagName);
  25.             }
  26.             writer.WriteValue((IStreamProvider)this);
  27.             if (!string.IsNullOrEmpty(_tagName))
  28.             {
  29.                 writer.WriteEndElement();
  30.             }
  31.         }
  32.  
  33.         Stream IStreamProvider.GetStream()
  34.         {
  35.             return _innerStream;
  36.         }
  37.  
  38.         void IStreamProvider.ReleaseStream(Stream stream)
  39.         {
  40.             if (stream != null)
  41.             {
  42.                 stream.Dispose();
  43.             }
  44.         }
  45.     }
  46. }


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
  1. using System;
  2. using System.ServiceModel.Description;
  3. using System.ServiceModel.Dispatcher;
  4.  
  5. namespace SharedLibrary
  6. {
  7.     public class CustomOperationBehavior : Attribute, IOperationBehavior
  8.     {
  9.         public CustomOperationBehavior()
  10.         {
  11.         }
  12.  
  13.         IOperationBehavior innerFormatterBehavior;
  14.         internal IOperationBehavior InnerFormatterBehavior
  15.         {
  16.             get { return innerFormatterBehavior; }
  17.             set { innerFormatterBehavior = value; }
  18.         }
  19.  
  20.         #region IOperationBehavior Members
  21.  
  22.         public void AddBindingParameters(OperationDescription description, System.ServiceModel.Channels.BindingParameterCollection parameters)
  23.         {
  24.             if (innerFormatterBehavior != null)
  25.             {
  26.                 innerFormatterBehavior.AddBindingParameters(description, parameters);
  27.             }
  28.         }
  29.  
  30.         public void ApplyClientBehavior(OperationDescription description, ClientOperation runtime)
  31.         {
  32.             if (innerFormatterBehavior != null && runtime.Formatter == null)
  33.             {
  34.                 // no formatter has been applied yet
  35.                 innerFormatterBehavior.ApplyClientBehavior(description, runtime);
  36.             }
  37.             runtime.Formatter = new CustomMessageFormatter(runtime.Formatter);
  38.         }
  39.  
  40.         public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation runtime)
  41.         {
  42.             if (innerFormatterBehavior != null && runtime.Formatter == null)
  43.             {
  44.                 // no formatter has been applied yet
  45.                 innerFormatterBehavior.ApplyDispatchBehavior(description, runtime);
  46.             }
  47.             runtime.Formatter = new CustomMessageFormatter(runtime.Formatter);
  48.         }
  49.  
  50.         public void Validate(OperationDescription description)
  51.         {
  52.             if (innerFormatterBehavior != null)
  53.             {
  54.                 innerFormatterBehavior.Validate(description);
  55.             }
  56.         }
  57.  
  58.         #endregion
  59.     }
  60. }



This will allow you to turn you serializer on with following way:


Code Snippet
  1. using System;
  2. using System.ServiceModel;
  3. using System.Runtime.Serialization;
  4. using SharedLibrary;
  5.  
  6. namespace Test.ProxyServer
  7. {
  8.     [ServiceContract]
  9.     public interface IService
  10.     {
  11.         [OperationContract]
  12.         string GetData(int value);
  13.  
  14.         [OperationContract]
  15.         [CustomOperationBehavior]
  16.         CompositeType[] GetDataUsingDataContract(CompositeType composite);
  17.     }
  18. }

Have you noticed CustomOperationBehavior attribute?
It's practically everything. No one change in the DataContracts.
The last thing is client tuning.


Client settings:
  1. using (ServiceReference.ServiceClient client = new ServiceReference.ServiceClient())
  2.             {
  3.                 // Apply query string formatter
  4.                 foreach (OperationDescription operationDescription in client.Endpoint.Contract.Operations)
  5.                 {
  6.                     operationDescription.Behaviors.Add(new CustomOperationBehavior() as IOperationBehavior);
  7.                 }
  8.  
  9.                 var deals = client.GetDataUsingDataContract(new CompositeType() { BoolValue = true, DecimalValue = 5, StringValue1 = "1", StringValue2 = "2", StringValue3 = "3" });
  10.             }

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.

понедельник, 26 сентября 2011 г.

Fiddler for localhost


Here is the very simple tip how to use fiddler for local queries.
Intead of      http://localhost:1234/MyApp/...
use               http://localhost.:1234/MyApp/...          
(notice the extra dot after localhost).

After that fiddler will catch such requests:
 



Hope it will help.

вторник, 26 апреля 2011 г.

Windows Task Scheduler Manager

Here are some link related to the managing of windows task scheduler from C# code.
Also some link contains information about the nearest problems.

The task was: Create WCF Service for managing Task Scheduler from clients.
WCF service is hosted on the IIS under embedded Network Service Account.
Problem was to give this account needed permissions durion service installation.



 Такая учетная запись называется Network Service и процессы, выполняемые под этой учетной записью, используют ее профиль, который расположен в разделе HKU\S-1-5-20 и его файлы расположены в папке %SystemRoot%\ServiceProfiles\NetworkService.

Second issue: this service should be able to manage Task Scheduler on the local servers.
Again, need to research through permissions and rights.

Security Group in the AD - for the machine specifications


Open Source library - the main part of this task. Thanks to the author of this library, David Hall.

Task Scheduler management
http://taskscheduler.codeplex.com/
http://taskscheduler.codeplex.com/license

And some other links:

schtasks.exe help - http://support.microsoft.com/kb/814596

http://forums.techarena.in/windows-server-help/684821.htm

http://www.tramontana.co.hu/wix/lesson6.php

четверг, 24 февраля 2011 г.

Large Object Heap and memory arrangement in CLR (.NET)

Статья MSDN посвящена тому, как CLR работает с памятью, какие объекты помещаются в стек, какие в GCHeap (обычная куча, подвергающаяся сборке мусора), а какие лезут в LargeObjectHeap (>= 85 000 byte)
http://msdn.microsoft.com/ru-ru/library/dd335945.aspx

понедельник, 21 февраля 2011 г.

RSDN - Сериализация в .NET. DataSet. BinaryFormatter

Статья посвящена сравнению способов сериализации в .NET. В том числе при сериализации датасета. Вывод - пишите сериализацию руками :)
http://www.rsdn.ru/article/dotnet/dotnetserial.xml

вторник, 8 февраля 2011 г.

WCF. Обработка сообщений. Message injection.

В приведенной статье описан удобный и красивый способ реализации обработчика сообщений для WCF сервиса. Причем как для клиента, так и для сервиса.

Идея статьи: сделать сжатие данных для WCF сервиса. Причем оно должно быть прозрачным и удобным для использования.

Но кроме этого, данный способ может оказаться полезным в любом случае, когда вам нужно что-то сделать с сообщением перед его получением сервисом, или перед началом обработки его клиентом. Например, добавить header, или наоборот считать что-то из заголовка и проверить.

Итак, линка: http://www.gotdotnet.ru/blogs/AlexMAS/462/

Данное решение подключается как атрибут класса. Это может быть не всегда удобно. Например, необходимо одну и ту же реализацию поставлять как с обработкой сообщений, так и без.
Это можно сделать, перенеся настройки в конфигурационный файл - http://www.eggheadcafe.com/sample-code/WCFWF/776b4a8d-8ea5-4334-a440-7d192b3ad83d/using-behaviorextensionelement-and-iservicebehavior-to-add-an-ierrorhandler-to-a-wcf-service.aspx

Downloading large data in chunks

The linked page describes how to download large data in chunks using WCF with streaming.
http://www.codeproject.com/KB/silverlight/DownloadingInChunks.aspx 

пятница, 21 января 2011 г.

When to call GC.Collect()

Всем изучающим .NET известно одно правило - не надо мешать сборщику мусора работать. Garbage Collector сам знает, что делать. И лучше его не трогать.

Следующая ссылка так и говорит:


Rule #1
Don't.


Но есть еще и Rule #2, о чем и написана приведенная статья.

http://blogs.msdn.com/b/ricom/archive/2004/11/29/271829.aspx

среда, 19 января 2011 г.

WCF Custom Message Headers

Ссылки на то, каким образом можно добавить свои заголовки к сообщениям для WCF сервиса.

http://blogs.microsoft.co.il/blogs/bursteg/archive/2006/04/23/141.aspx
http://msdn.microsoft.com/ru-ru/library/aa395196.aspx

Свои заголовки могут пригодиться, если необходимо похитрее передать какую-нибудь информацию с клиента на сервис, или обратно. Например, информацию для авторизации.

понедельник, 17 января 2011 г.

SQL indexes

Блог был задуман как сборник ссылок по .NET вообще и C# в частности. Но в жизни приходится сталкиваться со многими вещами. Например с индексами в SQL Server.

Link: http://www.sql.ru/articles/mssql/03013101indexes.shtml

Статься на sql.ru содержит фундаментальную информацию про индексы в SQL Server и организацию работы СУБД вообще. Очень полезна для понимания принципиальных аспектов работы с индексами в СУБД.

Localization in the .NET

Link: http://www.codeproject.com/KB/dotnet/Localization.aspx

Статья описывает, как можно использовать средства локализации в .NET на основе ресурсных файлов.

Memory problems. Garbage collector

Link: http://msdn.microsoft.com/ru-ru/magazine/cc163528.aspx
The same in english: http://msdn.microsoft.com/en-us/magazine/cc163528.aspx

В статье:

Описание средств исследования проблем с памятью в .NET;
OutOfMemoryException и что может служить его причиной:
Фрагментация памяти.

К сожалению, статья содержит в основном средства исследования проблемы, но не ее решения.