ソリューションが機能する場合、最も簡単な方法は、そのままにしておくことです。
一方、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
匿名メソッド内で参照する場合、実行時に保持される値であるため、に割り当てられた最後の値を取得します。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()
。