WCF Streaming

1. Configuring and Tracing

WCF can send messages in buffered mode and in streaming mode. The default is buffered mode, which means that the whole message needs to be transfered and received before the client can start processing it. When messages get too large to be send as one big blob and when those messages can’t be split in different message parts, streaming is your option. To use streaming (both on input and/or output parameters), define the parameters as type of stream (you should use the base class here, not a derived one like eg. FileStream), Message or IXmlSerializable. This should be the one and only parameter, no extra parameters are allowed !

Not all the bindings support streaming : you can use basicHttpBinding, NetTcpBinding, NetNamedPipeBinding and webHttpBinding. You can’t use wsHttpBinding : you can’t sign and/or encrypt messages without applying the algorithm on the complete message first, so streaming is no option here.

As example, let’s transfer an image both buffered and streamed. I’m going to use a REST service in order to use Internet Explorer as client application (which can show chunked data, i.e. showing the part of the image which has already been transferred).

[ServiceContract]
public interface IPicture
{
    [OperationContract]
    [WebGet(UriTemplate="/getpicture")]
    Stream GetPicture();
}

 

public class Picture : IPicture
{
    public System.IO.Stream GetPicture()
    {
        OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;
        context.ContentType = "image/jpeg";

        return new TraceFileStream(@"E:\Downloads\image.jpg", FileMode.Open);
    }
}

 

To show a trace of the Read operations on the stream, I derived a class TraceFileStream from FileStream and added some tracing to it.

public class TraceFileStream : FileStream
{
    public TraceFileStream(string path, FileMode mode)
        : base(path, mode)
    {
    }
    public override void Write(byte[] array, int offset, int count)
    {
        Debug.WriteLine(string.Format("Writing {0} bytes to Filestream", count));
        base.Write(array, offset, count);
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
        Debug.WriteLine(string.Format("Reading {0} bytes from Filestream", count));
        return base.Read(buffer, offset, count);
    }
}

 

Let’s configure buffered mode first:

<system.serviceModel>
    <bindings>
      <webHttpBinding>
        <binding name="CustomWebBinding" transferMode="Buffered" />
      </webHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="CustomBehavior" name="Picture">
        <endpoint address="" behaviorConfiguration="WebEndpointBehavior"
          binding="webHttpBinding" bindingConfiguration="CustomWebBinding"
          contract="IPicture" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WebEndpointBehavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
        <behavior name="CustomBehavior">
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
 

By tracing the message, you will see that a buffer of 104270 bytes is send to the client.

Untitled

To send the same data in streamed mode, apply the following configuration change:

<webHttpBinding>
   <binding name="CustomWebBinding" transferMode="Streamed" />
</webHttpBinding>

 

Tracing now learns us that the data arrives chunked (see: Transfer-Encoding: Chunked)

Untitled

2. A  custom Stream class

When creating a class to read and/or write data from a stream, you shouldn’t care on how the data will be retrieved from the client: derive your implementation from Stream (or an existing Stream class).

As example, let’s create a class UpsideDownJpegStream which is a stream to an open a jpeg file, but the resulting image will be upside down in it.

public class UpsideDownJpegStream : Stream
{
    private MemoryStream outStream = null;

    public UpsideDownJpegStream(string filePath)
    {
        TraceFileStream inStream = new TraceFileStream(filePath, FileMode.Open);
        JpegBitmapDecoder decoder = new JpegBitmapDecoder(inStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
        BitmapSource inSource = decoder.Frames[0];

        outStream = new TraceMemoryStream();
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.FlipHorizontal = true;
        encoder.FlipVertical = false;
        encoder.QualityLevel = 30;
        encoder.Rotation = Rotation.Rotate180;
        encoder.Frames.Add(BitmapFrame.Create(inSource));
        encoder.Save(outStream);
        outStream.Position = 0;
    }
    public override bool CanRead
    {
        get { return true; }
    }
    public override long Length
    {
        get { return outStream.Length; }
    }
    private long position = 0;
    public override long Position
    {
        get
        {
            return position;
        }
        set
        {
            position = value;
        }
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
        Debug.WriteLine(string.Format("Reading {0} bytes from UpsideDownJpegStream", count));

        int countRead = outStream.Read(buffer,offset, count);
        Position = outStream.Position;
        
        return countRead;
    }
    public override void Close()
    {
        outStream.Close();
        base.Close();
    }
    protected override void Dispose(bool disposing)
    {
        outStream.Dispose();

        base.Dispose(disposing);
    }
   ...
}

As you notice, the Read() method provides access to our stream by passing a buffer and requesting for an amount of data at a certain offset. I use in here also trace classes (TraceFileStream,TraceMemoryStream) to trace what’s happening. Let’s add an extra call to the WCF service for it.

[ServiceContract]
public interface IPicture
{
    [OperationContract]
    [WebGet(UriTemplate = "/getreversepicture")]
    Stream GetReversePicture();
}

public class Picture : IPicture
{
    public Stream GetReversePicture()
    {
        OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;
        context.ContentType = "image/jpeg";

        return new UpsideDownJpegStream(@"E:\Downloads\image.jpg");
    }
}

When running this, the picture will occur upside down (of course), and the trace output will be

Writing 20 bytes to MemoryStream                                    <== JpegBitmapEncoder writing to MemoryStream
Writing 4096 bytes to MemoryStream
Writing 4096 bytes to MemoryStream
Writing 4096 bytes to MemoryStream
Writing 4096 bytes to MemoryStream
Writing 4096 bytes to MemoryStream
Writing 1815 bytes to MemoryStream
Reading 256 bytes from UpsideDownJpegStream                         <== REST service reading UpsideDownJpegStream
Reading 256 bytes from MemoryStream                                 <== UpsideDownJpegStream reading MemoryStream
Reading 4096 bytes from UpsideDownJpegStream
Reading 4096 bytes from MemoryStream
Reading 65536 bytes from UpsideDownJpegStream
Reading 65536 bytes from MemoryStream
Reading 65536 bytes from UpsideDownJpegStream
Reading 65536 bytes from MemoryStream
 

3. Streaming video

Streaming movies works like explained above : open the movie as a FileStream, return the stream via WCF. To play it in a browser, apply the Content-Type: "video/x-ms-wmv". If you use the TraceFileStream class, you will see the traces of the call to the Read() method while playing the movie.

 
[OperationContract]
[WebGet(UriTemplate = "/getmovie")]
Stream GetMovie();

public Stream GetMovie()
{
    OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;
    context.ContentType = "video/x-ms-wmv";
    return new TraceFileStream(@"E:\Downloads\movie.wmv", FileMode.Open);
}