读取二进制文件并使用Response.BinaryWrite()

26

我有一个应用程序需要从文件系统中读取PDF文件,然后将其写入用户。该PDF文件大小为183KB,似乎完美无缺。当我使用下面的代码时,浏览器得到了一个224KB的文件,并且Acrobat Reader会弹出一个消息说文件已损坏,无法修复。

这是我的代码(我也尝试过使用File.ReadAllBytes(),但结果相同):

using (FileStream fs = File.OpenRead(path))
{
    int length = (int)fs.Length;
    byte[] buffer;

    using (BinaryReader br = new BinaryReader(fs))
    {
        buffer = br.ReadBytes(length);
    }

    Response.Clear();
    Response.Buffer = true;
    Response.AddHeader("content-disposition", String.Format("attachment;filename={0}", Path.GetFileName(path)));
    Response.ContentType = "application/" + Path.GetExtension(path).Substring(1);
    Response.BinaryWrite(buffer);
}

你在提供的代码示例中看到了224KB吗(fs.Length),还是在读取时看到的? - Jon B
在我拿回文件后,我检查了大小,发现我忘记在那里放置Response.End(),正如BarneyHDog所指出的那样。 - jhunter
这并非完全相关,但涉及您添加到头文件中的文件名。我不确定现在是否已经解决了,但是当文件名包含逗号时,Chrome会为我生成"重复头文件"警告,直到我将头修改为以下内容:context.Response.AddHeader("Content-disposition", "attachment; filename=\"" + {0} + "\""); 这将用引号括起文件名。以下是参考链接:什么是“重复头文件”警告? - fujiiface
10个回答

26

在调用 Response.BinaryWrite() 之后,尝试添加 Response.End();。

你可能会在 Response.BinaryWrite 后无意中发送其他内容,这可能会让浏览器混淆。使用 Response.End 将确保浏览器只接收到你真正想要发送的内容。


这有点像清空缓存。非常重要,因为每个字节都对使流有效至关重要。 - Roland
这对我起作用了:Response.BinaryWrite(myBites); Response.End(); Response.Flush(); - Broken_Window
对于未来的读者,请注意,由于某些原因,每次运行此代码都会使我们的生产应用程序池崩溃。 - Marie

16
        Response.BinaryWrite(bytes);
        Response.Flush();
        Response.Close();
        Response.End();

这对我们很有用。我们可以从SQL Reporting Services创建PDF文件。


10

我们已经成功地使用了这个方法。 WriteFile会为您执行下载操作,并在末尾进行Flush / End以将所有内容发送到客户端。

            //Use these headers to display a saves as / download
            //Response.ContentType = "application/octet-stream";
            //Response.AddHeader("Content-Disposition", String.Format("attachment; filename={0}.pdf", Path.GetFileName(Path)));

            Response.ContentType = "application/pdf";
            Response.AddHeader("Content-Disposition", String.Format("inline; filename={0}.pdf", Path.GetFileName(Path)));

            Response.WriteFile(path);
            Response.Flush();
            Response.End();

6

既然您直接从文件系统发送文件,没有中间处理,为什么不使用Response.TransmitFile呢?

Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition",
    "attachment; filename=\"" + Path.GetFileName(path) + "\"");
Response.TransmitFile(path);
Response.End();

(我怀疑您的问题是由于缺少“Response.End”引起的,这意味着您发送的其余页面内容将附加在PDF数据后面。)

5
仅供参考,如此博客文章所述:http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx 不建议调用 Response.Close()Response.End() - 相反请使用 CompleteRequest()。您的代码应该类似于以下内容:
    byte[] bytes = {};

    bytes = GetBytesFromDB();  // I use a similar way to get pdf data from my DB

    Response.Clear();
    Response.ClearHeaders();
    Response.Buffer = true;
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    Response.ContentType = "application/pdf";
    Response.AppendHeader("Content-Disposition", "attachment; filename=" + anhangTitel);
    Response.AppendHeader("Content-Length", bytes.Length.ToString());
    this.Context.ApplicationInstance.CompleteRequest();

哇,谢谢分享。我一直遇到“当使用自定义TextWriter时,OutputStream不可用”的问题,通过类似于你的代码修改,解决了我的部分问题。 - JoshYates1980

2

1
在我的MVC应用程序中,我已为所有响应启用了gzip压缩。如果您正在使用具有gzipped响应的ajax调用读取此二进制写入,则会获取gzipped字节数组,而不是您需要处理的原始字节数组。
//c# controller is compressing the result after the response.binarywrite

[compress]
public ActionResult Print(int id)       
{
... 
var byteArray=someService.BuildPdf(id);
return  return this.PDF(byteArray, "test.pdf");
}

//where PDF is a custom actionresult that eventually does this:
 public class PDFResult : ActionResult
{
...
    public override void ExecuteResult(ControllerContext context)
    {
        //Set the HTTP header to excel for download
        HttpContext.Current.Response.Clear();
        //HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
        HttpContext.Current.Response.ContentType = "application/pdf";
        HttpContext.Current.Response.AddHeader("content-disposition", string.Concat("attachment; filename=", fileName));
        HttpContext.Current.Response.AddHeader("Content-Length", pdfBytes.Length.ToString());
        //Write the pdf file as a byte array to the page
        HttpContext.Current.Response.BinaryWrite(byteArray);
        HttpContext.Current.Response.End();
    }
}

//javascript

function pdf(mySearchObject) {
    return $http({
    method: 'Post',
    url: '/api/print/',
    data: mySearchObject,
    responseType: 'arraybuffer',
    headers: {
    'Accept': 'application/pdf',
    }
    }).then(function (response) {

var type = response.headers('Content-Type');
//if response.data is gzipped, this blob will be incorrect.  you have to uncompress it first.
var blob = new Blob([response.data], { type: type });
var fileName = response.headers('content-disposition').split('=').pop();

if (window.navigator.msSaveOrOpenBlob) { // for IE and Edge
    window.navigator.msSaveBlob(blob, fileName);
} else {

    var anchor = angular.element('<a/>');
    anchor.css({ display: 'none' }); // Make sure it's not visible
    angular.element(document.body).append(anchor); // Attach to document

    anchor.attr({
    href: URL.createObjectURL(blob),
    target: '_blank',
    download: fileName
    })[0].click();

    anchor.remove();
}
});

}

"var blob = new Blob([response.data], { type: type });" 这段代码会在将字节数组转换为JavaScript中的文件时产生无效/损坏的文件,如果您不先解压缩它。
为了解决这个问题,您可以选择防止对二进制数据进行gzip压缩,以便您可以正确地将其转换为正在下载的文件,或者您必须在JavaScript代码中解压缩该gzip数据,然后再将其转换为文件。

1
也许你忘记了加上 Response.close 来关闭二进制流。

0

我也发现需要添加以下内容:

Response.Encoding = Encoding.Default

如果我不包含这个,我的JPEG文件会损坏并且字节大小会翻倍。
但是只有当处理程序从ASPX页面返回时才需要。似乎在ASHX中运行时不需要这样做。

0
除了Igor的Response.Close()之外,我还会添加一个Response.Flush()。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接