Search code examples
c#wcfstreamingwcf-streaming

WCF Stream.Read always returns 0 in client


I've spent most of my day trying to figure out why this isn't working. I have a WCF service that streams an object to the client. The client is then supposed to write the file to its disk. But when I call stream.Read(buffer, 0, bufferLength) it always returns 0. Here's my code:

namespace StreamServiceNS
{
    [ServiceContract]
    public interface IStreamService
    {
        [OperationContract]
        Stream downloadStreamFile();
    }
}
class StreamService : IStreamService
{
    public Stream downloadStreamFile()
    {
        ISSSteamFile sFile = getStreamFile();
        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream stream = new MemoryStream();
        bf.Serialize(stream, sFile);
        return stream;
    }
}

Service config file:

<system.serviceModel>
<services>
  <service name="StreamServiceNS.StreamService">
    <endpoint address="stream" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IStreamService"
              name="BasicHttpEndpoint_IStreamService" contract="SWUpdaterService.ISWUService" />
  </service>
</services>
<bindings>
  <basicHttpBinding>
    <binding name="BasicHttpBinding_IStreamService" transferMode="StreamedResponse" 
             maxReceivedMessageSize="209715200"></binding>
  </basicHttpBinding>
</bindings>
<behaviors>
  <serviceBehaviors>
    <behavior>
      <serviceThrottling maxConcurrentCalls ="100" maxConcurrentSessions="400"/>
      <serviceMetadata httpGetEnabled="true"/>
      <serviceDebug includeExceptionDetailInFaults="false"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

Client:

TestApp.StreamServiceRef.StreamServiceClient client = new StreamServiceRef.StreamServiceClient();
        try
        {
            Stream stream = client.downloadStreamFile();
            int bufferLength = 8 * 1024;
            byte[] buffer = new byte[bufferLength];

            FileStream fs = new FileStream(@"C:\test\testFile.exe", FileMode.Create, FileAccess.Write);
            int bytesRead;
            while ((bytesRead = stream.Read(buffer, 0, bufferLength)) > 0)
            {
                fs.Write(buffer, 0, bytesRead);
            }
            stream.Close();
            fs.Close();
        }
        catch (Exception e) { Console.WriteLine("Error: " + e.Message); }

Client app.config:

    <system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="BasicHttpEndpoint_IStreamService" maxReceivedMessageSize="209715200" transferMode="StreamedResponse">
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://[server]/StreamServices/streamservice.svc/stream"
            binding="basicHttpBinding" bindingConfiguration="BasicHttpEndpoint_IStreamService"
            contract="StreamServiceRef.IStreamService" name="BasicHttpEndpoint_IStreamService" />
    </client>
    </system.serviceModel>

(some code clipped for brevity)

I've read everything I can find on making WCF streaming services, and my code looks no different than theirs. I can replace the streaming with buffering and send an object that way fine, but when I try to stream, the client always sees the stream as "empty". The testFile.exe gets created, but its size is 0KB.

What am I missing?


UPDATE
Hrm. Well, I can't debug it because when I try to it tells me that the WCF Test Client does not support Streams. But, I know something is making it across in the stream because if I don't put the while loop, and just have one fs.Write(...), it will write 8KB once the stream closes. So something is getting across.

I looked at your post and added:

[MessageContract]
public class FileDownloadMessage
{
    [MessageBodyMember(Order = 1)]
    public MemoryStream FileByteStream;
}

to my IStreamService. And downloadStreamFile() now returns a FileDownloadMessage object.

But after updating the service reference on my client, it thinks downloadStreamFile() returns a TestApp.StreamServiceRef.MemoryStream object.
It also created a downloadStreamFileRequest() class, which I did not make and have no idea where it's coming from.


Solution

  • First of all, are you able to debug the service and see if the stream contains any data on that side before it gets sent to the client?

    I am returning a stream from my WCF service as well. I had a problem with it, too, though not quite the same issue. Hopefully the solution that I found will help. I found the solution here. This solution is dealing with uploading a stream, but the implementation in my download service worked fine.
    My SO question/solution is here (My own answer is the one you should probably look at, not the accepted answer as that is specific to AJAX)

    EDIT: The basic differences is that I wrapped my Stream in a MessageContract where I specified the Stream as the only member of the message and the basicHttp binding 'transferMode' attribute is 'Streamed'. I'm new to WCF, so I can't be sure this will help, but I hope it does!

    EDIT 2: I wouldn't use a service reference. You can run (from cmd prompt) 'svcutil.exe [mex url]' to get a config file and a .cs file that may be more accurate than what you get from the service reference. I don't even have a service reference in my project and it works fine. Just add that generated .cs file to your project and add a 'using' statement for that. Also, you can add the config settings to your app or web config file on the client. Those 2 generated files get created in the directory you run the svcutil command in.

    The DownloadStreamFileRequest class is just a request object that the client passes to the base.Channel when making the call to the service. In my code below, it's called GetExportedFileRequest

    In my generated .cs file, I have the following method that returns System.IO.MemoryStreamand it gets this stream from the FileDownloadMessage object:

    public System.IO.MemoryStream GetExportedFile()
    {
        GetExportedFileRequest inValue = new GetExportedFileRequest();
        FileDownloadMessage retVal = ((IDailyBillingParser)(this)).GetExportedFile( inValue );
        return retVal.FileByteStream;
    }