尝试使用asp.net流式传输PDF文件会产生“损坏的文件”

时间:2009-08-19 15:30:09

标签: asp.net filestream

在我的一个asp.net Web应用程序中,我需要隐藏正在向用户提供的pdf文件的位置。

因此,我正在编写一个方法,从CMS系统上的位置检索其二进制内容,然后将字节数组刷新到Web用户。

我很遗憾地在下载流时遇到错误:“无法打开文件,因为它已被删除”(或类似于在adobe reader中打开文件时)。

问题1:我做错了什么? 问题2:我可以使用这种方法下载大文件吗?

    private void StreamFile(IItem documentItem)
    {
        //CMS vendor specific API
        BinaryContent itemBinaryContent = documentItem.getBinaryContent();
        //Plain old .NET
        Stream fileStream = itemBinaryContent.getContentStream();
        var len = itemBinaryContent.getContentLength();
        SendStream(fileStream, len, itemBinaryContent.getContentType());
    }

    private void SendStream(Stream stream, int contentLen, string contentType)
    {
        Response.ClearContent();
        Response.ContentType = contentType;
        Response.AppendHeader("content-Disposition", string.Format("inline;filename=file.pdf"));
        Response.AppendHeader("content-length", contentLen.ToString());
        var bytes = new byte[contentLen];
        stream.Read(bytes, 0, contentLen);
        stream.Close();
        Response.BinaryWrite(bytes);
        Response.Flush();
    }

6 个答案:

答案 0 :(得分:7)

这是我使用的方法。这会传回一个附件,因此IE会生成一个打开/保存对话框。我也碰巧知道文件不会超过1M,所以我确信有更简洁的方法可以做到这一点。

我遇到了类似PDF的问题,我意识到我绝对不得不使用二进制流和ReadBytes。任何带有字符串的东西搞砸了。

Stream stream = GetStream(); // Assuming you have a method that does this.
BinaryReader reader = new BinaryReader(stream);

HttpResponse response = HttpContext.Current.Response;
response.ContentType = "application/pdf";
response.AddHeader("Content-Disposition", "attachment; filename=file.pdf");
response.ClearContent();
response.OutputStream.Write(reader.ReadBytes(1000000), 0, 1000000);

// End the response to prevent further work by the page processor.
response.End();

答案 1 :(得分:7)

其他答案在发送响应之前将文件内容复制到内存中。如果数据已经在内存中,那么您将拥有两个副本,这对可伸缩性来说不是很好。这可能效果更好:

public void SendFile(Stream inputStream, long contentLength, string mimeType, string fileName)
{
    string clength = contentLength.ToString(CultureInfo.InvariantCulture);
    HttpResponse response = HttpContext.Current.Response;
    response.ContentType = mimeType;
    response.AddHeader("Content-Disposition", "attachment; filename=" + fileName);
    if (contentLength != -1) response.AddHeader("Content-Length", clength);
    response.ClearContent();
    inputStream.CopyTo(response.OutputStream);
    response.OutputStream.Flush();
    response.End();
}

由于Stream是一个字节集合,因此不需要使用BinaryReader。只要输入流在文件末尾结束,您就可以在要发送到Web浏览器的流上使用CopyTo()方法。所有内容都将写入目标流,而不会制作任何数据的中间副本。

如果只需要从流中复制一定数量的字节,可以创建一个扩展方法,增加几个CopyTo()重载:

public static class Extensions
{
    public static void CopyTo(this Stream inStream, Stream outStream, long length)
    {
        CopyTo(inStream, outStream, length, 4096);
    }

    public static void CopyTo(this Stream inStream, Stream outStream, long length, int blockSize)
    {
        byte[] buffer = new byte[blockSize];
        long currentPosition = 0;

        while (true)
        {
            int read = inStream.Read(buffer, 0, blockSize);
            if (read == 0) break;
            long cPosition = currentPosition + read;
            if (cPosition > length) read = read - Convert.ToInt32(cPosition - length);
            outStream.Write(buffer, 0, read);
            currentPosition += read;
            if (currentPosition >= length) break;
        }
    }
}

然后您可以这样使用它:

inputStream.CopyTo(response.OutputStream, contentLength);

这适用于任何输入流,但一个简单的例子就是从文件系统中读取文件:

string filename = @"C:\dirs.txt";
using (FileStream fs = File.Open(filename, FileMode.Open))
{
    SendFile(fs, fs.Length, "application/octet-stream", filename);
}

如前所述,请确保您的MIME类型与内容相符。

答案 2 :(得分:1)

这个代码片段为我做了:

                Response.Clear();
                Response.ClearContent();
                Response.ClearHeaders();
                Response.ContentType = mimeType;
                Response.AddHeader("Content-Disposition", "attachment");
                Response.WriteFile(filePath);
                Response.Flush();

答案 3 :(得分:0)

我在当前的2.0网站上有类似的工作。而且我记得虽然已经有一段时间努力让它发挥作用所以我不记得这些挣扎。

但是,我拥有的东西之间存在一些差异。希望这些可以帮助您解决问题。

  • 在ClearContent调用之后,我调用ClearHeaders();
  • 我没有指定长度。
  • 我没有在“处理”中指定内联
  • 我知道每个人都说不要这样做,但我有一个Response.Flush();然后是Response.Close();

另外,检查你的contentType值以确保它对于PDF是正确的((“Application / pdf”)。

答案 4 :(得分:0)

当您使用代码时,您将直接在回复中复制pdf中的字节。所有特殊的ascii代码都需要编码以传入http。使用流功能时,流被编码,因此您不必担心它。

    var bytes = new byte[contentLen];
    stream.Read(bytes, 0, contentLen);
    stream.Close();
    Response.BinaryWrite(bytes);

答案 5 :(得分:0)

此代码段包含从文件路径读取文件的代码,并提取出文件名:

private void DownloadFile(string path)
{
    using (var file = System.IO.File.Open(path, FileMode.Open))
    {
        var buffer = new byte[file.Length];
        file.Read(buffer, 0, (int)file.Length);

        var fileName = GetFileName(path);
        WriteFileToOutputStream(fileName, buffer);
    }
}

private string GetFileName(string path)
{
    var fileNameSplit = path.Split('\\');
    var fileNameSplitLength = fileNameSplit.Length;
    var fileName = fileNameSplit[fileNameSplitLength - 1].Split('.')[0];
    return fileName;
}

private void WriteFileToOutputStream(string fileName, byte[] buffer)
{
    Response.Clear();
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition",
        $"attachment; filename\"{fileName}.pdf");
    Response.OutputStream.Write(buffer, 0, buffer.Length);
    Response.OutputStream.Close();
    Response.Flush();
    Response.End();
}