c# – MVC3将多个pdfs作为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()重载,它接受一个writer委托.委托是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,为了使它可读,你需要首先写入它,然后在将它传递给FileStreamResult构造函数之前回到位置0.这意味着该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的最后一个值,因为那样
是ZipFile.Save()运行时bla的值.

这是导致困难的延迟执行.

你想要的是来自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()时才调用该Action.

上一篇:使用自定义字体的MVCRazorToPdf(iTextSharp)


下一篇:itextsharp使用ukey进行签章