Thursday, 9 June 2011

WCF Pattern: Passing Additional Data to a Streaming Operation in a Stateless Service

Problem

You are implementing a service that implements streamed requests. The service is stateless. You want to pass additional information to streaming operations together with the data stream.

Operations that implement streamed requests can only take a single parameter containing the data to be streamed. If a service requires additional information, such as a name or identifier to associate with the streamed data, then this information cannot be passed as a parameter to the same operation. One common technique to circumvent this issue is to implement an additional operation that the client can use to specify the name or identifier, but this strategy requires that the service is able to retain state between operations. Not all bindings support sessions. For example, if you are using the BasicHttpBinding then sessions are not available. An alternative technique is to retain state in static variables in the service, but this solution is not suitable for scalable services that need to handle requests from multiple concurrent clients.

Forces

  • You want to provide an operation that implements streamed requests.
  • You need to provide additional information about the stream as well as the streamed data.
  • The binding used by the service does not support sessions.
  • You do not want to use static data in the service to retain state information.

Example Scenario

A WCF service uses transport security over the BasicHttBinding binding. For performance reasons, one operation named UploadData exposed by the service expects a streamed request. The operation also requires the name of the video so that the service can save the stream in a file with an appropriate name. The BasicHttpBinding binding does not support sessions.

Solution

Implement a custom message contract for the streaming operation. Provide the name of the video as metadata in the message header. Specify the streamed data as the message body. When WCF streams data, the message header is transmitted at the start of the data, so the operation can access the message metadata and retrieve the message name before it receives the streamed data.

Implementation

The sample implementation shown in this section comprises three elements:
  • The message contract. The TaggedStream class specifies the name to associate with the streamed data by using the MessageHeader attribute. The streamed data forms the body of the message and is tagged with the MessageBodyMember attribute.
  • The streaming service. This service implements the UploadData streaming operation. The operation retrieves the data from the stream and saves it to a file based on the metadata retrieved from the message header.
  • The client application. This application creates an instance of the TaggedStream class and populates it with a stream and the name to associate with the stream. The client application uses this object as the parameter to the UploadData operation.

Message Contract

The TaggedStream class implements the message contract. The Name field is transmitted as part of the message header, and the StreamData field constitutes the body of the message.
[MessageContract]
public class TaggedStream
{
    [MessageHeader(Name = "Name",
                   Namespace = "http://contentmaster.com",
                   MustUnderstand = true)]
    public string Name { get; set; }

    [MessageBodyMember(Name = "StreamData",
                       Namespace = "http://contentmaster.com")]
    public Stream StreamData { get; set; }
}

Streaming Service

The streaming service in this example implements the IStreamService interface shown below. The UploadData operation enables a client application to send data to the service as a streamed request. The message type specified as the parameter to the UploadData operation is TaggedStream.
[ServiceContract(Namespace = "http://contentmaster.com")]
public interface IStreamService
{
    [OperationContract(IsOneWay = true)]
    void UploadData(TaggedStream data);
}

The following code shows an example implementation of the streaming service. In this example, the streamed data is assumed to be a bitmap which is saved to a “.bmp” file. Replace this code with your own business logic, and handle exceptions as necessary.
[ServiceBehavior]
public class StreamService : IStreamService
{
    public void UploadData(TaggedStream data)
    {
        try
        {
            // Stream the data into the service
            System.Drawing.Bitmap bm = new System.Drawing.Bitmap(data.StreamData);
            bm.Save(String.Format(@"E:\{0}.bmp", data.Name));
        }
        catch
        {
            // Handle exceptions
            ...
        }
    }
}

Client Application

The code below shows a sample client application. The code creates a proxy object for communicating with the streaming service. In this example, the proxy class was generated by using the Add Service Reference functionality of Visual Studio. The client application then opens a filestream over a “.bmp” file, and populates a TaggedStream object with the name of this file and the stream. The client application calls the UploadData operation with the TaggedStream object.
class Program
{
    static void Main(string[] args)
    {
        try
        {
            StreamServiceClient proxy = StreamServiceClient("BasicHttpBinding_IStreamService");

            FileStream fs = new FileStream(@"E:\Test.bmp", FileMode.Open, FileAccess.Read);

            TaggedStream ts = new TaggedStream { Name = "Test", StreamData = fs };
            streamServiceProxy.UploadData(ts);
            ...
        }
        catch
        {
            // Handle exceptions
            ...
        }
    }
}

No comments: