Can WCF MemoryStream return type be written to the Http Response object for downloading as an Excel file?

StackOverflow https://stackoverflow.com/questions/4595341

Question

I built a parsing application that reads xml files and populates an Excel workbook using the NPOI library. Originally, I had that as part of my .net web app and would get the MemoryStream from NPOI and write that to the Response object so the browser would download it. I've since moved the parsing to a WCF netTcp service hosted in a Windows Service. The communication works great, but when I return the MemoryStream from the WCF service and write it to the response, I get the following error:

Microsoft JScript runtime error: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed.

My question is: What happens to the stream when it gets passed from the wcf service to my client? The stream is (theoretically) the exact same stream from NPOI that I was writing to the response originally. Is there any special processing that I need to do on the client to make this work?

Here is my client code: (the exception get thrown at Response.End()

string filename = "test.xls";

ms = client.GetExportedFile();
byte[] b = ms.ToArray();

ms.Flush();
ms.Close();

Response.Clear();
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader( "Content-Disposition", string.Format( "attachment;filename={0}", filename ) );
Response.AddHeader( "Content-Length", b.Length.ToString() );
Response.BinaryWrite( b );
Response.End();
Was it helpful?

Solution

You seem to retrun stream to request for partial page update with Update panel (search for Sys.WebForms.PageRequestManagerParserErrorException to find more details about exception).

Make sure that you are retruning stream only to full page request (GET/POST issued by browser itself, not by some script on the page that expects some particular type of responce).

OTHER TIPS

I found the solution here. Here is my lengthy answer in case it helps someone in the future. First off, netTcp does not support true streaming, only buffering. The article states that only basicHttp supports streaming, but that is not the case as I have successfully tested this with wsHttp. In my client-side config file, I now have 2 binding definitions; 1 for netTcp and the other for wsHttp (for all my non-streaming communication, I still want to use netTcp). If you use basicHttp, then you need to set the 'transferMode' attribute to 'Streamed' - wsHttp does not allow that attribute.

Then I had to define a new DataContract in my service that defined a member that I defined as the MessageBodyMember. This member is of type MemoryStream:

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

Then for the OperationContract that was returning a MemoryStream originally, I modified to return the new FileDownloadMessage data contract:

[OperationContract]
FileDownloadMessage GetExportedFile();

The implementation of that contract is:

public FileDownloadMessage GetExportedFile()
{
    FileDownloadMessage f = new FileDownloadMessage();
    f.FileByteStream = new MemoryStream();

    if ( ProgressMonitor.Status == ProcessStatus.CompletedReadyForExport )
    {
        f.FileByteStream = ProgressMonitor.ExportedFileData;

        ProgressMonitor.Status = ProcessStatus.Ready;
    }
    else
    {
        f.FileByteStream = null;
    }

    return f;
}

Now the WCF service is returning the Stream without any other accompanying metadata so my web page's Response object can correctly parse the stream:

MemoryStream ms = new MemoryStream();
string filename = "test.xls";

// the code file from the wcf service includes a get method to get the 
// MessageBodyMember directly
ms = streamingClient.GetExportedFile();
byte[] b = ms.ToArray();

ms.Flush();
ms.Close();

Response.Clear();
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader( "Content-Disposition", string.Format( "attachment;filename={0}", filename ) );
Response.AddHeader( "Content-Length", b.Length.ToString() );
Response.BinaryWrite( b );
Response.End();

My config looks like:

<wsHttpBinding>
    <binding name="WSHttpBindingEndPoint" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
            maxBufferPoolSize="524288" maxReceivedMessageSize="655360"
            messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
            allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="1638400"
                maxBytesPerRead="4096" maxNameTableCharCount="16384" />
        <reliableSession ordered="true" inactivityTimeout="00:10:00"
                enabled="false" />
        <security mode="Message">
            <transport clientCredentialType="Windows" proxyCredentialType="None"
                    realm="" />
            <message clientCredentialType="Windows" negotiateServiceCredential="true"
                    algorithmSuite="Default" />
        </security>
    </binding>
</wsHttpBinding>
<netTcpBinding>
    <binding name="NetTcpBindingEndPoint" closeTimeout="00:01:00"
        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
        transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
        hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288"
        maxBufferSize="655360" maxConnections="10" maxReceivedMessageSize="655360">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="1638400"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
        <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
        <security mode="Transport">
            <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
            <message clientCredentialType="Windows" />
        </security>
    </binding>
</netTcpBinding>

A few things to try if you're using IE; apparently it infers less about the data from the content type than other browsers:

  • Specify a public Pragma header, and a must-revalidate CacheControl header.

  • Try explicitly specifying the content transfer encoding: Response.AddHeader("Content-Transfer-Encoding","binary");.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top