本章和大家分享的是.NetCore的MVC框架上傳檔案的示例,主要講的內容有:form方式提交上傳,ajax上傳,ajax提交+上傳進度效果,Task並行處理+ajax提交+上傳進度,相信當你讀完文章內容後能後好的收穫,如果可以不妨點個贊;由於昨天電腦沒電了,快要寫完的內容沒有儲存,今天早上提前來公司從頭開始重新,斷電這情況的確讓人很頭痛啊,不過為了社群的分享環境,這也是值得的,不多說了來進入今天的正篇環節吧;
form方式上傳一組圖片
先來看看咋們html的程式碼,這裡先簡單說下要上傳檔案必須要設定form元素裡面的 enctype="multipart/form-data" 屬性和post方式,如果你想要多選上傳檔案的話,需要把檔案type='file'元素設定她的屬性multiple='multiple',因此就有了如下內容:
1 <form class="form-horizontal" action="/Home/FileUp" method="post" enctype="multipart/form-data"> 2 3 <input type="file" name="MyPhoto" class="form-control" multiple /> 4 <br /> 5 <button class="btn btn-default">form上傳</button> 6 <br /> 7 <span style="color:red">@ViewData["MsgBox"]</span> 8 </form>
由於採用form提交,這個測試用例只接用了button元素預設的type=submit來提交表單,對應的後臺Action中程式碼如下:
1 /// <summary> 2 /// form提交上傳 3 /// </summary> 4 /// <param name="user"></param> 5 /// <returns></returns> 6 [HttpPost] 7 public async Task<IActionResult> FileUp(MoUser user) 8 { 9 if (user.MyPhoto == null || user.MyPhoto.Count <= 0) { MsgBox("請上傳圖片。"); return View(); } 10 //var file = Request.Form.Files; 11 foreach (var file in user.MyPhoto) 12 { 13 var fileName = file.FileName; 14 var contentType = file.ContentType; 15 var len = file.Length; 16 17 var fileType = new string[] { "image/jpeg", "image/png" }; 18 if (!fileType.Any(b => b.Contains(contentType))) { MsgBox($"只能上傳{string.Join(",", fileType)}格式的圖片。"); return View(); } 19 20 if (len > 1024 * 1024 * 4) { MsgBox("上傳圖片大小隻能在4M以下。"); return View(); } 21 22 var path = Path.Combine(@"D:\F\學習\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName); 23 using (var stream = System.IO.File.Create(path)) 24 { 25 await file.CopyToAsync(stream); 26 } 27 } 28 MsgBox($"上傳成功"); 29 30 return View(); 31 }
從前端到後端的Action不得不說這種form表單提交的方式挺簡單的,需要注意的是Action這裡用的實體模型方式來對應上傳的檔案資訊,這裡自定義了MoUser類,通過屬性 public List<IFormFile> MyPhoto { get; set; } 來匹配html表單中檔案type='file'的name屬性名稱name="MyPhoto":
1 public class MoUser 2 { 3 public int UserId { get; set; } = 1; 4 public string UserName { get; set; } = "神牛步行3"; 5 6 public List<IFormFile> MyPhoto { get; set; } 7 }
這樣就能通過實體模型的方式把上傳的檔案資訊儲存在自定義MoUser類中的MyPhoto屬性中了;
ajax上傳一組圖片
這裡需要在上面例子中的html處修改一些東西,不再使用form提交,指定了普通button按鈕來觸發ajax的提交,完整html程式碼如:
1 <form class="form-horizontal" id="form01" method="post" enctype="multipart/form-data"> 2 3 <input type="file" name="MyPhoto01" class="form-control" multiple /> 4 <br /> 5 <button type="button" id="btnAjax" class="btn btn-default">ajax上傳</button> 6 <br /> 7 <span style="color:red" id="span01"></span> 8 </form>
有了佈局,再來看看具體的js實現程式碼,這裡我採用jquery的ajax提交的方法來操作,也用到了html5新增的FormData來儲存表單的資料:
1 $("#btnAjax").on("click", function () { 2 var msg = $("#span01"); 3 var form = document.getElementById("form01"); 4 //console.log(form); 5 var data = new FormData(form); 6 7 $.ajax({ 8 type: "POST", 9 url: "/home/AjaxFileUp", 10 data: data, 11 12 contentType: false, 13 processData: false, 14 success: function (data) { 15 if (data) { 16 msg.html(data.msg); 17 } 18 }, 19 error: function () { 20 msg.html("上傳檔案異常,請稍後重試!"); 21 } 22 }); 23 });
至於後臺Action的方法和示例一的相差不大,關鍵點在於這裡我直接使用 Request.Form.Files 方式來獲取上傳的所有檔案,不再使用實體模型的方式了,這樣測試用例更多樣化吧:
1 /// <summary> 2 /// ajax無上傳進度效果上傳 3 /// </summary> 4 /// <returns></returns> 5 [HttpPost] 6 public async Task<JsonResult> AjaxFileUp() 7 { 8 var data = new MoData { Msg = "上傳失敗" }; 9 try 10 { 11 var files = Request.Form.Files.Where(b => b.Name == "MyPhoto01"); 12 //非空限制 13 if (files == null || files.Count() <= 0) { data.Msg = "請選擇上傳的檔案。"; return Json(data); } 14 15 //格式限制 16 var allowType = new string[] { "image/jpeg", "image/png" }; 17 if (files.Any(b => !allowType.Contains(b.ContentType))) 18 { 19 data.Msg = $"只能上傳{string.Join(",", allowType)}格式的檔案。"; 20 return Json(data); 21 } 22 23 //大小限制 24 if (files.Sum(b => b.Length) >= 1024 * 1024 * 4) 25 { 26 data.Msg = "上傳檔案的總大小隻能在4M以下。"; return Json(data); 27 } 28 29 //寫入伺服器磁碟 30 foreach (var file in files) 31 { 32 33 var fileName = file.FileName; 34 var path = Path.Combine(@"D:\F\學習\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName); 35 using (var stream = System.IO.File.Create(path)) 36 { 37 await file.CopyToAsync(stream); 38 } 39 } 40 data.Msg = "上傳成功"; 41 data.Status = 2; 42 43 } 44 catch (Exception ex) 45 { 46 data.Msg = ex.Message; 47 } 48 return Json(data); 49 }
如果你有耐心讀到這裡,那麼後面的內容個人感覺對你開發會有好的幫助,不負你期待;
ajax提交+上傳進度+一組圖片上傳
同樣我們先來看對應的html程式碼,其實和示例2幾乎一樣,只是把名稱變動了下:
1 <form class="form-horizontal" id="form02" method="post" enctype="multipart/form-data"> 2 3 <input type="file" name="MyPhoto02" class="form-control" multiple /> 4 <br /> 5 <button type="button" id="btnAjax02" class="btn btn-default">ajax上傳進度效果上傳</button> 6 <br /> 7 <span style="color:red" id="span02"></span> 8 </form>
要加一個進度效果,需要用到js的定時器,定時獲取上傳檔案的上傳進度資料資訊,因此這裡通過js的setInterval方法來定時請求一個進度資料介面,注意用完之後需要清除這個定時器,不然一直再不斷請求您介面:
1 $("#btnAjax02").on("click", function () { 2 3 var interBar; 4 var msg = $("#span02"); 5 msg.html("上傳中,請稍後..."); 6 var form = document.getElementById("form02"); 7 //console.log(form); 8 var data = new FormData(form); 9 10 $.ajax({ 11 type: "POST", 12 url: "/home/AjaxFileUp02", 13 data: data, 14 15 contentType: false, 16 processData: false, 17 success: function (data) { 18 if (data) { 19 msg.html(data.msg); 20 //清除進度查詢 21 if (interBar) { clearInterval(interBar); } 22 } 23 }, 24 error: function () { 25 msg.html("上傳檔案異常,請稍後重試!"); 26 if (interBar) { clearInterval(interBar); } 27 } 28 }); 29 30 //獲取進度 31 interBar = setInterval(function () { 32 33 $.post("/home/ProgresBar02", function (data) { 34 35 if (data) { 36 var isClearVal = true; 37 var strArr = []; 38 $.each(data, function (i, item) { 39 strArr.push('檔案:' + item.fileName + ",當前上傳:" + item.percentBar + '<br/>'); 40 if (item.status != 2) { isClearVal = false; } 41 }); 42 msg.html(strArr.join('')); 43 if (isClearVal) { 44 if (interBar) { clearInterval(interBar); } 45 } 46 } 47 }); 48 }, 200); 49 });
既然上面說到單獨的進度資料介面,那麼我們除了上傳Action外,也需要進度的Action,而這進度Action得到的上傳檔案資料資訊必須和上傳的Action一直,因此就需要用到快取等儲存資料的方式,這裡我用的是MemoryCache的方式,對已netcore來說僅僅只需要在起始檔案(如:Startup.cs)中新增元件服務:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 // Add framework services. 4 services.AddMvc(); 5 6 //新增cache支援 7 services.AddDistributedMemoryCache(); 8 }
然後通過建構函式注入到對應的介面Controller中去:
1 readonly IMemoryCache _cache; 2 3 public HomeController(IOptions<MoOptions> options, ILogger<HomeController> logger, IMemoryCache cache) 4 { 5 this._options = options.Value; 6 _logger = logger; 7 _cache = cache; 8 }
到此我們就能利用cache來儲存我們上傳進度資訊了,來看下處理上傳的Action:
1 private string cacheKey = "UserId_UpFile"; 2 private string cacheKey03 = "UserId_UpFile03"; 3 /// <summary> 4 /// ajax上傳進度效果上傳 5 /// </summary> 6 /// <returns></returns> 7 [HttpPost] 8 public async Task<JsonResult> AjaxFileUp02() 9 { 10 var data = new MoData { Msg = "上傳失敗" }; 11 try 12 { 13 var files = Request.Form.Files.Where(b => b.Name == "MyPhoto02"); 14 //非空限制 15 if (files == null || files.Count() <= 0) { data.Msg = "請選擇上傳的檔案。"; return Json(data); } 16 17 //格式限制 18 var allowType = new string[] { "image/jpeg", "image/png" }; 19 if (files.Any(b => !allowType.Contains(b.ContentType))) 20 { 21 data.Msg = $"只能上傳{string.Join(",", allowType)}格式的檔案。"; 22 return Json(data); 23 } 24 25 //大小限制 26 if (files.Sum(b => b.Length) >= 1024 * 1024 * 4) 27 { 28 data.Msg = "上傳檔案的總大小隻能在4M以下。"; return Json(data); 29 } 30 31 //初始化上傳多個檔案的Bar,儲存到快取中,方便獲取上傳進度 32 var listBar = new List<MoBar>(); 33 files.ToList().ForEach(b => 34 { 35 listBar.Add(new MoBar 36 { 37 FileName = b.FileName, 38 Status = 1, 39 CurrBar = 0, 40 TotalBar = b.Length 41 }); 42 }); 43 _cache.Set<List<MoBar>>(cacheKey, listBar); 44 45 //寫入伺服器磁碟 46 foreach (var file in files) 47 { 48 //總大小 49 var totalSize = file.Length; 50 //初始化每次讀取大小 51 var readSize = 1024L; 52 var bt = new byte[totalSize > readSize ? readSize : totalSize]; 53 //當前已經讀取的大小 54 var currentSize = 0L; 55 56 var fileName = file.FileName; 57 var path = Path.Combine(@"D:\F\學習\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName); 58 using (var stream = System.IO.File.Create(path)) 59 { 60 //await file.CopyToAsync(stream); 61 //進度條處理流程 62 using (var inputStream = file.OpenReadStream()) 63 { 64 //讀取上傳檔案流 65 while (await inputStream.ReadAsync(bt, 0, bt.Length) > 0) 66 { 67 68 //當前讀取的長度 69 currentSize += bt.Length; 70 71 //寫入上傳流到伺服器檔案中 72 await stream.WriteAsync(bt, 0, bt.Length); 73 74 //獲取每次讀取的大小 75 readSize = currentSize + readSize <= totalSize ? 76 readSize : 77 totalSize - currentSize; 78 //重新設定 79 bt = new byte[readSize]; 80 81 //設定當前上傳的檔案進度,並重新快取到進度快取中 82 var bars = _cache.Get<List<MoBar>>(cacheKey); 83 var currBar = bars.Where(b => b.FileName == fileName).SingleOrDefault(); 84 currBar.CurrBar = currentSize; 85 currBar.Status = currentSize >= totalSize ? 2 : 1; 86 _cache.Set<List<MoBar>>(cacheKey, bars); 87 88 System.Threading.Thread.Sleep(1000 * 1); 89 } 90 } 91 } 92 } 93 data.Msg = "上傳完成"; 94 data.Status = 2; 95 } 96 catch (Exception ex) 97 { 98 data.Msg = ex.Message; 99 } 100 return Json(data); 101 }
程式碼一下子就變多了,其實按照邏輯來說增加了儲存進度的Cache,和逐一讀取上傳檔案流的邏輯而已,具體大家可以仔細看下程式碼,都有備註說明;再來就是咋們的進度資訊Action介面:
1 [HttpPost] 2 public JsonResult ProgresBar02() 3 { 4 var bars = new List<MoBar>(); 5 try 6 { 7 bars = _cache.Get<List<MoBar>>(cacheKey); 8 } 9 catch (Exception ex) 10 { 11 } 12 return Json(bars); 13 }
進度介面只需要獲取cache中的進度資訊就行了,注:這裡是測試用例,具體使用場景請各位自行增加其他邏輯程式碼;下面就來看下效果截圖:
Task並行處理+ajax提交+上傳進度+一組圖片上傳
這一小節,將會使用Task來處理上傳的檔案,通過上一小節截圖能夠看出,如果你上傳多個檔案,那麼都是按照次序一個一個讀取檔案流來生成上傳檔案到伺服器,這裡改良一下利用Task的特點,就能實現同時讀取不同檔案流了,先來看下html程式碼和js程式碼:
1 <form class="form-horizontal" id="form03" method="post" enctype="multipart/form-data"> 2 3 <input type="file" name="MyPhoto03" class="form-control" multiple /> 4 <br /> 5 <button type="button" id="btnAjax03" class="btn btn-default">task任務處理ajax上傳進度效果上傳</button> 6 <br /> 7 <span style="color:red" id="span03"></span> 8 </form>
由於和示例3的js程式碼無差別這裡我直接貼出程式碼:
1 $("#btnAjax03").on("click", function () { 2 3 var interBar; 4 var msg = $("#span03"); 5 msg.html("上傳中,請稍後..."); 6 var form = document.getElementById("form03"); 7 //console.log(form); 8 var data = new FormData(form); 9 10 $.ajax({ 11 type: "POST", 12 url: "/home/AjaxFileUp03", 13 data: data, 14 15 contentType: false, 16 processData: false, 17 success: function (data) { 18 if (data) { 19 msg.html(data.msg); 20 //清除進度查詢 21 if (interBar) { clearInterval(interBar); } 22 } 23 }, 24 error: function () { 25 msg.html("上傳檔案異常,請稍後重試!"); 26 if (interBar) { clearInterval(interBar); } 27 } 28 }); 29 30 //獲取進度 31 interBar = setInterval(function () { 32 33 $.post("/home/ProgresBar03", function (data) { 34 35 if (data) { 36 var isClearVal = true; 37 var strArr = []; 38 $.each(data, function (i, item) { 39 strArr.push('檔案:' + item.fileName + ",當前上傳:" + item.percentBar + '<br/>'); 40 if (item.status != 2) { isClearVal = false; } 41 }); 42 msg.html(strArr.join('')); 43 if (isClearVal) { 44 if (interBar) { clearInterval(interBar); } 45 } 46 } 47 }); 48 }, 200); 49 });
關鍵點在後臺,通過task陣列來儲存每個上傳檔案的處理任務 Task[] tasks = new Task[len]; ,然後使用 Task.WaitAll(tasks); 等待所有上傳任務的完成,這裡特別注意了這裡必須等待,不然會丟失上傳檔案流(多次測試結果):
1 /// <summary> 2 /// ajax上傳進度效果上傳 3 /// </summary> 4 /// <returns></returns> 5 [HttpPost] 6 public JsonResult AjaxFileUp03() 7 { 8 var data = new MoData { Msg = "上傳失敗" }; 9 try 10 { 11 var files = Request.Form.Files.Where(b => b.Name == "MyPhoto03"); 12 //非空限制 13 if (files == null || files.Count() <= 0) { data.Msg = "請選擇上傳的檔案。"; return Json(data); } 14 15 //格式限制 16 var allowType = new string[] { "image/jpeg", "image/png" }; 17 if (files.Any(b => !allowType.Contains(b.ContentType))) 18 { 19 data.Msg = $"只能上傳{string.Join(",", allowType)}格式的檔案。"; 20 return Json(data); 21 } 22 23 //大小限制 24 if (files.Sum(b => b.Length) >= 1024 * 1024 * 4) 25 { 26 data.Msg = "上傳檔案的總大小隻能在4M以下。"; return Json(data); 27 } 28 29 //初始化上傳多個檔案的Bar,儲存到快取中,方便獲取上傳進度 30 var listBar = new List<MoBar>(); 31 files.ToList().ForEach(b => 32 { 33 listBar.Add(new MoBar 34 { 35 FileName = b.FileName, 36 Status = 1, 37 CurrBar = 0, 38 TotalBar = b.Length 39 }); 40 }); 41 _cache.Set<List<MoBar>>(cacheKey03, listBar); 42 43 var len = files.Count(); 44 Task[] tasks = new Task[len]; 45 //寫入伺服器磁碟 46 for (int i = 0; i < len; i++) 47 { 48 var file = files.Skip(i).Take(1).SingleOrDefault(); 49 tasks[i] = Task.Factory.StartNew((p) => 50 { 51 var item = p as IFormFile; 52 53 //總大小 54 var totalSize = item.Length; 55 //初始化每次讀取大小 56 var readSize = 1024L; 57 var bt = new byte[totalSize > readSize ? readSize : totalSize]; 58 //當前已經讀取的大小 59 var currentSize = 0L; 60 61 var fileName = item.FileName; 62 var path = Path.Combine(@"D:\F\學習\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName); 63 using (var stream = System.IO.File.Create(path)) 64 { 65 //進度條處理流程 66 using (var inputStream = item.OpenReadStream()) 67 { 68 //讀取上傳檔案流 69 while (inputStream.Read(bt, 0, bt.Length) > 0) 70 { 71 72 //當前讀取的長度 73 currentSize += bt.Length; 74 75 //寫入上傳流到伺服器檔案中 76 stream.Write(bt, 0, bt.Length); 77 78 //獲取每次讀取的大小 79 readSize = currentSize + readSize <= totalSize ? 80 readSize : 81 totalSize - currentSize; 82 //重新設定 83 bt = new byte[readSize]; 84 85 //設定當前上傳的檔案進度,並重新快取到進度快取中 86 var bars = _cache.Get<List<MoBar>>(cacheKey03); 87 var currBar = bars.Where(b => b.FileName == fileName).SingleOrDefault(); 88 currBar.CurrBar = currentSize; 89 currBar.Status = currentSize >= totalSize ? 2 : 1; 90 _cache.Set<List<MoBar>>(cacheKey03, bars); 91 92 System.Threading.Thread.Sleep(1000 * 1); 93 } 94 } 95 } 96 97 }, file); 98 } 99 100 //任務等待 ,這裡必須等待,不然會丟失上傳檔案流 101 Task.WaitAll(tasks); 102 103 data.Msg = "上傳完成"; 104 data.Status = 2; 105 } 106 catch (Exception ex) 107 { 108 data.Msg = ex.Message; 109 } 110 return Json(data); 111 }
至於獲取上傳進度的Action也僅僅只是讀取快取資料而已:
1 [HttpPost] 2 public JsonResult ProgresBar03() 3 { 4 var bars = new List<MoBar>(); 5 try 6 { 7 bars = _cache.Get<List<MoBar>>(cacheKey03); 8 } 9 catch (Exception ex) 10 { 11 } 12 return Json(bars); 13 }
這裡再給出上傳進度的實體類:
1 public class MoData 2 { 3 /// <summary> 4 /// 0:失敗 1:上傳中 2:成功 5 /// </summary> 6 public int Status { get; set; } 7 8 public string Msg { get; set; } 9 } 10 11 public class MoBar : MoData 12 { 13 /// <summary> 14 /// 檔名字 15 /// </summary> 16 public string FileName { get; set; } 17 18 /// <summary> 19 /// 當前上傳大小 20 /// </summary> 21 public long CurrBar { get; set; } 22 23 /// <summary> 24 /// 總大小 25 /// </summary> 26 public long TotalBar { get; set; } 27 28 /// <summary> 29 /// 進度百分比 30 /// </summary> 31 public string PercentBar 32 { 33 get 34 { 35 return $"{(this.CurrBar * 100 / this.TotalBar)}%"; 36 } 37 } 38 }
到此task任務處理上傳檔案的方式就完成了,咋們來看圖看效果吧:
能夠通過示例3和4的效果圖對比出,沒使用Task和使用的效果區別,這樣效果您值得擁有,耐心讀完本文內容的朋友,沒讓你失望吧,如果可以不妨點個"贊"或掃個碼支援下作者,謝謝;內容最後附上具體測試用例程式碼:.NetCore上傳多檔案的幾種示例