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

上一篇: MVC3 return multiple pdfs as a zip file

下一篇: C# Variable = new function () {};