开发者

WCF and streaming requests and responses

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()开发者_如何学C;
          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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜