MVC3将多个pdfs作为zip文件返回
我有一个视图,它返回一个包含多个页面的pdf(使用iTextSharp),但现在我必须更改它,以便每个页面都是一个单独的pdf(带有它自己的唯一标题)并返回一个zip文件。
我的原始代码如下所示:
public FileStreamResult DownloadPDF() { MemoryStream workStream = new MemoryStream(); Document document = new Document(); PdfWriter.GetInstance(document, workStream).CloseStream = false; document.Open(); // Populate pdf items document.Close(); byte[] byteInfo = workStream.ToArray(); workStream.Write(byteInfo, 0, byteInfo.Length); workStream.Position = 0; FileStreamResult fileResult = new FileStreamResult(workStream, "application/pdf"); fileResult.FileDownloadName = "fileName"; return fileResult; }
使用gzip压缩文件看起来很简单,但我不知道如何gzip多个文件并将其作为一个zip文件返回。 或者我应该使用gzip以外的东西,比如dotnetzip或sharpzip?
提前致谢!
如果您的解决方案有效,那么最简单的方法就是保持原样。
另一方面,我对您使用DoTNetZip库有一些评论。
首先,您的代码有点误导。 在这个部分:
byte[] byteInfo = workStream.ToArray(); zip.Save(workStream); workStream.Write(byteInfo, 0, byteInfo.Length); workStream.Position = 0;
…您正在将workStream读入数组。 但是那时,你没有给workStream写任何东西,所以数组是空的,零长度。 然后将zip保存到工作流中。 然后将数组(长度为零)写入同一工作流。 这是一个NO-OP。 最后你重置位置。
您可以用以下内容替换所有内容:
zip.Save(workStream); workStream.Position = 0;
这不是DotNetZip本身的问题,它只是你对流的操作的错误理解。
好的,接下来,您将不必要地分配临时缓冲区(内存流)。 将MemoryStream想象成一个字节数组,其上有一个Stream包装器,以支持Write(),Read(),Seek()等。 基本上你的代码是将数据写入临时缓冲区,然后告诉DotNetZip将临时缓冲区中的数据读入自己的缓冲区进行压缩。 您不需要该临时缓冲区。 它的工作方式与您完成的方式相同,但效率更高。
DotNetZip有一个AddEntry()
重载,它接受一个writer委托。 委托是DotNetZip调用的一个函数,用于告诉您的应用将条目内容写入zip存档。 您的代码写入未压缩的字节,DotNetZip压缩并将它们写入输出流。
在该编写器委托中,您的代码直接写入DotNetZip流 – 由DotNetZip传递给委托的流。 没有干预缓冲区。 效率很高。
请记住有关闭包的规则。 如果在for循环中调用此writer委托,则需要有一种方法来检索委托中与zipentry相对应的“bla”。 在调用zip.Save()
之前,委托不会被执行! 所以你不能依赖循环中’bla’的值。
public FileStreamResult DownloadPDF() { MemoryStream workStream = new MemoryStream(); using(var zip = new ZipFile()) { foreach(Bla bla in Blas) { zip.AddEntry(bla.filename + ".pdf", (name,stream) => { var thisBla = GetBlaFromName(name); Document document = new Document(); PdfWriter.GetInstance(document, stream).CloseStream = false; document.Open(); // write PDF Content for thisBla into stream/PdfWriter document.Close(); }); } zip.Save(workStream); } workStream.Position = 0; FileStreamResult fileResult = new FileStreamResult(workStream, System.Net.Mime.MediaTypeNames.Application.Zip); fileResult.FileDownloadName = "MultiplePDFs.zip"; return fileResult; }
最后,我并不特别喜欢从MemoryStream
创建FileStreamResult
。 问题是你的整个zip文件都保存在内存中,这对内存的使用非常困难。 如果您的zip文件很大,您的代码将保留内存中的所有内容。
我对MVC3模型知之甚少,不知道其中是否有任何东西可以帮助解决这个问题。 如果没有,您可以使用匿名管道来反转流的方向 ,并消除将所有压缩数据保存在内存中的需要。
这就是我的意思:创建FileStreamResult
要求您提供可读的流。 如果你使用MemoryStream,为了使它可读,你需要首先写入它,然后在将它传递给FileStreamResult
构造函数之前回到位置0。 这意味着该zip文件的所有内容必须在某个时间点连续保存在内存中。
假设您可以为FileStreamResult
构造函数提供可读的流,这将允许读者在您写入它的那一刻准确读取。 这就是匿名管道流的作用。 它允许您的代码使用可写流,而MVC代码获取其可读流。
这是代码中的样子。
static Stream GetPipedStream(Action writeAction) { AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(); ThreadPool.QueueUserWorkItem(s => { using (pipeServer) { writeAction(pipeServer); pipeServer.WaitForPipeDrain(); } }); return new AnonymousPipeClientStream(pipeServer.GetClientHandleAsString()); } public FileStreamResult DownloadPDF() { var readable = GetPipedStream(output => { using(var zip = new ZipFile()) { foreach(Bla bla in Blas) { zip.AddEntry(bla.filename + ".pdf", (name,stream) => { var thisBla = GetBlaFromName(name); Document document = new Document(); PdfWriter.GetInstance(document, stream).CloseStream = false; document.Open(); // write PDF Content for thisBla to PdfWriter document.Close(); }); } zip.Save(output); } }); var fileResult = new FileStreamResult(readable, System.Net.Mime.MediaTypeNames.Application.Zip); fileResult.FileDownloadName = "MultiplePDFs.zip"; return fileResult; }
我没有试过这个,但它应该工作。 这比你写的更有优势,更有内存效率。 缺点是它使用命名管道和几个匿名函数相当复杂。
仅当zip内容超出> 1MB范围时才有意义。 如果您的拉链小于那个,那么您可以按照我上面展示的第一种方式进行。
附录
为什么你不能在匿名方法中依赖bla
的值?
有两个关键点。 首先,foreach循环定义了一个名为bla
的变量,每次循环时都会采用不同的值。 看似很明显,但值得明确说明。
其次,匿名方法作为参数传递给ZipFile.AddEntry()
方法,并且它不会在foreach循环运行时运行。 事实上,在ZipFile.Save()
时,为每个添加的条目重复调用匿名方法一次。 如果你在匿名方法中引用bla
,它会获得分配给bla
的最后一个值 ,因为这是在ZipFile.Save()
运行时bla
保持的值。
这是导致困难的延迟执行。
你想要的是来自foreach循环的bla
每个不同值,在调用匿名函数时可以访问 – 稍后,在foreach循环之外。 您可以使用实用程序方法( GetBlaForName()
)执行此操作,如上所示。 您还可以使用额外的闭包执行此操作,如下所示:
Action GetEntryWriter(Bla bla) { return new Action((name,stream) => { Document document = new Document(); PdfWriter.GetInstance(document, stream).CloseStream = false; document.Open(); // write PDF Content for bla to PdfWriter document.Close(); }; } foreach(var bla in Blas) { zip.AddEntry(bla.filename + ".pdf", GetEntryWriter(bla)); }
GetEntryWriter
返回一个方法 – 实际上是一个Action,它只是一个类型化的方法。 每次循环时,都会创建该Action的新实例,并为bla引用不同的值。 直到ZipFile.Save()
调用该Action。
我最终使用DotNetZip而不是SharpZipLib,因为解决方案更简单。 这是我最终做的,它工作正常,但如果有人有任何建议/改变我会很高兴在这里。
public FileStreamResult DownloadPDF() { MemoryStream workStream = new MemoryStream(); ZipFile zip = new ZipFile(); foreach(Bla bla in Blas) { MemoryStream pdfStream = new MemoryStream(); Document document = new Document(); PdfWriter.GetInstance(document, pdfStream).CloseStream = false; document.Open(); // PDF Content document.Close(); byte[] pdfByteInfo = pdfStream.ToArray(); zip.AddEntry(bla.filename + ".pdf", pdfByteInfo); pdfStream.Close(); } zip.Save(workStream); workStream.Position = 0; FileStreamResult fileResult = new FileStreamResult(workStream, System.Net.Mime.MediaTypeNames.Application.Zip); fileResult.FileDownloadName = "MultiplePDFs.zip"; return fileResult; }
正如Turnkey所说 – SharpZipLib非常适合使用多个文件和内存流。 只需预览需要压缩的文件并将其添加到存档中。 这是一个例子:
// Save it to memory MemoryStream ms = new MemoryStream(); ZipOutputStream zipStream = new ZipOutputStream(ms); // USE THIS TO CHECK ZIP :) //FileStream fileOut = File.OpenWrite(@"c:\\test1.zip"); //ZipOutputStream zipStream = new ZipOutputStream(fileOut); zipStream.SetLevel(0); // Loop your pages (files) foreach(string filename in files) { // Create and name entry in archive FileInfo fi = new FileInfo(filename); ZipEntry zipEntry = new ZipEntry(fi.Name); zipStream.PutNextEntry(zipEntry); // Put entry to archive (from file or DB) ReadFileToZip(zipStream, filename); zipStream.CloseEntry(); } // Copy from memory to file or to send output to browser, as you did zipStream.Close();
我不知道你是如何得到ziped的信息,所以我认为该文件是好的:)
/// /// Reads file and puts it to ZIP stream /// private void ReadFileToZip(ZipOutputStream zipStream, string filename) { // Simple file reading :) using(FileStream fs = File.OpenRead(filename)) { StreamUtils.Copy(fs, zipStream, new byte[4096]); } }
我建议使用SharpZipLib压缩成标准的zip文件。 将文件放入临时文件夹并使用FastZip类来制作zip。
- 从布局页面ASP.NET MVC3 RAZOR访问会话变量
- 视图标题中@model和@inherit的MVC4 Razor差异?
- 在cshtml razor视图中分离JavaScript
- ASP.NET MVC3和Facebook集成
- 如何在entity framework4.1中使用mvc-mini-profiler
- 将局部视图加载到模态弹出窗口中
- MusicStore’System.Data.Objects.ObjectSet ‘不包含’Add’的定义,也没有扩展方法’Add’接受第一个参数
- String.Format用于TextBoxFor上的货币
- 使用自定义消息的MVC 3 AuthorizeAttribute重定向