c# - MVC3将多个pdf作为zip文件返回

标签 c# asp.net-mvc-3 file zip itextsharp

我有一个视图可以返回包含多个页面的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保存到工作流中。然后,将数组(长度为零)写入同一工作流。这是没有操作。最后,您重置位置。

您可以将所有内容替换为:

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


最后,我不太喜欢您从FileStreamResult创建MemoryStream的情况。问题是您的整个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运行时保存的值。

造成困难的是延期执行。

您想要的是每个foreach循环中ZipFile.Save()的每个不同值
在调用匿名函数时可以访问-稍后,在foreach循环之外。您
可以使用实用程序方法(bla)来做到这一点,就像我上面显示的那样。您可以
也可以通过额外的闭合来做到这一点,例如:

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


GetBlaForName()返回一个方法-实际上是一个Action,它只是一个类型化方法。每次循环时,都会创建该Action的新实例,并为bla引用不同的值。直到GetEntryWriter时才调用该动作。

关于c# - MVC3将多个pdf作为zip文件返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10862768/

相关文章:

javascript - 查找任何用户的桌面文件路径

c++ - C++中真正的异步文件IO

c# - 在c#中以 block 的形式读取整个文件

c# - ViewModel 返回默认值 0

asp.net-mvc - ASP.Net C#Razor显示编码的HTML而不是原始html

visual-studio-2010 - 在 Visual Studio 2010 中同时运行两个项目 - 不想

c# - MVC 路由 : How to map two names to one action

c# - MonoDevelop 的 LINQ2SQL 替代品

asp.net-mvc-3 - MVC 关注点分离

c++ - 跨多个文件定义命名空间