MVC3返回多个PDF作为一个zip文件(MVC3 return multiple pdfs as

2019-07-29 16:42发布

我有一个返回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?

提前致谢!

Answer 1:

如果您的解决方案的工作,然后做最简单的事情就是把一切,因为是。

在另一方面,我确实有关于你的DoTNetZip库中使用的一些意见。

首先,你的代码是有点误导。 在这个部分:

byte[] byteInfo = workStream.ToArray();                        

zip.Save(workStream);                        

workStream.Write(byteInfo, 0, byteInfo.Length);                        
workStream.Position = 0;                        

...您正在阅读的工作流程到一个数组。 但是,在这一点上,你还没有任何数据写入工作流程,所以数组是空的,零长度。 然后保存压缩到工作流程。 然后,你写阵列(零长度)成相同的工作流程。 这是一个NO-OP。 最后,你复位位置。

你可以取代所有的与:

zip.Save(workStream);                        
workStream.Position = 0;                        

这是不是与DotNetZip本身的问题,这是对你的关于流的操作部分只是一个错误的认识。

OK,接下来将分配临时缓冲区(memorystreams)不必要的。 想想一个MemoryStream刚刚字节数组,上面有一个流封装方式,支持写(),读(),SEEK(),等等。 基本上你的代码将数据写入到该临时缓冲区,然后告诉DotNetZip从临时缓冲区中的数据读入到它自己的缓冲区进行压缩。 你不需要是临时缓冲区。 它的工作原理,你已经做的方式,但它可能是更有效的。

DotNetZip具有AddEntry()它接受一个作家委托过载。 该委托是DotNetZip来电告知您的应用程序编写条目的内容到压缩存档的功能。 您的代码写入压缩字节,并DotNetZip压缩,并将其写入到输出流。

在该作家的代表,你的代码直接写入DotNetZip流 - 传递到由DotNetZip委托流。 有没有中间缓冲。 尼斯效率。

记住关于闭包的规则。 如果你把这个作家代表在for循环中,你需要有检索“喇嘛”所对应的委托内的ZipEntry的一种方式。 该委托没有得到执行之前zip.Save()被调用! 所以你不能依靠“喇嘛”从循环的价值。

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

最后,我特别不喜欢你的一个创作FileStreamResultMemoryStream 。 问题是,你的整个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; 
}

我还没有试过,但它应该工作。 这有一个优点你写的是更多的内存效率。 其缺点是,它是一个相当复杂一点,使用命名管道和几个匿名函数。

这使得只有当压缩含量为> 1MB范围的意义。 如果你的拉链是小于,那么你可以做到这一点我已经展示了第一种方式,上面。


附录

你为什么不能依靠的价值bla匿名方法之内?

有两个关键点。 首先,foreach循环定义了一个名为可变bla ,这需要一个不同的值,每次通过循环。 似乎是显而易见的,但它是值得说明它明确。

其次,匿名方法被作为参数传递ZipFile.AddEntry()方法,它不会在foreach循环运行的时间运行。 事实上,匿名方法被一再呼吁,一旦为每个条目添加的,在时间ZipFile.Save() 如果你指的bla匿名方法中,它得到分配给最后一个值 bla ,因为那是价值bla持有当时ZipFile.Save()运行。

这是延迟执行引起的困难。

你想要的是一个每个不同的值bla从foreach循环是在匿名函数被调用的时间访问-后来,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返回一个方法-实际上是一个动作,这仅仅是一个类型化的方法。 通过每一次循环中,该行动的一个新实例被创建,并且它BLA引用不同的值。 这项行动不叫,直到时间ZipFile.Save()



Answer 2:

最后我用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;
}


Answer 3:

交钥匙说- SharpZipLib是多张文件和内存流相当不错。 只要你需要压缩,并将它们添加到压缩文件的foreach文件。 下面是例子:

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


Answer 4:

我会建议使用SharpZipLib来压缩成标准的zip文件。 这些文件放入到一个临时文件夹,并使用FastZip类,使拉链。



文章来源: MVC3 return multiple pdfs as a zip file