WCF MemoryStream 返回类型是否可以写入 Http Response 对象以作为 Excel 文件下载?

发布于 2024-10-10 00:08:52 字数 965 浏览 3 评论 0原文

我构建了一个解析应用程序,该应用程序读取 xml 文件并使用 NPOI 库填充 Excel 工作簿。最初,我将其作为 .net Web 应用程序的一部分,从 NPOI 获取 MemoryStream 并将其写入 Response 对象,以便浏览器下载它。此后,我将解析移至 Windows 服务中托管的 WCF netTcp 服务。通信效果很好,但是当我从 WCF 服务返回 MemoryStream 并将其写入响应时,出现以下错误:

Microsoft JScript 运行时错误: Sys.WebForms.PageRequestManagerParserErrorException: 从服务器收到的消息 无法解析。

我的问题是:当流从 wcf 服务传递到我的客户端时,会发生什么情况?该流(理论上)与我最初写入响应的 NPOI 流完全相同。我需要对客户端进行任何特殊处理才能使其正常工作吗?

这是我的客户端代码:(在 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();

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();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

把时间冻结 2024-10-17 00:08:52

您似乎重新运行流以请求使用更新面板进行部分页面更新(搜索 Sys.WebForms.PageRequestManagerParserErrorException 以查找有关异常的更多详细信息)。

确保您仅将流重新调整为全页请求(GET/POST 由浏览器本身发出,而不是由页面上需要某种特定类型响应的某些脚本发出)。

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

揽月 2024-10-17 00:08:52

我在此处找到了解决方案。这是我的冗长答案,以防将来对某人有所帮助。首先,netTcp 不支持真正的流式传输,仅支持缓冲。文章指出只有 basicHttp 支持流式传输,但事实并非如此,因为我已经使用 wsHttp 成功测试了这一点。在我的客户端配置文件中,我现在有 2 个绑定定义; 1 个用于 netTcp,另一个用于 wsHttp(对于我所有的非流通信,我仍然想使用 netTcp)。如果您使用 basicHttp,则需要将“transferMode”属性设置为“Streamed” - wsHttp 不允许该属性。

然后我必须在我的服务中定义一个新的 DataContract,它定义了一个我定义为 MessageBodyMember 的成员。该成员的类型为 MemoryStream:

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

然后,对于最初返回 MemoryStream 的 OperationContract,我修改为返回新的 FileDownloadMessage 数据协定:

[OperationContract]
FileDownloadMessage GetExportedFile();

该协定的实现是:

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;
}

现在 WCF 服务返回 Stream,而没有任何其他附带元数据,因此我的网页的 Response 对象可以正确解析流:

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();

我的配置如下:

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

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>
国粹 2024-10-17 00:08:52

如果您使用 IE,可以尝试以下一些操作;显然,与其他浏览器相比,它从内容类型推断的数据较少:

  • 指定公共 Pragma 标头和必须重新验证 CacheControl 标头。

  • 尝试显式指定内容传输编码:Response.AddHeader("Content-Transfer-Encoding","binary");

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文