MVC3将多个pdf作为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()
重载。 代理是DotNetZip调用的一个函数,用于告诉您的应用程序将条目内容写入zip存档。 您的代码写入未压缩的字节,并且DotNetZip压缩并将它们写入输出流。
在该编写器委托中,您的代码直接写入DotNetZip流 - 由DotNetZip传递给委托的流。 没有中间缓冲区。 效率不错。
请记住有关关闭的规则。 如果您在for循环中调用此作者委托,则需要获取与委托中的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,为了使其可读,您需要先写入它,然后再回到位置0,然后将它传递给FileStreamResult
构造函数。 这意味着该zip文件的所有内容必须在某个时间点连续存储在内存中。
假设您可以向FileStreamResult
构造函数提供可读流,这将允许读者在您写入它的时刻读取。 这是一个匿名管道流。 它允许您的代码使用可写入的流,而MVC代码获取其可读流。
这是代码中的样子。
static Stream GetPipedStream(Action<Stream> 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
,因为那是价值bla
持有当时ZipFile.Save()
运行。
这是推迟执行造成的困难。
你想要的是每个foreach循环中bla
不同值,在调用匿名函数的时候可以访问 - 稍后,在foreach循环之外。 您可以使用实用程序方法( GetBlaForName()
)来完成此操作,如上所示。 您也可以通过额外的关闭来完成此操作,如下所示:
Action<String,Stream> GetEntryWriter(Bla bla)
{
return new Action<String,Stream>((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()
时才被调用。
我最终使用了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,所以我认为该文件是好的:)
/// <summary>
/// Reads file and puts it to ZIP stream
/// </summary>
private void ReadFileToZip(ZipOutputStream zipStream, string filename)
{
// Simple file reading :)
using(FileStream fs = File.OpenRead(filename))
{
StreamUtils.Copy(fs, zipStream, new byte[4096]);
}
}
链接地址: http://www.djcxy.com/p/51093.html