在接着寫Asp.Net WebApi核心對象解析(下篇)之前,還是一如既往的扯扯淡,元旦剛過,整個人還是處於暈的狀態,一大早就來處理系統BUG,簡直是坑爹(好在沒讓我元旦趕過來該BUG),隊友挖的坑,還讓我含着淚去填。改BUG前看隊友寫的代碼,這里就不評價了,反正是邊改邊罵,我的嘴巴就沒停過,作為開發者,我那時的心情,就不再描述了,反正是找不到一個好詞形容。
新年展望,我感覺我是沒啥好展望的,反正去年的展望是一個都沒實現,事情該怎么做還是怎么做的,估計大多數人跟我差不多,任何事不能強求,事前努力去辦,事后得不到也就看淡一點,太苛求,遲早身心俱疲。
扯淡完畢,接着聊正事,上一篇寫的是Asp.Net WebApi核心對象解析(上篇),本文是下篇,不管寫的怎么樣,還望大家多多指正。
一.WebApi處理架構:
我們在學習Asp.Net WebApi時,應該對Asp.Net WebApi的內部運行機制有一個大致的了解,很多人說了解這些基本原理的意義不大,實際開發中應用不到而且還浪費時間,這樣說有一定的道理,但是如果我們的眼光放的長遠一些,就不會這樣想聊,我們了解基本原理后,可以在一定的程度上幫助我們處理一些程序底層的bug,而且還有可以讓我們從中學會思考,去深入的理解設計者的意圖,有利於我們更加熟練的運用。
在談WebApi處理架構之前,我們還是來看一下微軟為WebApi提供的海報,這里就不拿圖了,需要看的可以點擊下載:下載地址
Asp.Net Web Api處理架構可以分為三層,分別是托管層、消息處理程序管道、控制器處理。
托管層:位於WebApi和底層HTTP棧之間,是最底層負責WebApi托管。
消息處理程序管道層:用與實現消息的橫切關注點,例如日志和緩存。
控制器處理層:控制器和操作是在這一層進行調用,參數再次綁定和驗證,HTTP響應消息也在這里創建。
對於托管層測說明,會在下面進行講解。消息處理程序是對一個操作的抽象,它接受HTTP請求消息並返回HTTP響應消息。連接消息處理程序管道和控制器處理層的橋梁是控制器分發程序。控制器分發還是一個消息處理程序,主要是選擇、創建和調用正確的控制器來處理請求。
二.WebApi托管方式解析:
在Asp.Net Web Api的托管方式有三種,接下來我們來大致了解一下這三種托管方式。
(1).在任何Windows進程中自托管。
(2).Web托管,即在IIS之上使用ASP.NET管道進行托管。(如果需要了解IIS和ASPI.NET管道的知識,可以自己搜索查看,筆者建議做web開發的人員了解一下其運行機制,有利於我們對asp.net web程序有一個深入的了解。)
(3).OWIN托管。(在一個owin兼容的服務器上建立一個webapi層)
在使用web托管時,所使用的是ASP.NET的管道和路由功能,將HTTP請求轉發到一個新的ASP.NET處理程序,HttpControllerHandler中。這個程序接收到HtppRequest實例轉換成HttpRequestMesssage實例,然后推送到WebApi管道,從而在傳統的asp.net管道和新的asp.net webapi架構間建立起鏈接。
這里我們具體了解一下HttpControllerHandler這個類:
HttpControllerHandler類在 System.Web.Http.WebHost命名空間下,根據命名空間的名稱,我們就可以清晰的了解到該命名空間主要用於創建web托管的。
public class HttpControllerHandler : IHttpAsyncHandler, IHttpHandler
該類繼承自IHttpAsyncHandler, IHttpHandler接口,我們由底層代碼可知,該類實際具體繼承自HttpTaskAsyncHandler類,用與Http任務異步處理程序。接下來我們具體看一下該類的一些方法:
1.ProcessRequestAsync方法:提供處理異步任務的代碼。
/// <summary>
/// 提供處理異步任務的代碼
/// </summary>
/// <returns>
/// 異步任務。
/// </returns>
/// <param name="context">HTTP 上下文。</param>
public override Task ProcessRequestAsync(HttpContext context)
{
return this.ProcessRequestAsyncCore((HttpContextBase) new HttpContextWrapper(context));
}
internal async Task ProcessRequestAsyncCore(HttpContextBase contextBase)
{
HttpRequestMessage request = HttpContextBaseExtensions.GetHttpRequestMessage(contextBase) ?? HttpControllerHandler.ConvertRequest(contextBase);
System.Net.Http.HttpRequestMessageExtensions.SetRouteData(request, this._routeData);
CancellationToken cancellationToken = HttpResponseBaseExtensions.GetClientDisconnectedTokenWhenFixed(contextBase.Response);
HttpResponseMessage response = (HttpResponseMessage) null;
try
{
response = await this._server.SendAsync(request, cancellationToken);
await HttpControllerHandler.CopyResponseAsync(contextBase, request, response, HttpControllerHandler._exceptionLogger.Value, HttpControllerHandler._exceptionHandler.Value, cancellationToken);
}
catch (OperationCanceledException ex)
{
contextBase.Request.Abort();
}
finally
{
System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources(request);
request.Dispose();
if (response != null)
response.Dispose();
}
}
該方法是一個異步方法,並且接收的參數是HttpContext,表示http上下文內容,調用GetHttpRequestMessage()獲取HttpRequestMessage對象實例,調用SetRouteData()方法設置路由信息,調用GetClientDisconnectedTokenWhenFixed()方法獲取客戶端斷開令牌時修復,並返回取消令牌,該方法生成http請求后,對消息進行異步發送處理操作。
2.GetStreamContent方法:獲取請求獲取流內容。
private static HttpContent GetStreamContent(HttpRequestBase requestBase, bool bufferInput)
{
if (bufferInput)
return (HttpContent) new HttpControllerHandler.LazyStreamContent((Func<Stream>) (() =>
{
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.None)
return (Stream) new SeekableBufferedRequestStream(requestBase);
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.Classic)
{
requestBase.InputStream.Position = 0L;
return requestBase.InputStream;
}
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.Buffered)
{
if (requestBase.GetBufferedInputStream().Position <= 0L)
return (Stream) new SeekableBufferedRequestStream(requestBase);
requestBase.InputStream.Position = 0L;
return requestBase.InputStream;
}
throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentCulture, SRResources.RequestBodyAlreadyReadInMode, new object[1]
{
(object) ReadEntityBodyMode.Bufferless
}));
}));
return (HttpContent) new HttpControllerHandler.LazyStreamContent((Func<Stream>) (() =>
{
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.None)
return requestBase.GetBufferlessInputStream();
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.Classic)
throw new InvalidOperationException(SRResources.RequestStreamCannotBeReadBufferless);
if (requestBase.ReadEntityBodyMode == ReadEntityBodyMode.Bufferless)
{
Stream bufferlessInputStream = requestBase.GetBufferlessInputStream();
if (bufferlessInputStream.Position > 0L)
throw new InvalidOperationException(SRResources.RequestBodyAlreadyRead);
return bufferlessInputStream;
}
throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentCulture, SRResources.RequestBodyAlreadyReadInMode, new object[1]
{
(object) ReadEntityBodyMode.Buffered
}));
}));
}
該方法用與獲取HTTP請求的流內容,根據參數HttpRequestBase可知,該方法接受到HTTP請求后,對消息進行處理,bufferInput參數判斷傳入的是否為流對象,傳入的流對象,進入LazyStreamContent類進行處理,LazyStreamContent類的構造函數接受一個含有返回值的委托。
public LazyStreamContent(Func<Stream> getStream)
{
this._getStream = getStream;
}
GetStreamContent方法的相關操作主要是對HTTP請求內容的解析操作。
三.WebApi核心對象HttpRequestMessage和HttpResponseMessage:
1.HttpRequestMessageExtensions:HTTP消息請求實例的擴展類。
(1).GetRequestContext方法:獲取HTTP請求消息內容:
public static HttpRequestContext GetRequestContext(this HttpRequestMessage request)
{
if (request == null)
throw Error.ArgumentNull("request");
return HttpRequestMessageExtensions.GetProperty<HttpRequestContext>(request, HttpPropertyKeys.RequestContextKey);
}
根據傳入的HTTP請求,調用GetProperty()方法獲取屬性,我們具體看一下GetProperty()方法:
private static T GetProperty<T>(this HttpRequestMessage request, string key)
{
T obj;
DictionaryExtensions.TryGetValue<T>(request.Properties, key, out obj);
return obj;
}
該方法獲取請求對象,並根據KEY值調用TryGetValue()方法獲取屬性。
(2).CreateResponse():創建請求信息的響應。
/// <summary>
/// 創建與關聯的 HttpRequestMessage連接的HttpResponseMessage
/// </summary>
/// <returns>
/// 與關聯的 HttpRequestMessage連接的已初始化 HttpResponseMessage
/// </returns>
/// <param name="request">導致此響應消息的 HTTP 請求消息。</param>
<param name="statusCode">HTTP 響應狀態代碼。</param>
<param name="value">HTTP 響應消息的內容。</param>
<param name="configuration">包含用於解析服務的依賴關系解析程序的 HTTP 配置。</param>
<typeparam name="T">HTTP 響應消息的類型。</typeparam>
public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request, HttpStatusCode statusCode, T value, HttpConfiguration configuration)
{
if (request == null)
throw Error.ArgumentNull("request");
configuration = configuration ?? HttpRequestMessageExtensions.GetConfiguration(request);
if (configuration == null)
throw Error.InvalidOperation(SRResources.HttpRequestMessageExtensions_NoConfiguration);
IContentNegotiator contentNegotiator = ServicesExtensions.GetContentNegotiator(configuration.Services);
if (contentNegotiator == null)
throw Error.InvalidOperation(SRResources.HttpRequestMessageExtensions_NoContentNegotiator, (object) typeof (IContentNegotiator).FullName);
IEnumerable<MediaTypeFormatter> formatters = (IEnumerable<MediaTypeFormatter>) configuration.Formatters;
return NegotiatedContentResult<T>.Execute(statusCode, value, contentNegotiator, request, formatters);
}
該方法根據請求消息提供的相關信息,在處理完畢請求消息后,創建響應消息內容。
2.HttpResponseMessageExtensions:HTTP應答消息實例的擴展類。
TryGetContentValue():獲取內容的值。
/// <summary>
/// 嘗試檢索HttpResponseMessageExtensions 的內容的值。
/// </summary>
/// <returns>
/// 內容值的檢索結果。
/// </returns>
/// <param name="response">操作的響應。</param>
<param name="value">內容的值。</param>
<typeparam name="T">要檢索的值的類型。</typeparam>
public static bool TryGetContentValue<T>(this HttpResponseMessage response, out T value)
{
if (response == null)
throw Error.ArgumentNull("response");
ObjectContent objectContent = response.Content as ObjectContent;
if (objectContent != null && objectContent.Value is T)
{
value = (T) objectContent.Value;
return true;
}
value = default (T);
return false;
}
根據傳入的響應消息對象獲取響應消息的內容。
四.WebApi核心對象HttpClient:
上面介紹完服務器端的接收和響應HTTP請求的操作方法,接下來介紹一個客戶端生成HTTP請求,用與請求和獲取服務器返回的消息,在新版本的.NET中,提供類HTTPClient類用來在客戶端生成和獲取HTTP請求的類。
1.屬性概要:
BaseAddress:獲取或設置發送請求時所使用的互聯網資源的統一資源標識符(URI)的基地址。
DefaultRequestHeaders:獲取應隨每個請求發送的頭。
MaxResponseContentBufferSize:獲取或設置中的最大字節數讀取響應內容時緩沖。
Timeout:獲取或設置的毫秒數請求超時之前等待。
2.方法概要:
CancelPendingRequests:取消此實例上的所有未決請求。
DeleteAsync(String):發送一個DELETE請求到指定的URI為異步操作。
GetAsync(String):發送GET請求到指定的URI為異步操作。
GetStreamAsync(String):發送GET請求到指定的URI並返回響應主體作為一個異步操作流。
PostAsync(String, HttpContent):發送POST請求到指定的URI作為一個異步操作。
SendAsync(HttpRequestMessage):發送一個HTTP請求作為一個異步操作。
3.方法和屬性解析:
(1).BaseAddress:獲取或設置發送請求時所使用的互聯網資源的統一資源標識符(URI)的基地址。
/// <summary>
/// 獲取或設置發送請求時使用的 Internet 資源的統一資源標識符 (URI) 的基址。
/// </summary>
/// <returns>
/// 返回 <see cref="T:System.Uri"/>。發送請求時使用的 Internet 資源的統一資源標識符 (URI) 的基址。
/// </returns>
[__DynamicallyInvokable]
public Uri BaseAddress
{
[__DynamicallyInvokable] get
{
return this.baseAddress;
}
[__DynamicallyInvokable] set
{
HttpClient.CheckBaseAddress(value, "value");
this.CheckDisposedOrStarted();
if (Logging.On)
Logging.PrintInfo(Logging.Http, (object) this, "BaseAddress: '" + (object) this.baseAddress + "'");
this.baseAddress = value;
}
}
(2).GetContentAsync:根據指定的uri異步的獲取內容。
private Task<T> GetContentAsync<T>(Uri requestUri, HttpCompletionOption completionOption, T defaultValue, Func<HttpContent, Task<T>> readAs)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
HttpUtilities.ContinueWithStandard<HttpResponseMessage>(this.GetAsync(requestUri, completionOption), (Action<Task<HttpResponseMessage>>) (requestTask =>
{
if (HttpClient.HandleRequestFaultsAndCancelation<T>(requestTask, tcs))
return;
HttpResponseMessage result = requestTask.Result;
if (result.Content == null)
{
tcs.TrySetResult(defaultValue);
}
else
{
try
{
HttpUtilities.ContinueWithStandard<T>(readAs(result.Content), (Action<Task<T>>) (contentTask =>
{
if (HttpUtilities.HandleFaultsAndCancelation<T>((Task) contentTask, tcs))
return;
tcs.TrySetResult(contentTask.Result);
}));
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
}));
return tcs.Task;
}
該方法為異步的方法,用與生成get、post請求后,獲取對應的內容。TrySetResult()方法將底層System.Threading.Tasks.Task`1轉換為RanToCompletion狀態。
(3).SendAsync(): 以異步操作發送 HTTP 請求。
/// <summary>
/// 以異步操作發送 HTTP 請求。
/// </summary>
/// <returns>
/// 返回 <see cref="T:System.Threading.Tasks.Task`1"/>。表示異步操作的任務對象。
/// </returns>
/// <param name="request">要發送的 HTTP 請求消息。</param>
<param name="completionOption">操作應完成時(在響應可利用或在讀取整個響應內容之后)。</param>
<param name="cancellationToken">取消操作的取消標記。</param>
<exception cref="T:System.ArgumentNullException">
<paramref name="request"/> 為 null。</exception>
<exception cref="T:System.InvalidOperationException">請求消息已由 <see cref="T:System.Net.Http.HttpClient"/> 實例發送。</exception>
[__DynamicallyInvokable]
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
if (request == null)
throw new ArgumentNullException("request");
this.CheckDisposed();
HttpClient.CheckRequestMessage(request);
this.SetOperationStarted();
this.PrepareRequestMessage(request);
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.pendingRequestsCts.Token);
this.SetTimeout(linkedCts);
TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
HttpUtilities.ContinueWithStandard<HttpResponseMessage>(base.SendAsync(request, linkedCts.Token), (Action<Task<HttpResponseMessage>>) (task =>
{
try
{
this.DisposeRequestContent(request);
if (task.IsFaulted)
this.SetTaskFaulted(request, linkedCts, tcs, task.Exception.GetBaseException());
else if (task.IsCanceled)
{
this.SetTaskCanceled(request, linkedCts, tcs);
}
else
{
HttpResponseMessage result = task.Result;
if (result == null)
this.SetTaskFaulted(request, linkedCts, tcs, (Exception) new InvalidOperationException(SR.net_http_handler_noresponse));
else if (result.Content == null || completionOption == HttpCompletionOption.ResponseHeadersRead)
this.SetTaskCompleted(request, linkedCts, tcs, result);
else
this.StartContentBuffering(request, linkedCts, tcs, result);
}
}
catch (Exception ex)
{
if (Logging.On)
Logging.Exception(Logging.Http, (object) this, "SendAsync", ex);
tcs.TrySetException(ex);
}
}));
return tcs.Task;
}
該方法是以異步發的方法將HTTP請求發送出去,該方法的三個參數中,HttpRequestMessage表示http請求對象,HttpCompletionOption表示操作完成項,CancellationToken表示取消令牌。在發送HTTP請求之前,調用CheckRequestMessage方法對消息進行檢查。在使用異步方法時,需要考慮操作的取消等外部因素對方法的影響。
介紹完畢HttpClient對象,對於HttpClient的實際操作就不做介紹,HttpClient對象的使用非常的簡單,但是該類的底層實現還是比較的復雜。
五.總結:
本文分為上下兩篇,簡單的介紹類一下Asp.Net WebApi的一些核心對象,並簡單介紹了Asp.Net WebApi路由機制,處理架構,托管方式等等,如有不足和錯誤之處還望多多指正。(對我來說總算是寫完了,寫了上篇就得寫下篇,實在痛苦)

