在上篇文章中和大家一起學習了建立基本的WebAPI應用,立刻就有人想到了一些問題:1.客戶端和WebService之間文件傳輸2.客戶端或者服務端的安全控制要解決這些問題,要了解一下WebAPI的基本工作方式。
(一)WebAPI中工作的Class
在MVC中大家都知道,獲取Request和Response使用HttPRequest和HttpResponse兩個類,在WebAPI中使用兩外兩個類:HttpRequestMessage 和HttpResponseMessage,分別用于封裝Requset和Response。除了這兩個類之外,還有一個常見的抽象 類:HttpMessageHandler,用于過濾和加工HttpRequestMessage和HttpResponseMessage
(二)解決第一個問題
其 實第一個問題之所以被提出來應該是和客戶端有關,如果客戶端的請求是我們手寫提交的,比如使用HttpClient封裝的請求,則要傳遞文件之前,我們一 般會進行一次序列化,轉化為二進制數組之類的,在網絡上傳輸。這樣的話,在Controller中的Action參數里,我們只需要接收這個二進制數組類 型的對象就可以了。但是如果客戶端是Web Form呢,比如我們提交一個Form到指定的Controller的Action中,這個Action要接收什么類型的參數呢?或者我們問另外一個問題,如果我將Web Form提交到一個WebAPI的Action中 ,我要怎么去取出這個表單中的數據呢?其 實我們應該想到:我們的Action設置的參數之所以能夠被賦值,是因為WebAPI的架構中在調用Action時將HTTP請求中的數據解析出來分別賦 值給Action中的參數,如果真是這樣的話,我們只需要在Action中獲取到HTTP請求,然后直接獲取請求里面的數據,就能解決上面的問題。這 種想法是正確的,只不過,此時的HTTP請求已經不是最原始的HTTP Request,而是已經被轉化成了HttpRequestMessage,在Action中,我們可以直接調用base.Requet來得到這個 HttpRequestMessage實例,通過這個實例我們就可以隨心所欲的取出HTTP請求中想要的數據。
2.1從RequestMessage中獲取普通表單數據
這里的普通表單是指不包含File的表單,也就是說表單的enctype值不是multipart/form-data,這時,表單的數據默認情況下是以Json來傳遞的如下頁面
<form name="form" action="~/api/FormSubmit?key=11234" method="post"> <input type="text" name="key" id="txtKey" /> <br /> <input type="text" name="value" id="txtValue" /> <br /> <input type="submit" id="btnSubmit" value="Submit" /> </form>
捕獲到的請求為
提交到對應的Action為:
[HttpPost] public async void submitForm() { StringBuilder sb = new StringBuilder(); HttpContent content = Request.Content; JsonObject jsonValue = await content.ReadAsOrDefaultAsync<JsonObject>(); foreach (var x in jsonValue) { sb.Append(x.Key); string va ; if (x.Value.TryReadAs<string>(out va)) { sb.Append(va); } } }
這樣最后可以得到 Json的值:{"key":"123","value":"123"} sb處理后的值為:key123value123
注:在該action中使用到了關鍵字async和await,這些在4.5中新提出的關鍵字主要是用于進行多線程取值的,在MVCAPI的設計中,大部分的方法都被設計成類似于下面的方法
public static Task<T> ReadAsOrDefaultAsync<T>(this HttpContent content);
返 回值是一個Task,這種返回新線程的方法雖然可以提高系統的響應能力,但是多線程取值會給編碼帶來不便,所以新出的關鍵字await用于阻塞當前線程并 獲取目標線程的返回值,在方法體中使用await關鍵字后要求將方法聲明為async用來表示該方法是異步的,并且返回值必須為void或者將返回者封裝 在一個Task中當然,如果你不喜歡這種寫法,上面的action也可以寫為:
Task readTask = content.ReadAsOrDefaultAsync<JsonObject>().ContinueWith((task) => { jsonValue = task.Result; }); readTask.Wait();
2.2從RequestMessage中獲取multipart表單數據將view頁面改寫為
<form name="form" action="~/api/FormSubmit?key=11234" method="post" enctype="multipart/form-data" > <input type="text" name="key" id="txtKey" /> <br /> <input type="text" name="value" id="txtValue" /> <br /> <input type="file" name="file" id="upFile" /> <br /> <input type="submit" id="btnSubmit" value="Submit" /></form>
此時捕獲到得請求是
這里的文件內容被捕獲軟件解析成字符串,當然如果我上傳的是其他的非文本格式的文件,文件會被轉化為二進制數組這時如果我們不更改action,而直接調用,會發生錯誤,原因很明顯,這個HTTP的報文內容是無法被轉換為JSON的,這時我們需要將表單的報文解析成另外一種格式
IEnumerable<HttpContent> bodyparts = await content.ReadAsMultipartAsync(); foreach (var bodypart in bodyparts) { string name; name = bodypart.Headers.ContentDisposition.Name; sb.Append(name + ":"); if (bodypart.Headers.Contains("filename")) { Stream stream = await bodypart.ReadAsStreamAsync(); StreamReader reader = new StreamReader(stream); sb.Append(reader.ReadToEnd()); sb.Append("----"); } else { string val = await bodypart.ReadAsStringAsync(); sb.Append(val); sb.Append("----"); } }
得到的處理后的sb值為:
{"key":123----"value":123----"file":******{文件的內容}*****----}整合后的Action為
[HttpPost] public async void submitForm() { StringBuilder sb = new StringBuilder(); HttpContent content = Request.Content; if (content.IsMimeMultipartContent()) { IEnumerable<HttpContent> bodyparts = await content.ReadAsMultipartAsync(); foreach (var bodypart in bodyparts) { string name; name = bodypart.Headers.ContentDisposition.Name; sb.Append(name + ":"); if (bodypart.Headers.Contains("filename")) { Stream stream = await bodypart.ReadAsStreamAsync(); StreamReader reader = new StreamReader(stream); sb.Append(reader.ReadToEnd()); sb.Append("----"); } else { string val = await bodypart.ReadAsStringAsync(); sb.Append(val); sb.Append("----"); } } } else { JsonObject jsonValue = await content.ReadAsOrDefaultAsync<JsonObject>(); foreach (var x in jsonValue) { sb.Append(x.Key); string va; if (x.Value.TryReadAs<string>(out va)) { sb.Append(va); } } } }
(三)WebAPI工作方式
要想解決第二個問題就沒這么容易了,我們需要更深入的理解WebAPI的工作方式。其實對于WebAPI來說,它最初被設計為和WCF一樣的:客戶端、服務端兩套結構,我們到現在之所以還沒有提到客戶端,是因為我們的請求別的方式來封裝成HTTP請求或接收HTTP相應的,比如Ajax和Form表單提交。在這里先給出一個服務端的響應工作流,讓大家有個大體上的認識aspDfAAAAAAAAcgPyGwAAAAAAQG5AfgMAAAAAAMgNyG8AAAAAAAC5AfkNAAAAAAAgNyC/AQAAAAAA5AbkNwAAAAAAgNyA/AYAAAAAAJAbkN8AAAAAAAByA/IbAAAAAABAbsij/BZJTOZGAAAAAAAA9iAv8lskEgmHw+FwOBQKBewz/tlepgL2mWAwGAqFwuFw8gzGPMu0EAqFAgvD241srDAtMI3I+boAAAAAACCvqDy/xUOX32XzNX5AXXned/U7/js/YMp39TveM1+nao4HHPPxFCdsIRKJhEKhYDBIDT+k7v7Ie+brvlsvxRuhLj5F3TxENf/B77IlagQAAAAAACB9qs1v8dwVoLa8ncW+q98N6i9GLJ30qplTtrFgdzF1+Tmq4ZTf6w6FQuwTcUz8CwaD1GgtdeX5QNXb4dGHtG2M10jY1BRsP0tdec7bcSFAbfEaAQAAAAAAkES257f4hYtCSTjspHf5PJve8p8Hmz6ILAzSq6YkFdRfpO78kFpfCgaDTOPRE3d+P9XwW3/lWxGLPnkLtG000Hzad+271OpsvBGZNxcAAAAAAKhYluY39vfNgj6P39xCNf/Bd/dH1N0f+e7+iNL+wW9uCWw5Rb+6tn3SbGPNd/vl0HDZDrkrVuGJGt/tI9750UAgEAwGmRa8d34Y1GtSbIFeNUUs+ngjiHAAAAAAACChbMxv22fPFsepsje8Z77uL3sj2H4mPFrJVLD9jL/sDercP1N3f+RbHI+f7IqnvmAw6HW7fLdfDhsb6BVj6hWZ0fluHtxanvZ6vZ5Np+/2y+HRyl21QK8YI9Ye/91XPXMjiHAAAAAAAGzaPhepcJG+AGtaSNPsIhWuoiXFepVDsiu/bV+1uDbnrXjL
新聞熱點
疑難解答