本章和你們分享的是.NetCore的MVC框架上傳文件的示例,主要講的內容有:form方式提交上傳,ajax上傳,ajax提交+上傳進度效果,Task並行處理+ajax提交+上傳進度,相信當你讀完文章內容後能後好的收穫,若是能夠不妨點個贊;因爲昨天電腦沒電了,快要寫完的內容沒有保存,今天早上提早來公司從頭開始從新,斷電這狀況的確讓人很頭痛啊,不過爲了社區的分享環境,這也是值得的,很少說了來進入今天的正篇環節吧;html
先來看看咋們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中代碼以下:html5
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":jquery
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 }
若是你有耐心讀到這裏,那麼後面的內容我的感受對你開發會有好的幫助,不負你期待;服務器
一樣咱們先來看對應的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來處理上傳的文件,經過上一小節截圖可以看出,若是你上傳多個文件,那麼都是按照次序一個一個讀取文件流來生成上傳文件到服務器,這裏改良一下利用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上傳多文件的幾種示例