ServicesResourcesConferencesOur TeamWeblogsAboutContact
   

Buddhike's Weblog

{binary mind}


WCF - POX Streaming

Have you ever thought about returning a plain old XML document or some well formed HTML body snippet (for some crazy reason ;)) or your RSS feed from a WCF service? Well… I did :). In fact instead of returning the XML document itself, I wanted to stream it as the data source I was anticipating was not fast enough to provide me the complete document at once (i.e. it takes considerably more time to receive the portions of the document than the time taken for the actual transmission).

So my long (well… it's not really long) journey towards a solution started with the contract (oh! Nah! I'm not going to play that famous record once again ;)).

[OperationContract(Action = "*", ReplyAction="*")]

Message GetWeather();

My contract has only one operation. By default WCF uses SOAP action headers in the incoming/outgoing messages to properly dispatch them to the service/client. But in this case I have nothing SOAPish in my payload. Therefore, by setting Action="*" in my OperationContract I'm telling WCF that anything comes into the configured endpoint of this service must be dispatched to this method.

Moving on to my operation implementation, I have a single line of code that simply constructs a Message and return it to the runtime which takes care of transmitting it back to the client.

public Message GetWeather()

{

   Message msg = Message.CreateMessage(MessageVersion.None, "*", new WeatherReport());

   return msg;

}

So much of my solution lies within this line nevertheless. Let me brief you, Message is the fundamental unit of data transfer in WCF (if you have some socket background, think of it as the byte arrays in the world of sockets). You can create a message by calling one of the CreateMessage overloads in Message class. In WCF, these overloads are provided to support both push and pull mode data transfers. So in my case, I'm going for a push mode transfer and I'm doing it using an XML BodyWriter. You can create a BodyWriter by inheriting the BodyWriter abstract class. Then override the OnWriteBodyContenets, which is invoked by WCF runtime when it wants to serialize the message body. The runtime provides us a pointer to the XmlDictionaryWriter which, we can use to push the body contents. Consequently in my case I implemented my body writer in the WeatherReport class and wrote the XML document I wanted to send to the client in its OnWriteBodyContents overload.

protected override void OnWriteBodyContents(XmlDictionaryWriter writer)

{

   writer.WriteStartElement("weatherReport");

   Console.WriteLine("Sending weather report for Colombo");

   writer.WriteStartElement("Colombo");

   writer.WriteAttributeString("temp", "26");

   writer.WriteAttributeString("wind", "SW");

   writer.WriteAttributeString("humidity", "79");

   writer.Flush();

   Thread.Sleep(3000); // Simulate an I/O delay in the data source

 

   Console.WriteLine("Sending weather report for Munich");

   writer.WriteStartElement("Munich");

   writer.WriteAttributeString("temp", "25");

   writer.WriteAttributeString("wind", "NE");

   writer.WriteAttributeString("humidity", "37");

   writer.Flush();

   Thread.Sleep(3000);

 

   Console.WriteLine("Sending weather report for Seattle");

   writer.WriteStartElement("Seattle");

   writer.WriteAttributeString("temp", "15");

   writer.WriteAttributeString("wind", "SW");

   writer.WriteAttributeString("humidity", "80");

   writer.WriteEndElement();

   writer.Flush();

}

You might have already noticed that in the above code, I call writer.Flush() several times. I do this when I've written enough data that the client can understand (weather report for one city in this case) so that it will be transmitted to the client immediately. However, in order make sure that the data is sent back to the client immediately, we have to make sure that we are on the streaming mode. This has to be specified in our binding. I'm setting up my A(address),B(binding) and C(contract) imperatively in the code as follows.

CustomBinding binding = new CustomBinding();

// Encoder

TextMessageEncodingBindingElement encoder = new TextMessageEncodingBindingElement();

encoder.MessageVersion = MessageVersion.None;

binding.Elements.Add(encoder);

// Transport

HttpTransportBindingElement transport = new HttpTransportBindingElement();

transport.TransferMode = TransferMode.StreamedResponse;

transport.MaxBufferSize = 256;

binding.Elements.Add(transport);

// We will take about 10 minutes for our transmission.

binding.SendTimeout = TimeSpan.FromMinutes(10);

 

ServiceHost host = new ServiceHost(typeof(MyService));

host.AddServiceEndpoint(typeof(IMyService), binding,

"http://localhost:8011/myservice");

host.Open();

In this case, my binding contains only the most critical elements, the encoder and the transport we need to host a service. While setting up my encoder I set its MessageVersion property to MessageVersion.None. By doing this I'm telling the encoder that I want to get rid of all the SOAPish stuff in the message finally serialized (Tip: this is your key if you want to do non SOAP transfers). And the in the transport I set the transfer mode to StreamedResponse to stream the responses from my service (when we enable streaming in the http transport, it streams the content as specified in the chunked transfer coding in the HTTP spec). Furthermore I set the MaxBufferSize to 256 bytes since we are only sending a very small chunk at a time. This way you can optimize the memory consumption for read/write buffers used for streaming (default is 64K). Finally I create the ServiceHost and call the Open method in that to start the service.

On the client side, I setup my binding in almost the same way I did it in the service. Then I create a channel to communicate with my service endpoint and invoke the GetWeather operation. When I receive an instance of the Message class from the client side runtime, I get an XmlDictionaryReader at the body contents that I can use to read the underlying XML stream.

Message playlist = myservice.GetWeather();

XmlDictionaryReader reader = playlist.GetReaderAtBodyContents();

while (reader.Read())

{

   switch (reader.NodeType)

   {

     case XmlNodeType.Element:

       Console.WriteLine("{0} Temp:{1} Wind:{2} humidity:{3}",

       reader.Name,

       reader.GetAttribute("temp"),

       reader.GetAttribute("wind"),

       reader.GetAttribute("humidity"));

       break;

     case XmlNodeType.Text:

       break;

     case XmlNodeType.EndElement:

       break;
   }

}

reader.Close();

Now, it is important to note that I've also set the MaxBytesPerRead quota to 64 bytes in ReaderQuotas property of my encoder.

encoder.ReaderQuotas.MaxBytesPerRead = 64;

This value indicates how many bytes the XmlDictionaryReader should read when reading the element start tag and its attributes. Therefore, this value should essentially be large enough to read that information. If you set an unnecessarily large value here, the XmlDictionaryReader.Read() method will not return until it receives enough bytes from the underlying transport (this could be problematic if you receive very small data chunks with a considerable amount of delay as demonstrated in my code). Consequently you would not be able to read the data being streamed in timely fashion (this might even make you think that your data not actually streamed ;)).

You can download my sample code here and take a good look at it. Questions, ideas and corrections are welcomed!

Cheers,

posted on Wednesday, May 23, 2007 2:08 PM

# Link Listing - May 29, 2007 @ Wednesday, May 30, 2007 4:24 AM

RESTful Web Services [Via: Jon Udell ] SCSFContrib is Alive! [Via: bsimser ] Expression Studio on MSDN...
Christopher Steen

# Streaming XML @ Friday, August 03, 2007 7:28 PM

Streaming XML sample, via thinktecture , the code is attached -abel-
SoftwareNotes

# Streaming XML @ Friday, August 03, 2007 7:28 PM

Streaming XML sample, via thinktecture , the code is attached -abel-
SoftwareNotes

# Streaming XML @ Friday, August 03, 2007 7:28 PM

Streaming XML sample, via thinktecture , the code is attached
SoftwareNotes


Powered by Community Server, by Telligent Systems