原代碼直接讀取文件到 byte 數組再 Response.BinaryWrite,存在以下問題:
Content-Length,進度條無法顯示。Response.End() 會拋出 ThreadAbortException,影響性能。public IActionResult Download(string fileName) {
var path = Path.Combine(_env.ContentRootPath, "Files", fileName);
if (!System.IO.File.Exists(path)) return NotFound();
return PhysicalFile(path, "application/octet-stream", fileName);
}
public ActionResult Download(string fileName) {
string path = Server.MapPath("~/Files/" + fileName);
if (!System.IO.File.Exists(path)) return HttpNotFound();
// 設置響應頭
Response.Clear();
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition",
"attachment; filename=" + Server.UrlEncode(fileName));
Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
// 分塊傳輸,避免大文件內存壓力
const int bufferSize = 1024 * 1024; // 1MB
byte[] buffer = new byte[bufferSize];
using (var fs = System.IO.File.OpenRead(path)) {
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) {
Response.OutputStream.Write(buffer, 0, bytesRead);
Response.Flush(); // 及時發送給客戶端
}
}
Response.SuppressContent = true; // 防止後續內容污染
HttpContext.ApplicationInstance.CompleteRequest(); // 替代 Response.End()
return new EmptyResult();
}
protected void Page_Load(object sender, EventArgs e) {
string fileName = Request.QueryString["f"];
if (string.IsNullOrEmpty(fileName)) return;
string path = Server.MapPath("~/Files/" + fileName);
if (!File.Exists(path)) { Response.StatusCode = 404; return; }
Response.Clear();
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition",
"attachment; filename=" + Server.UrlEncode(fileName));
Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
Response.TransmitFile(path); // 內部使用分塊傳輸,最高效
Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
Response.TransmitFile 或手動循環 Read/Write,避免大文件佔用內存。Server.UrlEncode 處理中文文件名,防止亂碼。CompleteRequest() 或 SuppressContent,防止線程中止異常。Task 包裝提升併發。fileName,防止目錄遍歷攻擊(../)。