【问题标题】:Trying to stream a PDF file with asp.net is producing a "damaged file"尝试使用 asp.net 流式传输 PDF 文件会产生“损坏的文件”
【发布时间】:2009-08-19 15:30:09
【问题描述】:

在我的一个 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();
    }

【问题讨论】:

  • 为什么不使用 Response.WriteFile 方法?
  • 该文件在文件系统中并不真正存在。
  • 更清楚一点:该文件存在于 CMS 数据库中。它是 url 可寻址的,但我无法真正从文件系统中检索文件。
  • 啊,我错过了那部分。如何删除内容长度?另外,为什么你需要 string.Format("inline;filename=file.pdf") ?
  • 不幸的是,删除没有区别:(

标签: asp.net filestream


【解决方案1】:

这是我使用的一种方法。这会传回一个附件,因此 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();

【讨论】:

  • 不知道是哪一点代码让它工作的,但它终于工作了 :) 谢谢!
  • 你在 ReportViewer 中使用这个吗?
  • 指定一定数量的字节的缺点是所有这些字节都将写入流,即使文件没有那么大。您可以在调用 reader.ReadBytes 时获取流的长度并使用该数字代替
【解决方案2】:

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

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 类型对于内容是正确的。

【讨论】:

    【解决方案3】:

    这个片段是为我做的:

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

    【讨论】:

      【解决方案4】:

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

      不过,我所拥有的与你所拥有的之间存在一些差异。希望这些能帮助您解决问题。

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

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

      【讨论】:

        【解决方案5】:

        当您使用代码时,您会直接在响应中从 pdf 中复制字节。所有特殊的ascii代码都需要编码才能传入http。使用流功能时,流是经过编码的,因此您不必担心。

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

        【讨论】:

          【解决方案6】:

          这个sn-p包含了从文件路径读入文件并提取文件名的代码:

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

          【讨论】:

          • 它以字节数组的形式读取整个文件并保持文件打开。
          • 你会推荐什么改变?干杯
          • 我是否还需要调用 Response.OutputStream.Close()?
          • 我是否将文件的开头包含在 using 语句中?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-16
          • 2018-06-05
          • 1970-01-01
          • 2020-10-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多