在上篇文章中和大家一起學(xué)習(xí)了建立基本的WebAPI應(yīng)用,立刻就有人想到了一些問題:1.客戶端和WebService之間文件傳輸2.客戶端或者服務(wù)端的安全控制要解決這些問題,要了解一下WebAPI的基本工作方式。
(一)WebAPI中工作的Class
在MVC中大家都知道,獲取Request和Response使用HttPRequest和HttpResponse兩個(gè)類,在WebAPI中使用兩外兩個(gè)類:HttpRequestMessage 和HttpResponseMessage,分別用于封裝Requset和Response。除了這兩個(gè)類之外,還有一個(gè)常見的抽象 類:HttpMessageHandler,用于過濾和加工HttpRequestMessage和HttpResponseMessage
(二)解決第一個(gè)問題
其 實(shí)第一個(gè)問題之所以被提出來應(yīng)該是和客戶端有關(guān),如果客戶端的請(qǐng)求是我們手寫提交的,比如使用HttpClient封裝的請(qǐng)求,則要傳遞文件之前,我們一 般會(huì)進(jìn)行一次序列化,轉(zhuǎn)化為二進(jìn)制數(shù)組之類的,在網(wǎng)絡(luò)上傳輸。這樣的話,在Controller中的Action參數(shù)里,我們只需要接收這個(gè)二進(jìn)制數(shù)組類 型的對(duì)象就可以了。但是如果客戶端是Web Form呢,比如我們提交一個(gè)Form到指定的Controller的Action中,這個(gè)Action要接收什么類型的參數(shù)呢?或者我們問另外一個(gè)問題,如果我將Web Form提交到一個(gè)WebAPI的Action中 ,我要怎么去取出這個(gè)表單中的數(shù)據(jù)呢?其 實(shí)我們應(yīng)該想到:我們的Action設(shè)置的參數(shù)之所以能夠被賦值,是因?yàn)閃ebAPI的架構(gòu)中在調(diào)用Action時(shí)將HTTP請(qǐng)求中的數(shù)據(jù)解析出來分別賦 值給Action中的參數(shù),如果真是這樣的話,我們只需要在Action中獲取到HTTP請(qǐng)求,然后直接獲取請(qǐng)求里面的數(shù)據(jù),就能解決上面的問題。這 種想法是正確的,只不過,此時(shí)的HTTP請(qǐng)求已經(jīng)不是最原始的HTTP Request,而是已經(jīng)被轉(zhuǎn)化成了HttpRequestMessage,在Action中,我們可以直接調(diào)用base.Requet來得到這個(gè) HttpRequestMessage實(shí)例,通過這個(gè)實(shí)例我們就可以隨心所欲的取出HTTP請(qǐng)求中想要的數(shù)據(jù)。
2.1從RequestMessage中獲取普通表單數(shù)據(jù)
這里的普通表單是指不包含F(xiàn)ile的表單,也就是說表單的enctype值不是multipart/form-data,這時(shí),表單的數(shù)據(jù)默認(rèn)情況下是以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>
捕獲到的請(qǐng)求為
提交到對(duì)應(yīng)的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中使用到了關(guān)鍵字async和await,這些在4.5中新提出的關(guān)鍵字主要是用于進(jìn)行多線程取值的,在MVCAPI的設(shè)計(jì)中,大部分的方法都被設(shè)計(jì)成類似于下面的方法
public static Task<T> ReadAsOrDefaultAsync<T>(this HttpContent content);
返 回值是一個(gè)Task,這種返回新線程的方法雖然可以提高系統(tǒng)的響應(yīng)能力,但是多線程取值會(huì)給編碼帶來不便,所以新出的關(guān)鍵字await用于阻塞當(dāng)前線程并 獲取目標(biāo)線程的返回值,在方法體中使用await關(guān)鍵字后要求將方法聲明為async用來表示該方法是異步的,并且返回值必須為void或者將返回者封裝 在一個(gè)Task中當(dāng)然,如果你不喜歡這種寫法,上面的action也可以寫為:
Task readTask = content.ReadAsOrDefaultAsync<JsonObject>().ContinueWith((task) => { jsonValue = task.Result; }); readTask.Wait();
2.2從RequestMessage中獲取multipart表單數(shù)據(jù)將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>
此時(shí)捕獲到得請(qǐng)求是
這里的文件內(nèi)容被捕獲軟件解析成字符串,當(dāng)然如果我上傳的是其他的非文本格式的文件,文件會(huì)被轉(zhuǎn)化為二進(jìn)制數(shù)組這時(shí)如果我們不更改action,而直接調(diào)用,會(huì)發(fā)生錯(cuò)誤,原因很明顯,這個(gè)HTTP的報(bào)文內(nèi)容是無法被轉(zhuǎn)換為JSON的,這時(shí)我們需要將表單的報(bào)文解析成另外一種格式
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":******{文件的內(nèi)容}*****----}整合后的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工作方式
要想解決第二個(gè)問題就沒這么容易了,我們需要更深入的理解WebAPI的工作方式。其實(shí)對(duì)于WebAPI來說,它最初被設(shè)計(jì)為和WCF一樣的:客戶端、服務(wù)端兩套結(jié)構(gòu),我們到現(xiàn)在之所以還沒有提到客戶端,是因?yàn)槲覀兊恼?qǐng)求別的方式來封裝成HTTP請(qǐng)求或接收HTTP相應(yīng)的,比如Ajax和Form表單提交。在這里先給出一個(gè)服務(wù)端的響應(yīng)工作流,讓大家有個(gè)大體上的認(rèn)識(shí)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
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注