13

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.

Cheeso
  • 189,189
  • 101
  • 473
  • 713

2 Answers2

9

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(PipeDirection.In, pipeServer.ClientSafePipeHandle);
}

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.

Paul Groke
  • 6,259
  • 2
  • 31
  • 32
Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • 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 Apr 28 '10 at 12:30
  • 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 Apr 28 '10 at 17:33
  • ps: I've asked this question before, see http://stackoverflow.com/questions/1499520 . I'm very pleased to get a general, simple answer. – Cheeso Apr 28 '10 at 17:45
  • @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 Apr 28 '10 at 18:51
  • @Aaron, thanks for the insight. More q's: What do you mean by "pure threaded approach"? Assuming we're have the I-need-to-return-a-stream-but-component-X-wants-to-write-to-one situation, as you put it, I couldn't imagine a solution that did not involve a bg thread, and some signalling to coordinate the reads & writes. I had actually written an adapter stream to address the issue, but it wasn't hardened and was waaay more code than I wanted to own. This anon pipe approach seems simple and easy. So -- what am I missing with the "pure threaded approach"? – Cheeso Apr 28 '10 at 22:52
  • @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 Apr 28 '10 at 22:59
  • There's a serious bug in the above `GetPipedStream()` implementation. Long story short: `new AnonymousPipeClientStream(pipeServer.GetClientHandleAsString())` will create a second `SafeHandle` refering to the same handle value, potentially causing a completely unrelated handle to be freed whenever the GC pick up that `SafeHandle` (the same handle value may have been re-used at that point because the first `SafeHandle` has already released it). Fix: use `new AnonymousPipeClientStream(PipeDirection.In, pipeServer.ClientSafePipeHandle)` instead. I will edit the answer. – Paul Groke Aug 18 '15 at 18:18
  • Note that if an exception escapes the lambda passed to `QueueUserWorkItem` the process will terminate. This is likely, as the pipe will break if the client disconnects while you're still writing the response. – Tim Sylvester Jun 16 '18 at 00:41
3

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).

podiluska
  • 50,950
  • 7
  • 98
  • 104
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 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 Apr 28 '10 at 06:25
  • @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 Apr 28 '10 at 07:44