如果您的解决方案有效,那么最简单的做法就是保持原样。
另一方面,我确实有一些关于您使用 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 循环中调用此 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 模型了解得不够多,无法知道其中是否有对此有所帮助的东西。如果没有,您可以use an Anonymous Pipe to invert the direction of the streams,并且无需将所有压缩数据保存在内存中。
我的意思是:创建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 的一个新实例,并且它为 bla 引用不同的值。直到ZipFile.Save() 时才会调用该操作。