メモリサイズが42 MBの大きなファイルがあります。少ないメモリ消費量でファイルをダウンロードしたい。
コントローラーコード
public ActionResult Download()
{
var filePath = "file path in server";
FileInfo file = new FileInfo(filePath);
Response.ContentType = "application/Zip";
Response.AppendHeader("Content-Disposition", "attachment; filename=folder.Zip");
Response.TransmitFile(file.FullName);
Response.End();
}
Streamで試した代替方法
public ActionResult Download()
{
string failure = string.Empty;
Stream stream = null;
int bytesToRead = 10000;
long LengthToRead;
try
{
var path = "file path from server";
FileWebRequest fileRequest = (FileWebRequest)FileWebRequest.Create(path);
FileWebResponse fileResponse = (FileWebResponse)fileRequest.GetResponse();
if (fileRequest.ContentLength > 0)
fileResponse.ContentLength = fileRequest.ContentLength;
//Get the Stream returned from the response
stream = fileResponse.GetResponseStream();
LengthToRead = stream.Length;
//Indicate the type of data being sent
Response.ContentType = "application/octet-stream";
//Name the file
Response.AddHeader("Content-Disposition", "attachment; filename=SolutionWizardDesktopClient.Zip");
Response.AddHeader("Content-Length", fileResponse.ContentLength.ToString());
int length;
do
{
// Verify that the client is connected.
if (Response.IsClientConnected)
{
byte[] buffer = new Byte[bytesToRead];
// Read data into the buffer.
length = stream.Read(buffer, 0, bytesToRead);
// and write it out to the response's output stream
Response.OutputStream.Write(buffer, 0, length);
// Flush the data
Response.Flush();
//Clear the buffer
LengthToRead = LengthToRead - length;
}
else
{
// cancel the download if client has disconnected
LengthToRead = -1;
}
} while (LengthToRead > 0); //Repeat until no data is read
}
finally
{
if (stream != null)
{
//Close the input stream
stream.Close();
}
Response.End();
Response.Close();
}
return View("Failed");
}
ファイルのサイズのため、パフォーマンスの問題につながるより多くのメモリを消費しています。
iisログをチェックした後、ダウンロードプロセスはそれぞれ42 mbと64 mbを使用しています。
前もって感謝します
より良いオプションは、ActionResultの代わりにFileResultを使用することです:
この方法を使用すると、提供する前にファイル/バイトをメモリにロードする必要がありません。
public FileResult Download()
{
var filePath = "file path in server";
return new FilePathResult(Server.MapPath(filePath), "application/Zip");
}
編集:ファイルが大きい場合、FilePathResultも失敗します。
あなたの最善の策はおそらくResponse.TransmitFile()です。これを大きなファイル(GB)で使用したことがあり、以前は問題がなかった
public ActionResult Download()
{
var filePath = @"file path from server";
Response.Clear();
Response.ContentType = "application/octet-stream";
Response.AppendHeader("Content-Disposition", "filename=" + filePath);
Response.TransmitFile(filePath);
Response.End();
return Index();
}
MSDNから:
メモリにバッファリングせずに、指定したファイルを直接HTTP応答出力ストリームに書き込みます。
同様の問題がありましたが、ローカルディスクにファイルがなく、APIからダウンロードする必要がありました(私のMVCはプロキシのようなものでした)。重要なことは、MVCアクションにResponse.Buffer=false;
を設定することです。 @JanusPienaarの最初のソリューションはこれで動作するはずです。私のMVCアクションは:
public class HomeController : Controller
{
public async Task<FileStreamResult> Streaming(long RecordCount)
{
HttpClient Client;
System.IO.Stream Stream;
//This is the key thing
Response.Buffer=false;
Client = new HttpClient() { BaseAddress=new Uri("http://MyApi", };
Stream = await Client.GetStreamAsync("api/Streaming?RecordCount="+RecordCount);
return new FileStreamResult(Stream, "text/csv");
}
}
そして、私のテストWebApi(ファイルを生成します)は次のとおりです。
public class StreamingController : ApiController
{
// GET: api/Streaming/5
public HttpResponseMessage Get(long RecordCount)
{
var response = Request.CreateResponse();
response.Content=new PushStreamContent((stream, http, transport) =>
{
RecordsGenerator Generator = new RecordsGenerator();
long i;
using(var writer = new System.IO.StreamWriter(stream, System.Text.Encoding.UTF8))
{
for(i=0; i<RecordCount; i++)
{
writer.Write(Generator.GetRecordString(i));
if(0==(i&0xFFFFF))
System.Diagnostics.Debug.WriteLine($"Record no: {i:N0}");
}
}
});
return response;
}
class RecordsGenerator
{
const string abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char[] Chars = new char[14];//Ceiling(log26(2^63))
public string GetRecordString(long Record)
{
int iLength = 0;
long Div = Record, Mod;
do
{
iLength++;
Div=Math.DivRem(Div, abc.Length, out Mod);
//Save from backwards
Chars[Chars.Length-iLength]=abc[(int)Mod];
}
while(Div!=0);
return $"{Record} {new string(Chars, Chars.Length-iLength, iLength)}\r\n";
}
}
}
}
RecordCountが100000000の場合、TestApiによって生成されるファイルは1.56 GBです。 WebApiもMVCもそれほど多くのメモリを消費しません。
Transfer-Encodingヘッダーをチャンクに設定し、PushStreamContentを使用してHttpResponseMessageを返します。チャンクのTransfer-Encodingは、HTTP応答にContent-Lengthヘッダーがないため、クライアントはHTTP応答のチャンクをストリームとして解析する必要があることを意味します。注、チャンクされた転送エンコーディングを処理しなかったクライアント(ブラウザなど)に出くわしたことはありません。詳しくは、以下のリンクをご覧ください。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding
[HttpGet]
public async Task<HttpResponseMessage> Download(CancellationToken token)
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new PushStreamContent(async (stream, context, transportContext) =>
{
try
{
using (var fileStream = System.IO.File.OpenRead("some path to MyBigDownload.Zip"))
{
await fileStream.CopyToAsync(stream);
}
}
finally
{
stream.Close();
}
}, "application/octet-stream"),
};
response.Headers.TransferEncodingChunked = true;
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "MyBigDownload.Zip"
};
return response;
}
私のために働いた Rizwan Ansari の投稿があります:
サーバー上のどこかにある、または実行時に生成される大きなファイルのダウンロードオプションを提供する必要がある場合があります。以下の機能を使用して、任意のサイズのファイルをダウンロードできます。大きなファイルをダウンロードすると、「プログラムの実行を続行するにはメモリが不足しています」という例外OutOfMemoryExceptionがスローされる場合があります。したがって、この関数は、ファイルを1 MBのチャンクに分割することによってもこの状況を処理します(bufferSize変数を変更してカスタマイズできます)。
使用法:
DownloadLargeFile("A big file.pdf", "D:\\Big Files\\Big File.pdf", "application/pdf", System.Web.HttpContext.Current.Response);
右で「application/pdf」を変更できます MIMEタイプ
ダウンロード機能:
public static void DownloadLargeFile(string DownloadFileName, string FilePath, string ContentType, HttpResponse response)
{
Stream stream = null;
// read buffer in 1 MB chunks
// change this if you want a different buffer size
int bufferSize = 1048576;
byte[] buffer = new Byte[bufferSize];
// buffer read length
int length;
// Total length of file
long lengthToRead;
try
{
// Open the file in read only mode
stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
// Total length of file
lengthToRead = stream.Length;
response.ContentType = ContentType;
response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(DownloadFileName, System.Text.Encoding.UTF8));
while (lengthToRead > 0)
{
// Verify that the client is connected.
if (response.IsClientConnected)
{
// Read the data in buffer
length = stream.Read(buffer, 0, bufferSize);
// Write the data to output stream.
response.OutputStream.Write(buffer, 0, length);
// Flush the data
response.Flush();
//buffer = new Byte[10000];
lengthToRead = lengthToRead - length;
}
else
{
// if user disconnects stop the loop
lengthToRead = -1;
}
}
}
catch (Exception exp)
{
// handle exception
response.ContentType = "text/html";
response.Write("Error : " + exp.Message);
}
finally
{
if (stream != null)
{
stream.Close();
}
response.End();
response.Close();
}
}
IISを使用してHTTPダウンロードを有効にする必要があります。これを見てください link
そして、ダウンロードするファイルのHTTPパスをすばやく簡単に返すだけです。