MVC4 WebAPI(二)——Web API工做方式

http://www.cnblogs.com/wk1234/archive/2012/05/07/2486872.htmlhtml

 

在上篇文章中和你們一塊兒學習了創建基本的WebAPI應用,馬上就有人想到了一些問題:
1.客戶端和WebService之間文件傳輸
2.客戶端或者服務端的安全控制
要解決這些問題,要了解一下WebAPI的基本工做方式。json

(一)WebAPI中工做的Classapi

在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爲:async

複製代碼
        [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表單提交。

在這裏先給出一個服務端的響應工做流,讓你們有個大致上的認識

(不得已在圖片中加了水印,由於看到本身辛苦寫的東西被人直接拿走,也不給出原文連接,內心真的很差受..但願不會影響你們的閱讀...)
因爲圖片大小限制,全部的HttpRequestMessage被簡寫爲HttpRequestMsg,HttpResponseMessage被簡寫了HttpResponseMsg

大 家能夠看到,HTTP的請求最早是被傳遞到HOST中的,若是WebAPI是被寄宿在IIS上的,這個HOST就是IIS上,HOST是沒有能力也沒有必 要進行請求的處理的,請求經過HOST被轉發給了HttPServer此時已經進入WebAPI的處理加工範圍,HttpServer是 System.Net.HTTP中的一個類,經過HttpServer,請求被封裝成了WebAPI中的請求承載 類:HttpRequestMessage,這個封裝後的請求能夠通過一系列自定義的Handler來處理,這些handler串聯成一個 pipeline,最後請求會被傳遞給HttpControlDispather,這個類經過對路由表的檢索來肯定請求將被轉發到的具體的 Controller中的Action。

Client端的處理與服務端相似,直接上圖:

其實根據微軟的說法,他們自己就被設計成相似可是能夠獨立運行的結構

ASP.NET Web API has a pipeline for processing HTTP messages on both the client and server. The client and server sides are designed to be symmetrical but independent; you can use each half by itself. Both sides are built on some common objects:

  • HttpRequestMessage represents the HTTP request.
  • HttpResponseMessage represents the HTTP response.
  • HttpMessageHandler objects process the request and response.

直接看圖,在客戶端,Handlers pipeline最終是被傳遞到HttpClientHandler上的,由他負責HttpRequestMessage到HTTP請求的轉換。

這裏只說明一下Request,Response與其相似。

(四)解決第二個問題

由 此咱們早就能夠看出,想要解決第二個問題,能夠直接在Handler PipeLine中進行,這種AOP風格的過濾器(攔截器)在REST的Webservice的安全驗證中應用很廣,通常你們比較樂於在HTTP頭或者在 HTTP請求的URL中加上身份驗證字段進行身份驗證,下面舉一個在Http頭中添加身份驗證信息的小例子

4.1客戶端
客戶端的customhandler用於將身份驗證信息添加入報頭

複製代碼
    class RequestUpHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            request.Headers.Add("key", "11234");
            return base.SendAsync(request, cancellationToken);
        }
    }
複製代碼

注:
1.customhandler繼承自DelegatingHandler類,上面已經說過,WebAPI的客戶端和服務端被設計爲相互對應的兩套結構,因此不管是在客戶端仍是服務端,customhandler都是繼承自DelegatingHandler類
2.DelegatingHandler的sendAsync方法即是處理請求和接受請求時會被調用的方法,該方法返回值是HttPResponseMessage,接收的值爲HttpRequestMessage,符合咱們的通常認知
3.方法的最後,調用base.SendAsync是將Request繼續向該pipeline的其餘customHandler傳遞,並獲取其返回值。因爲該方法不包含Response的處理邏輯,只需直接將上一個CustomHandler的
返回值直接返回
客戶端主程序

複製代碼
        static void Main(string[] args)
        {
            HttpClient client = new HttpClient(new RequestUpHandler() { InnerHandler = new HttpClientHandler() });
            HttpResponseMessage response = client.GetAsync("http://localhost:60023/api/FormSubmit").Result;
            response.Content.ReadAsAsync<string>().ContinueWith((str) => { Console.WriteLine(str.Result); });
            Console.Read();
        }
複製代碼

客 戶端的主程序建立了一個HttpClient,HttpClient能夠接受一個參數,該參數就是CustomHandler,此處咱們嵌入了咱們定義的 RequestUpHandler,用於對Request報頭進行嵌入身份驗證碼的處理,CustomHandler經過InnerHandler屬性嵌 入其內置的下一個CustomHandler,此處,因爲沒有下一個CustomerHandler,咱們直接嵌入HttpClientHandler用 於將HttpRequestMessage轉化爲HTTP 請求、將HTTP響應轉化爲HttpResponseMessage

4.2服務端
服務端的customHandler用於解析HTTP報頭中的身份認證碼

複製代碼
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            int matchHeaderCount = request.Headers.Count((item) =>
            {
                if ("key".Equals(item.Key))
                {
                    foreach (var str in item.Value)
                    {
                        if ("11234".Equals(str))
                        {
                            return true;
                        }
                    }
                }
                return false;
            });
            if (matchHeaderCount>0)
            {
                return base.SendAsync(request, cancellationToken);
            }
            return Task.Factory.StartNew<HttpResponseMessage>(() => { return new HttpResponseMessage(HttpStatusCode.Forbidden); });
        }
複製代碼

注:代碼的處理邏輯很簡單:若是身份驗證碼匹配成功,則經過base.SendAsync繼續將請求向下傳遞,不然返回直接中斷請求的傳遞,直接返回一個響應碼爲403的響應,指示沒有權限。
注意因爲SendAsync的返回值須要封裝在Task之中,因此須要使用Task.Factory.StartNew將返回值包含在Task中

將customHandler注入到HOST中
本例中WebAPI HOST在IIS上,因此咱們只需將咱們定義的CustomHandler在Application_Start中定義便可

        protected void Application_Start()
        {
            //省略其餘邏輯代碼

            GlobalConfiguration.Configuration.MessageHandlers.Add(new HttpUrlHandler());
        }

因爲WebAPI Host在IIS上,因此HttpServer和HttpControllerDispatcher不用咱們手工處理

在加上上面的處理後,若是沒有身份驗證碼的請求,會獲得以下的響應

 

 ******************************************************************************
做者:王坤 
出處:http://www.cnblogs.com/wk1234本文版權歸 王坤和博客園共有,歡迎轉載,但請註明出處。

相關文章
相關標籤/搜索