對于.NET的分布式應用開發,可以供我們選擇的技術和框架比較多,例如webservice,.net remoting,MSMQ,WCF等等技術。對于這些技術很多人都不會陌生,即時沒有深入的了解,但是肯定聽說過,每種技術都各有優勢和適用范圍,沒有絕對的好壞,只有相對的合適程度。不過可惜了,今天我們講解的主題不是這幾種技術,今天主要講解的是ASP.NET WebAPI。
對于ASP.NET WebAPI的優勢和特點,在這里就不講了,需要用到的自然就會選擇,也不需要我浪費篇幅去講解這些,這篇博文主要講解ASP.NET WebAPI中的HTTP消息的結構和處理消息的核心對象。
一.WebAPI的HTTP概述:
有關HTTP協議的相關內容在這里就不做介紹,在筆者前面的博文中已經做過介紹,現在提供一下地址,因為過多的贅述就是浪費時間,我就姑且看這篇博文的讀者已經對HTTP協議和WebAPI都有所了解。博文地址:
http://www.cnblogs.com/pengze0902/p/5976388.html
http://www.cnblogs.com/pengze0902/p/6224792.html
http://www.cnblogs.com/pengze0902/p/6230105.html
1.在.NET4.5之前的版本中,處理HTTP的核心對象:
(1).在客戶端:System.Net.HttpWebRequest用于初始化HTTP請求,處理相關的響應; System.Net.HttpWebResponse處理HTTP響應頭和數據讀取的檢索。
(2).在服務器端:System.Web.HttpContext,System.Web.HttpRequest,System.Web.HttpResponse類用在ASP.NET上下文中,代表單個請求和響應。System.Net.HttpListenerContext類,提供對HTTP請求和響應對象的訪問。
2.在.NET4.5版本中,處理HTTP的核心對象:
(1).在客戶端和服務器端使用同樣的類。(HttpRequestMessage和HttpResponseMessage對象中不包含上下文消息,所以可以在服務器和客戶端共用。)
(2).由于在.NET4.5中引入了TAP(異步任務模型),所以在新的HTTP模型中,處理HTTP請求的方法可以使用async和awit實現異步編程。(可以簡單高效的實現異步編程)
我們對于新舊的HTTP編程模型時,會很容易的發現在新版本的HTTP模型中,無論是編程的難度和代碼編寫的精簡度,已經執行的效率都是很高的。在對于Web項目的開發中,我們對HTTP知識的了解是必要的,對于ASP.NET的HTTP處理的原理在這里就不做具體的介紹,網上也有比較多的文章可供閱讀和了解。
對于ASP.NET的HTTP處理方式的了解,是我在開發微信公眾平臺時進一步學習的,微信公眾平臺提供了對外訪問的接口,我們的程序和服務器對微信服務器的接口進行請求訪問,微信服務器獲取HTTP請求后,返回處理結果,本地服務器獲取返回結果。這樣一個請求-響應模式,組成一個會話。對于微信公眾平臺的開發對于很多剛學習.NET的人來說有些高大(當然這是相對而言),即時開發過很多次這個類別的程序的人(調用第三方接口的開發)也不一定可以很清晰的知道這個其中的原理,筆者覺得對于這樣的第三方平臺的開發,其主要的核心部分就是對于HTTP協議的處理,建立請求、獲取響應消息和解析消息這三大步驟,返回的消息內容一般為json或者xml,獲取響應消息后,主要是對消息內容的反序列化,獲得消息的實體信息,進而在程序中進一步處理。
在WeAPI中消息的產生和解析,以及消息的格式都是可以動態的創建和協商,下面我們進一步的了解實現這一過程的核心對象。
二.WebAPI的HTTP消息解析:
HTTP協議的工作方式是在客戶端和服務器之間交換請求和響應消息,那么這也就可以說明HTTP的核心就是消息,對于“消息”的了解,我們只要知道消息分為“消息頭部”和“消息內容”,我們接下來的對新HTTP編程模型的介紹的主體就是“消息頭部”和“消息內容”。
在命名空間System.Net.Http中,具有兩個核心對象:HttpRequestMessage和HttpResponseMessage。兩個對象的結構如下圖:
以上主要講解了HttpRequestMessage對象和HttpResponseMessage對象包含的主要內容,請求和響應消息都可以包含一個可選的消息正文,兩中消息類型以及消息內容,都可以使用響應的標頭。接下來具體了解一些消息的結構。
1.HttpRequestMessage對象解析:
(1).HttpRequestMessage主要屬性和方法概述:
名稱 | 說明 |
Version | 獲取或設置 HTTP 消息版本 |
Content | 獲取或設置 HTTP 消息的內容 |
Method | 獲取或設置 HTTP 請求信息使用的 HTTP 方法 |
RequestUri | 獲取或設置 HTTP 請求的 Uri |
Headers | 獲取 HTTP 請求標頭的集合 |
Properties | 獲取 HTTP 請求的屬性集 |
ToString | 返回表示當前對象的字符串 |
該對象主要用于表示 HTTP 請求消息。對于該對象的這些屬性和方法,大部分應該都不會陌生,因為一個HTTP消息中主要包含頭部、消息內容等等,在這里主要介紹一個屬性Properties,該屬性并不屬于任何標準的HTTP消息,當消息傳輸時,不會保留該屬性。
(2).Properties屬性解析:
[__DynamicallyInvokable] public IDictionary<string, object> Properties { [__DynamicallyInvokable] get { if (this.properties == null) { this.properties = new Dictionary<string, object>(); } return this.properties; } }
有以上的代碼可以很明顯的看出該屬性只有一個只讀屬性,并返回一個IDictionary<string, object>。當消息在服務器或者客戶端本地進行處理時,該屬性用于保存附加的消息信息。該屬性只是一個通用的容器,保存本地消息屬性。(與接受消息的連接相關的客戶端認證;將消息與配置路由進行匹配,得到的路由數據)
2.HttpResponseMessage對象解析:
(1).HttpRequestMessage主要屬性和方法概述:
名稱 | 說明 |
EnsureSuccessStatusCode | 如果 HTTP 響應的 IsSuccessStatusCode 屬性為 false, 將引發異常 |
StatusCode | 獲取或設置 HTTP 響應的狀態代碼 |
ReasonPhrase | 獲取或設置服務器與狀態代碼通常一起發送的原因短語 |
RequestMessage | 獲取或設置導致此響應消息的請求消息 |
IsSuccessStatusCode | 獲取一個值,該值指示 HTTP 響應是否成功 |
對于該對象的一些屬性沒有列舉,因為在HttpRequestMessage對象已經介紹,如:Version、Content、Headers等,該對象主要用于表示 HTTP 響應消息。在這里主要介紹StatusCode屬性。
(2).StatusCode屬性:
[__DynamicallyInvokable] public HttpStatusCode StatusCode { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.statusCode; } [__DynamicallyInvokable] set { if ((value < ((HttpStatusCode) 0)) || (value > ((HttpStatusCode) 0x3e7))) { throw new ArgumentOutOfRangeException("value"); } this.CheckDisposed(); this.statusCode = value; } }
StatusCode屬性為枚舉屬性,該屬性可讀可寫,對于狀態碼這個概念,很多人都是比較了解的,在HTTP協議中,狀態碼主要是表示在消息的請求在服務器中處理的結果,狀態有2XX,3XX,4XX,5XX等等,具體表示的意義就不再描述。
3.HTTP模型消息標頭解析:
在HTTP中,請求和響應消息,以及消息內容自身,都可以使用稱為標頭的額外字段,包含更多的信息。
(1).標頭分類:
標頭名稱 | 描述 | HTTP模型標頭容器類 |
User-Agent | 為請求提供擴展信息,描述產生這個請求的應用程序 | HttpRequestHeaders |
Server | 為響應提供關于源服務器軟件的擴展信息 | HttpResponseHeaders |
Content-Type | 定義請求或響應有效載荷正文中,資源表示使用的媒體類型 | HttpContentHeaders |
(2).HttpHeaders抽象類分析:
名稱 | 描述 |
Add | 添加指定的標頭及其值到 HttpHeaders 集合中。 |
TryAddWithoutValidation | 返回一個值,該值指示指定標頭及其值是否已添加到HttpHeaders 集合,而未驗證所提供的信息。 |
Clear | 從 HttpHeaders 集合中移除所有標頭。 |
Remove | 從HttpHeaders集合中移除指定的標頭。 |
GetValues | 返回存儲在HttpHeaders 集合中所有指定標頭的標頭值。 |
Contains | 如果指定標頭存在于 HttpHeaders 集合則返回。 |
ToString | 返回表示當前 HttpHeaders對象的字符串。 |
HttpHeaders是一個抽象類,HttpRequestHeaders、HttpResponseHeaders、HttpContentHeaders三個類繼承了該類。接下來我們來了解一下Add()方法:
[__DynamicallyInvokable] public void Add(string name, string value) { HeaderStoreItemInfo info; bool flag; this.CheckHeaderName(name); this.PrepareHeaderInfoForAdd(name, out info, out flag); this.ParseAndAddValue(name, info, value); if (flag && (info.ParsedValue != null)) { this.AddHeaderToStore(name, info); } }
Add()方法具有兩個重載版本,該方法可以向容器添加標頭,如果要添加的標頭有標準名,在添加之前標頭值會進行驗證。Add方法還會驗證標頭是否可以有多個值。
4.HTTP消息內容解析:
在.NET4.5版本的HTTP模型中,HTTP消息的正文由抽象基類HttpContent表示,HttpResponseMessage和HttpRequestMessage對象都包含一個HttpContent類型的Content屬性。
(1).HttpContent主要屬性和方法:
名稱 | 描述 |
ReadAsByteArrayAsync | 以異步操作將 HTTP 內容寫入字節數組。 |
SerializeToStreamAsync | 以異步操作將 HTTP 內容序列化到流。 |
CopyToAsync | 以異步操作將 HTTP 內容寫入流。 |
LoadIntoBufferAsync | 以異步操作將 HTTP 內容序列化到內存緩沖區。 |
CreateContentReadStreamAsync | 以異步操作將 HTTP 內容寫入內存流。 |
TryComputeLength | 確定 HTTP 內容是否具備有效的字節長度。 |
Headers | 根據 RFC 2616 中的定義,獲取內容標頭。 |
(2).CopyToAsync()方法解析:
[__DynamicallyInvokable] public Task CopyToAsync(Stream stream, TransportContext context) { Action<Task> continuation = null; this.CheckDisposed(); if (stream == null) { throw new ArgumentNullException("stream"); } TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); try { Task task = null; if (this.IsBuffered) { task = Task.Factory.FromAsync<byte[], int, int>(new Func<byte[], int, int,
AsyncCallback, object, IAsyncResult>(stream.BeginWrite), new Action<IAsyncResult>(stream.EndWrite),
this.bufferedContent.GetBuffer(), 0, (int) this.bufferedContent.Length, null); } else { task = this.SerializeToStreamAsync(stream, context); this.CheckTaskNotNull(task); } if (continuation == null) { continuation = delegate (Task copyTask) { if (copyTask.IsFaulted) { tcs.TrySetException(GetStreamCopyException(copyTask.Exception.GetBaseException())); } else if (copyTask.IsCanceled) { tcs.TrySetCanceled(); } else { tcs.TrySetResult(null); } }; } task.ContinueWithStandard(continuation); } catch (IOException exception) { tcs.TrySetException(GetStreamCopyException(exception)); } catch (ObjectDisposedException exception2) { tcs.TrySetException(GetStreamCopyException(exception2)); } return tcs.Task; }
在使用消息內容時,需要使用HtppContent的方法或者擴展方法。在HttpContent中利用CopyToAsync()方法以推送方式訪問原始的消息內容,由方法代碼可以看出,該方法接受兩個參數,一個是流對象,一個是有關傳輸的信息(例如,通道綁定),此參數可以為 null。該方法可以把消息內容寫入到這個流中。
在該方法的實現代碼中 創建了一個TaskCompletionSource<object>的泛型對象,該對象表示未綁定到委托的 Task<TResult> 的制造者方,并通過 Task 屬性提供對使用者方的訪問。SerializeToStreamAsync方法將傳入的流對象序列化,該方法為異步方法。
我們需要注意的幾點,主要為委托的創建和使用,在C#中,盡量使用有.NET提供的委托類,不要自己去創建。還有一點就是在程序中對異常的處理方式,異常的捕獲具有層次性,并且調用了自定義的一個異常處理方法TrySetException。
(2).ReadAsStreamAsync()方法解析:
在獲取原始消息內容時,除了調用上面介紹的方法外,還可以調用ReadAsStreamAsync()方法以拉取的方式訪問原始的消息內容。
在HttpContent中包含有另外兩個類似的方法,ReadAsStringAsync()和ReadAsByteArrayAsync()異步的提供消息內容的緩沖副本,ReadAsByteArrayAsync()返回原始的字節內容,ReadAsStringAsync()將內容解碼為字符串返回。
三.DotNet中新舊HTTP模型分析:
1..NET4.5之前版本創建HTTP POST請求實例:
public static string HttpPost(string postUrl, string postData) { if (string.IsNullOrEmpty(postUrl)) throw new ArgumentNullException(postUrl); if (string.IsNullOrEmpty(postData)) throw new ArgumentNullException(postData); var request = WebRequest.Create(postUrl) as HttpWebRequest; if (request == null) throw new ArgumentNullException("postUrl"); try { var cookieContainer = new CookieContainer(); request.CookieContainer = cookieContainer; request.AllowAutoRedirect = true; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; var data = Encoding.UTF8.GetBytes(postData); request.ContentLength = data.Length; var outstream = request.GetRequestStream(); outstream.Write(data, 0, data.Length); outstream.Close(); //發送請求并獲取相應回應數據,獲取對應HTTP請求的響應 var response = request.GetResponse() as HttpWebResponse; if (response != null) { var instream = response.GetResponseStream(); var content = string.Empty; if (instream == null) { return content; } using (var sr = new StreamReader(instream, Encoding.UTF8)) { content = sr.ReadToEnd(); } return content; } } catch (ArgumentException arex) { throw arex; } catch (IOException ioex) { throw ioex; } return null; }
2..NET4.5版本創建HTTP POST請求實例:
async static void getResponse(string url) { using (HttpClient client = new HttpClient()) { using (HttpResponseMessage response = await client.GetAsync(url)) { using (HttpContent content = response.Content) { string myContent = await content.ReadAsStringAsync(); } } } } async static void postResponse(string url) { while (true) { IEnumerable<KeyValuePair<string, string>> queries = new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string> ("test","test") }; HttpContent q = new FormUrlEncodedContent(queries); using (HttpClient client = new HttpClient()) { using (HttpResponseMessage response = await client.PostAsync(url, q)) { using (HttpContent content = response.Content) { string myContent = await content.ReadAsStringAsync(); Console.WriteLine(myContent); } } } } }
四.總結:
以上主要講解了.NET4.5之前和之后版本對HTTP編程模式的一些內容, 兩者的主要區別在于.NET4.5版本之前的HTTP編程模型會區分客戶端和服務器,兩者使用的對象存在不同,實現的原理上雖然存在一定的相似性,但是使用的類卻不同。.NET4.5之后的版本中,對象的使用沒有客戶端和服務器之分,兩者可以共用。
文章列表