MVC3返回多个PDF作为一个zip文件 [英] MVC3 return multiple pdfs as a zip file

查看:195
本文介绍了MVC3返回多个PDF作为一个zip文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个返回PDF(使用iTextSharp的)具有多页的观点,但我现在必须改变它,这样每个页面都是一个单独的PDF(与它自己独特的标题),并返回一个zip文件。

I have a view that returns a pdf (using iTextSharp) with multiple pages, but now I have to change it so that each page is a separate pdf (with it's own unique title) and return a zip file.

我原来的code是这样的:

My original code looks like this:

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

看起来pretty简单的COM preSS用gzip的文件,但我不知道如何gzip压缩多个文件,并返回它作为一个ZIP文件。或者我应该使用比gzip其他的东西,比如dotnetzip或sharpzip?

It looks pretty simple to compress a file with gzip, but I don't know how to gzip multiple files and return it as one zip file. Or should I use something other than gzip, like dotnetzip or sharpzip?

在此先感谢!

推荐答案

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

If your solution works then the easiest thing to do is to just keep it, as is.

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

On the other hand I do have some comments about your usage of the DoTNetZip library.

首先,你的code是有点误导。在本节中:

First, your code is sort of misguided. In this section:

byte[] byteInfo = workStream.ToArray();                        

zip.Save(workStream);                        

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

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

...you are reading the workStream into an array. But at that point, you haven't written anything to workStream, so the array is empty, zero-length. Then you save the zip into the workstream. Then you write the array (of zero length) into the same workstream. This is a NO-OP. Finally you reset the position.

您可以用替换所有的是:

You could replace all of that with :

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

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

This isn't an issue with DotNetZip per se, it's just an mis-understanding on your part regarding the operation of streams.

确定,下一步,你是分配临时缓冲区(memorystreams)不必要的。想想一个MemoryStream刚才字节数组,上面有一个流包装的,支持写(),读(),SEEK(),等等。基本上你的code将数据写入到该临时缓​​冲区,然后告诉DotNetZip从临时缓冲区中的数据读入它自己的缓冲区为COM pression。你并不需要一个临时的缓冲区。它的工作原理,你已经做的方式,但它可能是更有效的。

OK, Next, you are allocating temporary buffers (memorystreams) unnecessarily. Think of a MemoryStream as just an array of bytes, with a Stream wrapper on it, to support Write(), Read(), Seek(), and so on. Essentially your code is writing data into that temporary buffer, then telling DotNetZip to read the data from the temp buffer into its own buffer for compression. You don't need that interim buffer. It works the way you've done it, but it could be more efficient.

DotNetZip具有接受一个作家代表一个的AddEntry()超载。该委托是DotNetZip来电告知您的应用程序编写条目内容到zip归档功能。您code写uncom pressed字节,DotNetZip COM presses,并将其写入到输出流。

DotNetZip has an AddEntry() overload that accepts a writer delegate. The delegate is a function that DotNetZip calls to tell your app to write the entry content into the zip archive. Your code writes uncompressed bytes, and DotNetZip compresses and writes them to the output stream.

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

In that writer delegate, your code writes directly into the DotNetZip stream - the stream that is passed into the delegate by DotNetZip. There's no intervening buffer. Nice for efficiency.

请闭包的规则。如果调用此笔者代表在for循环中,你需要有检索喇嘛所对应的委托范围内的ZipEntry的一种方式。该委托没有得到,直到 zip.Save()执行被称为!所以你不能依靠喇嘛从循环的价值。

Keep in mind the rules about closures. If you call this writer delegate in a for loop, you need to have a way of retrieving the "bla" corresponding to the zipentry within the delegate. The delegate does not get executed until zip.Save() is called! So you cannot rely on the value of 'bla' from the loop.

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 A FileStreamResult 的创建。问题是你的整个zip文件保存在内存中,它可以是很辛苦的内存使用情况。如果你的zip文件很大,你的code将保留所有内存中的内容。

Finally, I don't particularly like your creation of a FileStreamResult from a MemoryStream. The problem is your entire zip file is kept in memory, which can be very hard on memory usage. If your zip files are large, your code will retain all of the content in memory.

我不充分了解MVC3模型知道有东西在它与这有助于。如果没有,你可以使用一个匿名管道反转流的方向,消除需要容纳所有在COM $ p $内存pssed数据。

I don't know enough about the MVC3 model to know if there is something in it that helps with this. If there is not, you can use an Anonymous Pipe to invert the direction of the streams, and eliminate the need to hold all the compressed data in memory.

这就是我的意思是:创建一个 FileStreamResult 要求您提供一个可读的流。如果你使用一个MemoryStream,以使其可读的,你需要写它,然后再寻求回到位置0,将它传递给 FileStreamResult 构造函数之前。这意味着所有的该压缩文件中的内容必须在存储器中连续在某个时间点进行。

Here's what I mean: creating a FileStreamResult requires that you provide a readable stream. If you use a MemoryStream, in order to make it readable, you need to write to it first, then seek back to position 0, before passing it to the FileStreamResult constructor. This means all the content for that zip file has to be held in memory contiguously at some point in time.

假设你可以提供一个可读的流的 FileStreamResult 的构造,这将让读者在正是你写的那一刻读取。这是一个匿名管道流做了什么。它可以让你的code使用可写流,而MVC code得到它的可读性流。

Suppose you could provide a readable stream to the FileStreamResult constructor, that would allow the reader to read at exactly the moment you wrote to it. This is what an anonymous pipe stream does. It allows your code to use a writeable stream, while the MVC code gets its readable stream.

下面是它看起来像在code。

Here's what it would look like in code.

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

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

I haven't tried this but it ought to work. This has an advantage over what you wrote, of being more memory efficient. The disadvantage is that it is quite a bit more complex, using named pipes and several anonymous functions.

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

This makes sense only if the zip content is into the >1MB range. If your zips are smaller than that, then you can just do it the first way I've shown, above.

附录

你为什么不能依靠匿名方法中的喇嘛的价值?

Why can you not rely on the value of bla within the anonymous method?

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

There are two key points. First, the foreach loop defines a variable named bla, which takes a different value, each time through the loop. Seems obvious but it's worth stating it explicitly.

二,匿名方法被作为的参数的传递到
ZipFile.AddEntry()方法,它不会在当时运行
foreach循环运行。事实上匿名方法被调用
反反复复,一次为每个条目添加的,在时间
ZipFile.Save()。如果您参考喇嘛中的匿名
方法,它得到的的最后一个值的分配给喇嘛,因为这
喇嘛保存当时的价值 ZipFile.Save()运行。

Second, the anonymous method is being passed as an argument to the ZipFile.AddEntry() method, and it won't run at the time the foreach loop runs. In fact the anonymous method gets called repeatedly, once for each entry added, at the time of ZipFile.Save(). If you refer to bla within the anonymous method, it gets the last value assigned to bla, because that is the value bla holds at the time ZipFile.Save() runs.

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

It's the deferred execution that causes the difficulty.

您需要的是从foreach循环喇嘛的每个不同的值是
访问当时的匿名函数被调用 - 后,foreach循环之外。您
可以用一个实用方法做到这一点( GetBlaForName()),就像我上面显示。您可以
也做一个额外的关闭,像这样:

What you want is each distinct value of bla from the foreach loop to be accessible at the time the anonymous function is invoked - later, outside the foreach loop. You could do this with a utility method (GetBlaForName()), like I showed above. You can also do this with an additional closure, like so:

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的时间()

The GetEntryWriter returns a method - actually an Action, which is just a typed method. Each time through the loop, a new instance of that Action is created, and it references a different value for bla. That Action is not called until the time of ZipFile.Save().

这篇关于MVC3返回多个PDF作为一个zip文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆