Может ли возвращаемый тип WCF MemoryStream записываться в объект Http Response для загрузки в виде файла Excel?

Я создал приложение для синтаксического анализа, которое читает XML-файлы и заполняет книгу Excel с использованием библиотеки NPOI. Первоначально я имел это как часть моего веб-приложения.net и получал MemoryStream от NPOI и записывал это в объект Response, чтобы браузер загружал его. С тех пор я переместил анализ в службу WCF netTcp, размещенную в службе Windows. Связь прекрасно работает, но когда я возвращаю MemoryStream из службы WCF и записываю его в ответ, я получаю следующую ошибку:

Ошибка выполнения 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();

3 ответа

Решение

Похоже, вы повторно запускаете поток, чтобы запросить частичное обновление страницы с помощью панели "Обновление" (найдите Sys.WebForms.PageRequestManagerParserErrorException, чтобы найти более подробную информацию об исключении).

Убедитесь, что вы перезапускаете поток только к полностраничному запросу (GET/POST выдается самим браузером, а не каким-либо скриптом на странице, который ожидает какой-то определенный тип ответа).

Я нашел решение здесь. Вот мой длинный ответ на случай, если это поможет кому-то в будущем. Во-первых, 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;
}

Затем для OperationContract, который первоначально возвращал MemoryStream, я изменил так, чтобы он возвращал новый контракт данных 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 возвращает поток без каких-либо сопутствующих метаданных, поэтому объект 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>

Несколько вещей, которые нужно попробовать, если вы используете IE; по-видимому, он меньше влияет на данные типа контента, чем другие браузеры:

  • Укажите общедоступный заголовок Pragma и обязательный повторный проверку заголовка CacheControl.

  • Попробуйте явно указать кодировку передачи контента: Response.AddHeader("Content-Transfer-Encoding","binary");,

Другие вопросы по тегам