.Net Web API 006 Controller上傳大文件

1、上傳大文件的方式

上傳大文件就需要一段一段的上傳,主要是先在客戶端獲取文件的大小,例如想一次傳256kb,那就按照256kb分割。分割後又兩種上傳方式。

(1)逐個數據段讀取,然後調用API上傳,把數據追加到文件上。上傳完這一段,接着傳下一段,直到上傳完畢。

(2)方式與(1)類似,只是可以好幾段可以並行上傳,上傳後,會把每段按照索引命名,在服務器上保存成臨時文件。判斷都上傳完畢後,再調用合併命令,把這些小的碎文件,合併成大的目標文件。

我一般喜歡用第一種方式,主要是因爲簡單明瞭。缺點就是,因爲必須等上一段完成後,再傳下一段,所以無法並行上傳,導致速度會受到影響。

但我參與的項目基本上都是在局域網運行並且使用人數有限,所以這種傳大文件的方式是可以滿足系統要求。

2、上傳大文件API實現

下面的實現用到了定義的AttachedFileEntity和HttpClientEx,這兩個類的定義可參考 005 Controller上傳小文件

分三步,開始上傳、循環上傳二進制段、結束上傳。

開始上傳API的目的是爲了獲取文件在服務器上的存儲路徑,代碼如下。

/// <summary>
/// 開始大上傳文件
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("StartUploadBigFile")]
public IActionResult StartUploadBigFile(AttachedFileEntity pEntity, string pFileEx)
{
    string myServerFilePath = DateTime.Now.ToString("yyyy_MM_dd") + "\\" + Guid.NewGuid().ToString() + pFileEx;
    pEntity.ServerPath = myServerFilePath;
    return this.Ok(pEntity);
}

分段上傳文件,接收到後,追加到現有的文件上。

/// <summary>
/// 上傳大文件
/// </summary>
/// <param name="pServerPath"></param>
/// <returns></returns>
[HttpPost]
[Route("UploadBigFile")]
[DisableRequestSizeLimit]
public IActionResult UploadBigFile(string pServerPath)
{
    var myFile = Request.Form.Files[0];

    //創建目錄
    string myFullServerPath = AppDomain.CurrentDomain.BaseDirectory + "\\Files\\" + pServerPath;
    string myFullFolder = Path.GetDirectoryName(myFullServerPath)!;
    if (Directory.Exists(myFullFolder) == false)
    {
        Directory.CreateDirectory(myFullFolder);
    }

    //寫入文件
    Stream? myStream = null;
    FileStream? myFileStream = null;
    BinaryWriter? myBinaryWriter = null;
    try
    {
        myStream = myFile.OpenReadStream();
        byte[] myBytes = new byte[myStream.Length];
        myStream.Read(myBytes, 0, myBytes.Length);
        myStream.Seek(0, SeekOrigin.Begin);

        myFileStream = new FileStream(myFullServerPath, FileMode.Append);
        myBinaryWriter = new BinaryWriter(myFileStream);
        myBinaryWriter.Write(myBytes);
    }
    catch (Exception ex)
    {
        return this.BadRequest("上傳大文件失敗," + ex.Message);
    }
    finally
    {
        myBinaryWriter?.Close();
        myFileStream?.Close();
        myStream?.Close();
    }

    return this.Ok();
}

 

最後,結束上傳,把文件的信息記錄到數據庫中。

/// <summary>
/// 結束上傳大文件
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("FinishUploadBigFile")]
public IActionResult FinishUploadBigFile(AttachedFileEntity pEntity)
{
    if (string.IsNullOrEmpty(pEntity.GUID))
    {
        pEntity.GUID = Guid.NewGuid().ToString();
    }
    //記錄到數據庫中
    //代碼略
    return this.Ok(pEntity);
}

3、客戶端調用

客戶端如果用的C#,代碼中沒有加入進度信息,進度信息可以傳入一個ProcessInfo對象,傳一段數據後,就更新下進度信息。

調用的代碼入下所示。

private void Init_BigFileUpLoad_UIs()
{
    this.UI_BigFile_Button.Click += (x, y) =>
    {
        var myOpenFileDialog = new OpenFileDialog
        {
            Filter = ".*|*.*"
        };
        var myIsOK = myOpenFileDialog.ShowDialog();
        if (myIsOK != true)
        {
            return;
        }
        this.UI_BigFile_TextBox.Text = myOpenFileDialog.FileName;
    };

    this.UI_BigFileUpLoad_Button.Click += async (x, y) =>
    {
        var myFilePath = this.UI_BigFile_TextBox.Text.Trim();
        if (myFilePath.Length == 0)
        {
            MessageBox.Show("請選擇一個文件。");
            return;
        }
        if (File.Exists(myFilePath) == false)
        {
            MessageBox.Show("文件不存在,請重新選擇。");
            return;
        }

        //定義AttachedFileEntity
        var myFileEntity = new AttachedFileEntity()
        {
            GUID = Guid.NewGuid().ToString(),
            Name = "用戶頭像",
            KeyWord = "UserProfilePhoto",
            Description = "",
            EntityGUID = "AAAA"
        };

        //打開上傳的文件
        var myFileStream = new FileStream(myFilePath, FileMode.Open);
        myFileEntity.FileSize = (int)myFileStream.Length;
        var myFileName = Path.GetFileName(myFilePath);

        //每次上傳256kb
        int myChunkSize = 1024 * 256;
        int myChunkCount = (int)Math.Ceiling(myFileStream.Length / (double)myChunkSize);

        //調用開始上傳
        var myHttpClient = new HttpClient();
        var myHttpClientEx = new HttpClientEx(myHttpClient)
        {
            Url = "http://localhost:5000/api/AttachedFile/StartUploadBigFile",
            HttpContent = JsonContent.Create(myFileEntity)
        };
        myHttpClientEx.ParameterDictionary.Add("pFileEx", Path.GetExtension(myFileName));
        await myHttpClientEx.PostAsync();
        if (myHttpClientEx.IsSuccess == false)
        {
            myFileStream.Close();
            MessageBox.Show("上傳文件失敗," + myHttpClientEx.ResponseContenString);
            return;
        }
        myFileEntity = myHttpClientEx.GetResponseObject<AttachedFileEntity>();
        if (myFileEntity == null)
        {
            myFileStream.Close();
            MessageBox.Show("上傳文件失敗,返回的AttachedFileEntity爲null。");
            return;
        }

        //循環上傳文件
        for (int i = 0; i < myChunkCount; i++)
        {
            //組織數據
            int myByteArraySize = myChunkSize;
            if (i == myChunkCount - 1)
            {
                myByteArraySize = (int)(myFileStream.Length % myChunkSize);
            }
            byte[] myBytes = new byte[myByteArraySize];
            myFileStream.Position = myChunkSize * i;
            myFileStream.Read(myBytes, 0, myBytes.Length);
            var myMemoryStream = new MemoryStream(myBytes);

            //請求服務
            myHttpClientEx = new HttpClientEx(myHttpClient)
            {
                Url = "http://localhost:5000/api/AttachedFile/UploadBigFile",
                HttpContent = new MultipartFormDataContent
                    {
                        {new StreamContent(myMemoryStream),"pFile",myFileName}
                    }
            };
            myHttpClientEx.ParameterDictionary.Add("pServerPath", myFileEntity!.ServerPath);
            await myHttpClientEx.PostAsync();

            //解析結果
            if (myHttpClientEx.IsSuccess == false)
            {
                myFileStream.Close();
                MessageBox.Show("上傳文件失敗," + myHttpClientEx.ResponseContenString);
                return;
            }
        }

        //結束上傳
        myHttpClientEx = new HttpClientEx(myHttpClient)
        {
            Url = "http://localhost:5000/api/AttachedFile/FinishUploadBigFile",
            HttpContent = JsonContent.Create<AttachedFileEntity>(myFileEntity)
        };
        await myHttpClientEx.PostAsync();
        if (myHttpClientEx.IsSuccess == false)
        {
            myFileStream.Close();
            MessageBox.Show("上傳文件失敗," + myHttpClientEx.ResponseContenString);
            return;
        }
        myFileStream.Close();
        myFileEntity = myHttpClientEx.GetResponseObject<AttachedFileEntity>();
        var myEntityJosnString = JsonSerializer.Serialize<AttachedFileEntity>(myFileEntity);
        MessageBox.Show(myEntityJosnString);
    };
}

如果客戶端是js,代碼如下。

on(myButton, "change", function (e) {
    var myFileReader = new FileReader();
    var myFileName = "";

    myFileReader.onloadend = function () {
        var myFileResult = myFileReader.result;
        var myFileLength = myFileResult.byteLength;

        var myPerLength = 1024 * 256;
        var myCount = Math.ceil(myFileLength / myPerLength);

        var myFileEntity = new Object()
        {
            ServerPath: ""
        };

        //調用開始上傳StartUploadBigFile,具體代碼略。

        var myK = 0;
        Upload();

        function Upload() {

            var myByteArray = myFileResult.slice(myPerLength * myK, myPerLength * (myK + 1));
            var myBlob = new Blob([myByteArray]);
            var myFile = new File([myBlob], myFileName);
            var myFormData = new FormData();
            myFormData.append("file", myFile)
            request.post(myUrl + "?pServerFile=" + myFileEntity.ServerPath +, {
                data: myFormData
            }).then(function (data) {
                myFileEntity = json.parse(data);
                myK++;
                if (myK < myCount) {
                    Upload();
                }
                else {
                    alert("上傳大文件結束。");
                    alert(json.stringify(myFileEntity));
                    //結束,post FinishUploadBigFile 
                }

            }, function (err) {
                alert(err);
                return;
            });
        }
    }

    myFileName = this.files[0].name;
    myFileReader.readAsArrayBuffer(this.files[0]);
});

Js代碼沒有實際測試,只是一個思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章