views:

458

answers:

2

Is it correct that in WCF, I cannot have a service write to a stream that is received by the client?

Streaming is supported in WCF for requests, responses, or both. I would like to support a scenario where the data generator (either the client in case of streamed request, or the server in case of streamed response) can Write on the stream. Is this supported?

The analogy is the Response.OutputStream from an ASP.NET request. In ASPNET, any page can invoke Write on the output stream, and the content is received by the client. Can I do something similar in a WCF service - invoke Write on a stream that is received by the client?

Let me explain with a WCF illustration. The simplest example of Streaming in WCF is the service returning a FileStream to a client. This is a streamed response. The server code to implement this, is like this:

[ServiceContract]
public interface IStreamService
{
    [OperationContract]
    Stream GetData(string fileName);
}
public class StreamService : IStreamService
{
    public Stream GetData(string filename)
    {
        return new FileStream(filename, FileMode.Open)
    }
}

And the client code is like this:

StreamDemo.StreamServiceClient client = 
    new WcfStreamDemoClient.StreamDemo.StreamServiceClient();
Stream str = client.GetData(@"c:\path\on\server\myfile.dat");
do {
  b = str.ReadByte(); //read next byte from stream
  ...
} while (b != -1);

(example taken from http://blog.joachim.at/?p=33)

Clear, right? The server returns the Stream to the client, and the client invokes Read on it.

Is it possible for the client to provide a Stream, and the server to invoke Write on it?
In other words, rather than a pull model - where the client pulls data from the server - it is a push model, where the client provides the "sink" stream and the server writes into it. The server-side code might look similar to this:

[ServiceContract]
public interface IStreamWriterService
{
    [OperationContract]
    void SendData(Stream clientProvidedWritableStream);
}
public class DataService : IStreamWriterService
{
    public void GetData(Stream s)
    {
        do {
          byte[] chunk = GetNextChunk();
          s.Write(chunk,0, chunk.Length);
        } while (chunk.Length > 0); 
    }
}

Is this possible in WCF, and if so, how? What are the config settings required for the binding, interface, etc? What is the terminology?

Maybe it will just work? (I haven't tried it)

Thanks.

A: 

WCF streaming works both ways - your clients can upload stuff in a stream, but your WCF service can also send down stuff in a stream. You can definitely write a service that streams back large files based on their file name or some ID or something - absolutely.

If you want to send data back from the service, you need to do so in the return value of your service method, which needs to be of type Stream - you cannot (as far as I know) "supply" a stream to write into - the WCF service will create a response stream and send it back.

Have you checked out MSDN Streaming Message Transfer or David Wood's blog post or Kjell-Sverre's blog post on WCF streaming? They all nicely show what config settings you need (basically setting the TransferMode in your binding config to Streamed, StreamedRequest or StreamedResponse).

marc_s
So, Marc, what you're saying is ... for a streamed response, a service must return a Stream to the client. In other words, there is no direct analog to the ASPNET Response.OutputStream, that a service could write directly into. Correct?
Cheeso
@Cheeso: correct - the service must return a Stream, but cannot just write onto a stream in the client - the server and the client don't have any standing connection in WCF - all you can do is exchange serialized messages (as a whole, or in a chunked fashion as with streaming)
marc_s
+3  A: 

I'm fairly certain that there's no combination of WCF bindings that will allow you to literally write to the client's stream. It does seem logical that there should be, given that somewhere beneath the surface there's definitely going to be a NetworkStream, but once you start adding encryption and all that fun stuff, WCF would have to know how to wrap this stream to turn it into the "real" message, which I don't think it does.

However, the I-need-to-return-a-stream-but-component-X-wants-to-write-to-one scenario is a common one, and I have a catch-all solution that I use for this scenario, which is to create a two-way stream and spawn a worker thread to write to it. The easiest and safest way to do this (by which I mean, the way that involves writing the least code and therefore least likely to be buggy) is to use anonymous pipes.

Here's an example of a method that returns an AnonymousPipeClientStream that can be used in WCF messages, MVC results, and so on - anywhere you want to invert the direction:

static Stream GetPipedStream(Action<Stream> writeAction)
{
    AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream();
    ThreadPool.QueueUserWorkItem(s =>
    {
        using (pipeServer)
        {
            writeAction(pipeServer);
            pipeServer.WaitForPipeDrain();
        }
    });
    return new AnonymousPipeClientStream(pipeServer.GetClientHandleAsString());
}

An example usage of this would be:

static Stream GetTestStream()
{
    string data = "The quick brown fox jumps over the lazy dog.";
    return GetPipedStream(s =>
    {
        StreamWriter writer = new StreamWriter(s);
        writer.AutoFlush = true;
        for (int i = 0; i < 50; i++)
        {
            Thread.Sleep(50);
            writer.WriteLine(data);
        }
    });
}

Just two caveats about this approach:

  1. It will fail if the writeAction does anything to dispose the stream (that's why the test method doesn't actually dispose the StreamWriter - because that would dispose the underlying stream);

  2. It might fail if the writeAction doesn't actually write any data, because WaitForPipeDrain will return immediately and create a race condition. (If this is a concern, you can code a little more defensively to avoid this, but it complicates things a lot for a rare edge case.)

Hopefully that helps in your specific case.

Aaronaught
P.S. I should have added in the answer - take stock of how much data really needs to be streamed out. This is useful for several MB or more, but if you've only got a few KB to send then it would cost less to just create a `MemoryStream` for your component to write to and then return that.
Aaronaught
Whoa, yes!. Now let me understand this. Please confirm; (1) The AnonymousPipe{Client,Server}Stream is a pair of classes that will be used server-side only. (2) What's returned to the client is a System.IO.Stream.
Cheeso
ps: I've asked this question before, see http://stackoverflow.com/questions/1499520 . I'm very pleased to get a general, simple answer.
Cheeso
@Cheeso: (1) Absolutely, anonymous pipes *may* be used cross-process but they are perfectly fine for in-process use. They're not as "slim" as a pure threaded approach, but for very large streams the overhead becomes trivial. And (2) absolutely, the `PipeStream` classes all descend from `System.IO.Stream`.
Aaronaught
Cheeso
@Cheeso: Sorry if that was a little vague. What I meant by "pure threaded" was a solution that uses only threads and synchronization primitives, which would be more lightweight than an anonymous pipe (which is a global resource). Probably something similar to the adapter you describe. If you had to process 500 transactions per second, then I might be wary of using a pipe, that's about all I meant.
Aaronaught
gotcha, thank you.
Cheeso