天天看點

Asp.Net WebAPI核心對象解析(三)

   對于.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。兩個對象的結構如下圖:

Asp.Net WebAPI核心對象解析(三)

      以上主要講解了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 集合則傳回。
傳回表示目前 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 内容是否具備有效的位元組長度。
根據 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之後的版本中,對象的使用沒有用戶端和伺服器之分,兩者可以共用。

愛知求真,靜心鑽研,虛心學習,務實創新,細緻平和。