前言
前面我們講過利用AngularJs上傳到WebAPi中進行處理,同時我們在MVC系列中講過檔案上傳,本文結合MVC+WebAPi來進行檔案的同步或者異步上傳,順便回顧下css和js,MVC作為用戶端,而WebAPi利用不依賴于IIS的selfhost模式作為服務端來接收用戶端的檔案且其過程用Ajax來實作,下面我們一起來看看。
同步上傳
多餘的話不用講,我們直接看頁面。
<div class="container"> <div> @if (ViewBag.Success != null) { <div class="alert alert-danger" role="alert"> <strong>成功啦 !</strong> 成功上傳. <a href="@ViewBag.Success" target="_blank">open file</a> </div> } else if (ViewBag.Failed != null) { <div class="alert alert-danger" role="alert"> <strong>失敗啦 !</strong> @ViewBag.Failed </div> } </div> @using (Html.BeginForm("SyncUpload", "Home", FormMethod.Post, new { role = "form", enctype = "multipart/form-data", @style = "margin-top:50px;" })) { <div class="form-group"> <input type="file" id="file" name="file" /> </div> <input type="submit" value="Submit" class="btn btn-primary" /> } </div>
上述我們直接上傳後通過上傳的狀态來顯示檢視上傳檔案路徑并通路,就是這麼簡單。下面我們來MVC背景邏輯
[HttpPost] public ActionResult SyncUpload(HttpPostedFileBase file) { using (var client = new HttpClient()) { using (var content = new MultipartFormDataContent()) { byte[] Bytes = new byte[file.InputStream.Length + 1]; file.InputStream.Read(Bytes, 0, Bytes.Length); var fileContent = new ByteArrayContent(Bytes); //設定請求頭中的附件為檔案名稱,以便在WebAPi中進行擷取 fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = file.FileName }; content.Add(fileContent); var requestUri = "http://localhost:8084/api/upload/post"; var result = client.PostAsync(requestUri, content).Result; if (result.StatusCode == System.Net.HttpStatusCode.Created) { //擷取到上傳檔案位址,并渲染到視圖中進行通路 var m = result.Content.ReadAsStringAsync().Result; var list = JsonConvert.DeserializeObject<List<string>>(m); ViewBag.Success = list.FirstOrDefault(); } else { ViewBag.Failed = "上傳失敗啦,狀态碼:" + result.StatusCode + ",原因:" + result.ReasonPhrase + ",錯誤資訊:" + result.Content.ToString(); } } } return View(); }
注意:上述将擷取到檔案位元組流數組需要傳遞給 MultipartFormDataContent ,要不然傳遞到WebAPi時會擷取不到檔案資料。
到這裡為止在MVC中操作就已經完畢,此時我們來看看在WebAPi中需要完成哪些操作。
(1)首先肯定需要判斷上傳的資料是否是MimeType類型。
if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); }
(2)我們肯定是需要重新生成一個檔案名稱以免重複,利用Guid或者Date或者其他。
string name = item.Headers.ContentDisposition.FileName.Replace("\"", ""); string newFileName = Guid.NewGuid().ToString("N") + Path.GetExtension(name);
(3)我們需要利用此類 MultipartFileStreamProvider 設定上傳路徑并将檔案寫入到這個裡面。
var provider = new MultipartFileStreamProvider(rootPath); var task = Request.Content.ReadAsMultipartAsync(provider).....
(4) 傳回上傳檔案位址。
return Request.CreateResponse(HttpStatusCode.Created, JsonConvert.SerializeObject(savedFilePath));
分步驟解析了這麼多,組裝代碼如下:
public Task<HttpResponseMessage> Post() { List<string> savedFilePath = new List<string>(); if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var substringBin = AppDomain.CurrentDomain.BaseDirectory.IndexOf("bin"); var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, substringBin); string rootPath = path + "upload"; var provider = new MultipartFileStreamProvider(rootPath); var task = Request.Content.ReadAsMultipartAsync(provider). ContinueWith<HttpResponseMessage>(t => { if (t.IsCanceled || t.IsFaulted) { Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception); } foreach (MultipartFileData item in provider.FileData) { try { string name = item.Headers.ContentDisposition.FileName.Replace("\"", ""); string newFileName = Guid.NewGuid().ToString("N") + Path.GetExtension(name); File.Move(item.LocalFileName, Path.Combine(rootPath, newFileName)); //Request.RequestUri.PathAndQury為需要去掉域名的後面位址 //如上述請求為http://localhost:80824/api/upload/post,這就為api/upload/post //Request.RequestUri.AbsoluteUri則為http://localhost:8084/api/upload/post Uri baseuri = new Uri(Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.PathAndQuery, string.Empty)); string fileRelativePath = rootPath +"\\"+ newFileName; Uri fileFullPath = new Uri(baseuri, fileRelativePath); savedFilePath.Add(fileFullPath.ToString()); } catch (Exception ex) { string message = ex.Message; } } return Request.CreateResponse(HttpStatusCode.Created, JsonConvert.SerializeObject(savedFilePath)); }); return task; }
注意:上述item.LocalFileName為 E:\Documents\Visual Studio 2013\Projects\WebAPiReturnHtml\WebAPiReturnHtml\upload\BodyPart_fa01ff79-4a5b-40f6-887f-ab514ec6636f ,因為此時我們重新命名了檔案名稱,是以需要将該檔案移動到我們重新命名的檔案位址。
整個過程就是這麼簡單,下面我們來看看示範結果。

此時居然出錯了,有點耐人尋味,在服務端是傳回如下的Json字元串
List<string> savedFilePath = new List<string>();
此時進行反序列化時居然出錯,再來看看頁面上的錯誤資訊:
無法将字元串轉換為List<string>,這不是一一對應的麼,好吧,我來看看傳回的字元串到底是怎樣的,【當将滑鼠放上去】時檢視的如下:
【當點選檢視按鈕】時結果如下:
由上知點選檢視按鈕時傳回的才是正确的json,到了這裡我們發現Json.NET序列化時也是有問題的,于是乎在進行反序列化時将傳回的字元串需要進行一下處理轉換成正确的json字元串來再來進行反序列化,修改如下:
var m = result.Content.ReadAsStringAsync().Result; m = m.TrimStart('\"'); m = m.TrimEnd('\"'); m = m.Replace("\\", ""); var list = JsonConvert.DeserializeObject<List<string>>(m);
将上述傳回的json字元串首末尾的\和多出的\\去掉。然後再來看看反序列的資料
最終在頁面顯示如下:
到這裡我們的同步上傳告一段落了,這裡面利用Json.NET進行反序列化時居然出錯問題,第一次遇到Json.NET反序列化時的問題,比較奇葩,費解。
異步上傳
所謂的異步上傳不過是利用Ajax進行上傳,這裡也就是為了複習下腳本或者Razor視圖,下面的内容隻是将視圖進行了修改而已,對于異步上傳我利用了jquery.form.js中的異步api,請看如下代碼:
<script src="~/Scripts/jquery-1.10.2.min.js"></script> <script src="~/Scripts/jquery.form.js"></script> <script src="~/Scripts/bootstrap.min.js"></script> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> <div class="container" style="margin-top:30px"> <div id="success" style="display:none;"> <div class="alert alert-danger" role="alert"> <strong>上傳成功</strong><span style="margin-right:50px;"></span><a href="" target="_blank" id="linkAddr">檔案通路位址</a> </div> </div> <div id="fail" style="display:none;"> <div class="alert alert-danger" role="alert"> <strong>上傳失敗</strong> </div> </div> </div> @using (Ajax.BeginForm("AsyncUpload", "Home", new AjaxOptions() { HttpMethod = "POST" }, new { enctype = "multipart/form-data",@style="margin-top:10px;" })) { <div class="form-group"> <input type="file" name="file" id="fu1" /> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="上傳" /> </div> } <div class="form-group"> <div class="progress" id="progress" style="display:none;"> <div class="progress-bar">0%</div> </div> <div id="status"></div> </div> <style> .progress { position: relative; width: 400px; border: 1px solid #ddd; padding: 1px; } .progress-bar { width: 0px; height: 40px; background-color: #57be65; } </style> <script> (function () { var bar = $('.progress-bar'); var percent = $('.progress-bar'); $('form').ajaxForm({ beforeSend: function () { $("#progress").show(); var percentValue = '0%'; bar.width(percentValue); percent.html(percentValue); }, uploadProgress: function (event, position, total, percentComplete) { var percentValue = percentComplete + '%'; bar.width(percentValue); percent.html(percentValue); }, success: function (d) { var percentValue = '100%'; bar.width(percentValue); percent.html(percentValue); $('#fu1').val(''); }, complete: function (xhr) { if (xhr.responseText != null) { $("#linkAddr").prop("href", xhr.responseText); $("#success").show(); } else { $("#fail").show(); } } }); })(); </script>
我們截圖看下其中上傳過程
上傳中:
上傳完成:
當然這裡的100%不過是針對小檔案的實時上傳,如果是大檔案肯定不是實時的,利用其它元件來實作更加合适,這裡我隻是學習學習僅此而已。
注意:這裡還需重申一遍,之前在MVC上傳已經叙述過,MVC預設的上傳檔案是有限制的,是以超過其限制,則無法上傳,需要進行如下設定
(1)在IIS 5和IIS 6中,預設檔案上傳的最大為4兆,當上傳的檔案大小超過4兆時,則會得到錯誤資訊,但是我們通過如下來設定檔案大小。(2)在IIS 7+,預設檔案上傳的最大為28.6兆,當超過其預設設定大小,同樣會得到錯誤資訊,但是我們卻可以通過如下來設定檔案上傳大小(同時也要進行如上設定)。<system.web> <httpRuntime maxRequestLength="2147483647" executionTimeout="100000" /> </system.web>
![]()
ASP.NET WebAPi(selfhost)之檔案同步或異步上傳 <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="2147483647" /> </requestFiltering> </security> </system.webServer>
![]()
ASP.NET WebAPi(selfhost)之檔案同步或異步上傳
總結
本節我們學習了如何将MVC和WebAPi隔離開來來進行上傳,同時我們也發現在反序列化時Json.NET有一定問題,特此記錄下,當發現一一對應時反序列化傳回的Json字元串不是标準的Json字元串,我們對傳回的Json字元串需要作出如下處理才行(也許還有其他方案)。
var jsonString = "傳回的json字元串"; jsonString = jsonString.TrimStart('\"'); jsonString = jsonString.TrimEnd('\"'); jsonString = jsonString.Replace("\\", "");
接下來會準備系統學習下SQL Server和Oracle,循序漸進,你說呢!休息,休息!
所有的選擇不過是為了下一次選擇做準備