iT邦幫忙

2

[C#] ASP.NET 檔案下載(2) - 大型檔案下載

[C#] ASP.NET 檔案下載(1) - POST 和 GET 觸發檔案下載
[C#] ASP.NET 檔案下載(2) - 大型檔案下載
[C#] ASP.NET 檔案下載(3) - 檔案續傳

上一篇最後寫到,後端的寫法不適合大型檔案下載,因為這種寫法會一次配置整個檔案所需的記憶體空間,並且檔案完全載入後才會輸出到瀏覽器,這時如果檔案大小超過了 ASP.NET 可用的記憶體上限,就會出現錯誤造成檔案下載失敗。

當然要來證實一下XD,我先產生一個1GB的空白檔案,然後下載看看。
產生空白檔案的方法,開啟 cmd 輸入:
fsutil file createnew test.txt 1073741824

fsutil file createnew 檔案名稱 檔案大小 (單位為byte)

1KB = 1 x 1024 = 1024
1MB = 1 x 1024 x 1024 = 1048576
1GB = 1 x 1024 x 1024 x 1024 = 1073741824

https://ithelp.ithome.com.tw/upload/images/20171014/2010686511MGbBOfIK.jpg
真的掛掉了XD。
可以這樣改善,不要一次配置整個檔案所需的記憶體空間,而是只配置一小塊緩衝區,每次讀取檔案只讀取一部份到緩衝區,然後馬上輸出到瀏覽器,這樣就可以用很少的記憶體完成整個檔案讀取輸出的動作,程式碼如下:

public class Download : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        var index = context.Request.Params["index"];

        var fileName = "test.txt";

        //取得檔案在Server上的實體路徑
        var filePath = context.Server.MapPath("~/File/" + fileName);

        //緩衝區大小,每次讀取100KB
        var bufferSize = 102400;
        var buffer = new byte[bufferSize];
        var fs = new FileStream(filePath,
            FileMode.Open, FileAccess.Read);
        //輸出檔案的位元組總長度
        var outputLength = fs.Length;
        //每次讀取的位元組長度
        var readLength = 0;

        context.Response.Clear();
        context.Response.AddHeader(
            "Content-Length", outputLength.ToString());
        context.Response.ContentType = "application/octet-stream";
        context.Response.AddHeader(
            "content-disposition",
            "attachment; filename=" + fileName);

        //剩餘位元組長度大於零,且與瀏覽器連接著,就繼續執行
        while (outputLength > 0 && context.Response.IsClientConnected)
        {
            readLength = fs.Read(buffer, 0, bufferSize);
            context.Response.OutputStream.Write(buffer, 0, readLength);
            context.Response.Flush();
            outputLength = outputLength - readLength;
        }

        fs.Close();
        context.Response.End();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

結果:
https://ithelp.ithome.com.tw/upload/images/20171014/20106865hvaodQzoqF.jpg
大型檔案下載成功。


1 則留言

0
神Q超人
iT邦新手 5 級 ‧ 2017-10-16 23:01:38

其實我滿好奇的,我現在做檔案下載都是直接用放href耶
這兩種方式有差別嗎?

是指程式讀取和href放檔案路徑嗎?
如果沒有特殊需求,我會用href,因為IIS會處理好其他細節,就不用自己手刻XD
但如果有特殊需求,例如: 檔案要加權限、檔案存在資料庫、檔案是動態產生(PDF報表)、等等...,就會需要程式控制。
以上是我的淺見XD

我要留言

立即登入留言