3

複数のページを含む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で圧縮して1つのzipファイルとして返す方法がわかりません。または、dotnetzipやsharpzipなどのgzip以外のものを使用する必要がありますか?

前もって感謝します!

4

4 に答える 4

11

ソリューションが機能する場合、最も簡単な方法は、そのままにしておくことです。

一方、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自体の問題ではなく、ストリームの操作に関するあなたの側の誤解にすぎません。

OK、次に、一時バッファ(メモリストリーム)を不必要に割り当てています。MemoryStreamは、Write()、Read()、Seek()などをサポートするためのStreamラッパーを含む単なるバイト配列と考えてください。基本的に、コードはその一時バッファーにデータを書き込み、圧縮のために一時バッファーから独自のバッファーにデータを読み取るように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; 
}

FileStreamResult最後に、私はあなたがからを作成するのは特に好きではありませんMemoryStream。問題は、zipファイル全体がメモリに保持されていることです。これはメモリ使用量に非常に苦労する可能性があります。zipファイルが大きい場合、コードはすべてのコンテンツをメモリに保持します。

私はMVC3モデルについて、これを助ける何かがあるかどうかを知るのに十分なことを知りません。存在しない場合は、無名パイプを使用してストリームの方向を反転し、すべての圧縮データをメモリに保持する必要をなくすことができます。

つまり、を作成するにFileStreamResultは、読み取り可能なストリームを提供する必要があります。MemoryStreamを使用する場合、読み取り可能にするために、最初に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を超える範囲にある場合にのみ意味があります。zipがそれよりも小さい場合は、上記の最初の方法で実行できます。


補遺

bla匿名メソッド内の値に依存できないのはなぜですか?

2つの重要なポイントがあります。まず、foreachループは、blaループを通過するたびに異なる値をとる、という名前の変数を定義します。当たり前のようですが、明示的に述べる価値があります。

次に、匿名メソッドが引数としてメソッドに渡されZipFile.AddEntry()ており、foreachループの実行時には実行されません。実際、anonymousメソッドは、追加されたエントリごとに1回、の時点で繰り返し呼び出され ZipFile.Save()ます。bla匿名メソッド内で参照する場合、実行時に保持される値であるため、に割り当てられた最後の値を取得します。blablaZipFile.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()

于 2012-06-05T03:20:50.460 に答える
3

ソリューションが単純なため、SharpZipLibの代わりにDotNetZipを使用することになりました。これが私がやったことです、それはうまくいきます、しかし誰かが何かアドバイス/変更を持っているならば、私はここで彼らに喜んでいるでしょう。

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;
}
于 2012-06-03T13:17:54.903 に答える
2

ターンキーが言ったように-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();

情報を圧縮する方法がわからないので、ファイルは大丈夫だと思います:)

    /// <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]);
        }
    }
于 2012-06-02T17:51:42.323 に答える
1

SharpZipLibを使用して標準のzipファイルに圧縮することをお勧めします。ファイルを一時フォルダーに入れ、FastZipクラスを使用してzipを作成します。

于 2012-06-02T13:54:52.420 に答える