.Net Web API 006 Controller上传大文件
上传大文件就需要一段一段的上传,主要是先在客户端获取文件的大小,例如想一次传256kb,那就按照256kb分割。分割后又两种上传方式。
(1)逐个数据段读取,然后调用API上传,把数据追加到文件上。上传完这一段,接着传下一段,直到上传完毕。
(2)方式与(1)类似,只是可以好几段可以并行上传,上传后,会把每段按照索引命名,在服务器上保存成临时文件。判断都上传完毕后,再调用合并命令,把这些小的碎文件,合并成大的目标文件。
我一般喜欢用第一种方式,主要是因为简单明了。缺点就是,因为必须等上一段完成后,再传下一段,所以无法并行上传,导致速度会受到影响。
但我参与的项目基本上都是在局域网运行并且使用人数有限,所以这种传大文件的方式是可以满足系统要求。
下面的实现用到了定义的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代码没有实际测试,只是一个思路。