閱讀137 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Self Host模式下的ASP. NET Web API是如何進行請求的監聽與處理的?

構成ASP.NET Web API核心框架的消息處理管道既不關心請求消息來源於何處,也不需要考慮響應消息歸於何方。當我們采用Web Host模式將一個ASP.NET應用作為目標Web API的宿主時,實際上是由ASP.NET管道解決了這兩個問題。具體來說,ASP.NET自身的URL路由係統借助於HttpControllerHandler這個自定義的HttpHandler實現了ASP.NET管道和ASP.NET Web API管道之間的“連通”,但是在Self Host寄宿模式下,請求的監聽、接收和響應又是如何實現的呢?[本文已經同步到《How ASP.NET Web API Works?》]

目錄
一、HttpBinding模型
    Binding模型
    HttpBinding
    實例演示:直接利用HttpBinding進行請求的接收和響應
二、HttpSelfHostServer
    HttpSelfHostConfiguration
    HttpSelfHostServer與消息處理管道
    實例演示:創建自定義HttpServer模擬HttpSelfHostServer的工作原理

一、HttpBinding模型

和WCF服務一樣,我們可以采用Self Host模式將Web API寄宿於任何一種類型的托管應用程序下,宿主可以是一個Windows Form應用、WPF應用、控製台應用以及Windows Service。Self Host模式下的WCF和ASP.NET Web API不僅外在表現形式極為相似,其實在內部實現原理上也是一致的。

Binding模型

對於WCF具有基本了解的讀者應該都知道,它是一個基於消息的分布式通信框架,消息交換借助於客戶端和服務端對等的終結點(Endpoint)來完成,而終結點由經典的ABC(Address、Binding、Contract)三元素組成。WCF同樣具有一個處理消息的管道,這個管道是一組Channel的有序組合,WCF下的Channel相對於ASP.NET Web API下的HttpMessageHandler。

WCF的消息處理管道的締造者是作為終結點三要素之一的Binding。Binding不僅僅為服務端創建用於接收請求回複響應的管道,同時也為客戶端創建發送請求接收響應的管道。Binding模型本身也相對比較複雜,所以我們不可能對其進行詳細討論。如果讀者對此比較感興趣,可以參閱《WCF的綁定模型》。由於ASP.NET Web API隻是利用HttpBinding創建服務端消息處理管道,所以我們隻討論Binding的服務端模型。

從結構上講,一個Binding是若幹BindingElement對象的有序組合。對於最終創建的消息處理管道來說,每個Channel都對應著一個BindingElement。BindingElement並非直接創建對應的Channel,由它直接創建的實際上是一個名為ChannelListener的對象,Channel由ChannelListener創建。右圖基本揭示了Binding的服務端模型。

顧名思義,ChannelListener用於請求的監聽。當Binding對象開啟(調用其Open方法)時,每個BindingElement會創建各自的ChannelListener。這些ChannelListener按照對應BindingElement的順序連接成串,位於底部(麵向傳輸層)的ChannelListener被綁定到某個端口進行請求的監聽。一旦探測到抵達的請求,它會利用由所有ChannelListener創建的Channel組成的管道來接收並處理該請求。對於最終需要返回的響應消息,則按照從上到下的順序被這個管道進行處理並最終返回給客戶端。

對於這個由Channel組成消息處理管道來說,有兩種類型的Channel是必不可少的。一種是麵向傳輸層用於發送和接收消息的TransportChannel,另一種被稱為MessageEncodingChannel則負責對接收的消息實施解碼並對發送的消息實施編碼。TransportChannel由TransportChannelListener創建,而後者由TransportBindingElement創建。與之類似,MessageEncodingBindingElement是MessageEncodingChannelListener的創建者,而後者又是MessageEncodingChannel的創建者。

如果采用Self Host寄宿模式,請求的監聽是由一個類型為HttpBinding的Binding對象創建的ChannelListener管道來完成的,由它創建的管道實現了針對請求的接收和針對響應的回複。HttpBinding類型定義在“System.Web.Http.SelfHost.Channels”命名空間下,我們接下來對它進行詳細講述。

HttpBinding

Binding存在的目的在於創建用於處理和傳輸消息的信道棧,組成信道棧的每一個Channel均對應著一個BindingElement,所以Binding本身處理消息的能力由其BindingElement的組成來決定,我們可以通過分析BindingElement的組成來了解消息最終是如何處理的。現在我們就來討論一下ASP.NET Web API在Self Host模式下使用的HttpBinding由哪些BindingElement構成。

如左圖所示,HttpBinding僅僅由兩種必需的BindingElement構成,TransportBindingElement的類型決定於最終采用的傳輸協議。如果采用單純的HTTP協議,采用的TransportBindingElement是一個HttpTransportBindingElement對象。在采用HTTPS協議的情況下,TransportBindingElement的類型是HttpsTransportBindingElement。

我們現在著重來分析與消息編碼/解碼相關的BindingElement,從圖3-11可以看出這是一個HttpMessageEncodingBindingElement對象(HttpMessageEncodingBindingElement是一個定義在程序集“System.Web.Http.SelfHost.dll”中的內部類型),它最終會創建一個MessageEncoder對象完成針對消息的編碼/解碼工作。

ASP.NET Web API分別利用 HttpRequestMessage和HttpResponseMessage對象表示消息處理管道處理的請求和響應,而WCF消息處理管道的請求和響應均是一個Message對象(Message是定義在命名空間“System.ServiceModel.Channels”下的一個抽象類型)。經過HttpMessageEncoder解碼後的Message對象會轉成一個HttpRequestMessage對象並傳入ASP.NET Web API消息處理管道進行處理,由此管道返回的HttpResponseMessage對象需要轉換成一個Message對象並由HttpMessageEncoder根據需求進行解碼。

這個具體的消息實際上是一個HttpMessage對象,HttpMessage繼承自抽象類Message,它是一個定義在程序集“System.Web.Http.SelfHost.dll”中的內部類型。如下麵的代碼片斷所示,HttpMessage實際上是對一個HttpRequestMessage或者HttpResponseMessage對象的封裝,兩個方法GetHttpRequestMessage和GetHttpResponseMessage分別用於提取被封裝的HttpRequestMessage和HttpResponseMessage對象。

   1: internal sealed class HttpMessage : Message
   2: {
   3:     //其他成員   
   4:     public HttpMessage(HttpRequestMessage request);
   5:     public HttpMessage(HttpResponseMessage response);
   6:    
   7:     public HttpRequestMessage  GetHttpRequestMessage(bool extract);
   8:     public HttpResponseMessage GetHttpResponseMessage(bool extract);
   9: }

這兩個方法均具有一個布爾類型的參數extract,它表示是否“抽取”被封裝的HttpRequestMessage/HttpResponseMessage對象。如果指定的參數值為True,方法執行之後被封裝的HttpRequestMessage/HttpResponseMessage對象會從HttpMessage對象中抽取出來,所以再次調用它們會返回Null。

再次將我們的關注點拉回到由HttpBinding創建的消息處理管道上。當我們開啟HttpBinding後,它利用創建的ChannelListener管道監聽請求。一旦探測到抵達的請求後,基於HTTP/HTTPS協議的TransportChannel會負責接收請求。接收的二進製數據會由MessageEncoder解碼後生成一個HttpRequestMessage對象,該對象進而被封裝成一個HttpMessage對象,傳入消息處理管道的HttpRequestMessage是直接通過調用GetHttpRequestMessage方法從該HttpMessage對象中提取的。

當ASP.NET Web API消息處理管道完成了請求的處理並最終輸出一個HttpResponseMessage對象後,該對象同樣先被封裝成一個HttpMessage對象。在通過傳輸層將響應返回給客戶端之前,需要利用MessageEncoder對其進行編碼,而解碼的內容實際上就是調用GetHttpResponseMessage方法提取的HttpResponseMessage對象。

實例演示:直接利用HttpBinding進行請求的接收和響應

當我們采用Self Host寄宿模式將一個非Web應用程序作為目標Web API的宿主時,最終網絡監聽任務實際上是由HttpBinding創建的ChannelListener管道來完成的,而ChannelListener管道創建的消息處理管道最終實現了對請求的接收和對響應的發送。為了讓讀者對此具有深刻的認識,我們通過一個簡單的實例來演示如何直接使用HttpBinding實現對請求的監聽、接收和響應。

我們創建一個空的控製台程序作為監聽服務器,它相當於Self Host寄宿模式下的宿主程序。如下麵的代碼片斷所示,我們創建了一個HttpBinding,並指定監聽地址("https://127.0.0.1:3721")調用其BuildChannelListener<IReplyChannel>方法創建了一個ChannelListener管道(返回的是組成管道的第一個ChannelListener對象)。在調用Open方法開啟該ChannelListener管道之後,我們調用其AcceptChannel方法創建了消息處理管道,返回的是組成管道的第一個Channel對象。在Open方法將其開啟後,我們在一個While循環中調用Channel對象的ReceiveRequest方法進行請求的監聽和接收。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Uri listenUri = new Uri("https://127.0.0.1:3721");
   6:         Binding binding = new HttpBinding();
   7:  
   8:         //創建、開啟信道監聽器
   9:         IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri);
  10:         channelListener.Open();
  11:  
  12:         //創建、開啟回複信道
  13:         IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue);
  14:         channel.Open();
  15:  
  16:         //開始監聽
  17:         while (true)
  18:         {
  19:             //接收輸出請求消息
  20:             RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue);
  21:             PrintRequestMessage(requestContext.RequestMessage);
  22:             //消息回複
  23:             requestContext.Reply(CreateResponseMessage());
  24:         }
  25:     }
  26: }

對於成功接收的消息,我們調用具有如下定義的PrintRequestMessage方法將相關的信息打印在控製台上。通過上麵的介紹我們知道這個接收到的消息實際上是一個HttpMessage對象,由於這是一個內部類型,所以我們隻能以反射的方式調用其GetHttpRequestMessage方法獲取被封裝的HttpRequestMessage對象。在得到表示請求的HttpRequestMessage對象之後,我們將請求地址和所有報頭輸出到控製台上。

   1: private static void PrintRequestMessage(Message message)
   2: {
   3:     MethodInfo method = message.GetType().GetMethod("GetHttpRequestMessage");
   4:     HttpRequestMessage request = (HttpRequestMessage)method.Invoke(message, new object[]{false});
   5:  
   6:     Console.WriteLine("{0, -15}:{1}", "RequestUri", request.RequestUri);
   7:     foreach (var header in request.Headers)
   8:     {
   9:         Console.WriteLine("{0, -15}:{1}", header.Key, string.Join("," ,header.Value.ToArray()));
  10:     }
  11: }

在對請求進行處理之後,我們需要創建一個Message對象對該請求予以響應,響應消息的創建是通過CreateResponseMessage方法完成的。如下麵的代碼片斷所示,我們首先創建了一個響應狀態為“200, OK”的HttpResponseMessage對象,並將其表示主體內容的Content屬性設置為一個ObjectContent<Employee>對象。由於我們采用MediaTypeFormatter類型為JsonMediaTypeFormatter,指定的Employee對象會以JSON格式進行序列化。最終的響應消息依然是一個HttpMessage對象,它是對我們創建的HttpResponseMessage對象的封裝。限於“內部類型”的限製,我們也不得不采用反射的方式來創建這麼一個HttpMessage對象。

   1: private static Message CreateResponseMessage()
   2: {
   3:     HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
   4:     Employee employee = new Employee("001","Zhang San","123456","zhangsan@gmail.com");
   5:     response.Content = new ObjectContent<Employee>(employee, new JsonMediaTypeFormatter());
   6:  
   7:     string httpMessageTypeName = "System.Web.Http.SelfHost.Channels.HttpMessage, System.Web.Http.SelfHost";
   8:     Type httpMessageType = Type.GetType(httpMessageTypeName);
   9:     return (Message)Activator.CreateInstance(httpMessageType, new object[] { response });
  10: }
  11:  
  12: public class Employee
  13: {
  14:     public string Id { get; set; }
  15:     public string Name { get; set; }
  16:     public string PhoneNo { get; set; }
  17:     public string EmailAddress { get; set; }
  18:  
  19:     public Employee(string id, string name, string phoneNo, string emailAddress)
  20:     {
  21:         this.Id              = id;
  22:         this.Name            = name;
  23:         this.PhoneNo         = phoneNo;
  24:         this.EmailAddress    = emailAddress;
  25:     }
  26: }

當我們直接運行該程序後,實際上就已經啟動了針對基地址"https://127.0.0.1:3721"的監聽器。現在我們通過瀏覽器對這個監聽器發起請求,為了使請求更像一個針對Web API的調用,我們將請求地址設置為“https://127.0.0.1:3721/employees/001”(看起來好像是獲取某個編號為001的員工信息)。如右圖所示,通過瀏覽器發送的請求相關信息會顯示在控製台上,而瀏覽器上也會顯示基於JSON格式的員工信息。

二、HttpSelfHostServer

ASP.NET Web API的Self Host寄宿模式是通過一個System.Web.Http.SelfHost.HttpSelfHostServer對象來完成的,那麼HttpSelfHostServer與上麵介紹的HttpBinding又有何關係呢?HttpSelfHostServer與ASP.NET Web API的消息處理管道又是如何集成的呢?

如下麵的代碼片斷所示,HttpSelfHostServer其實是HttpServer的子類,所以在Self Host模式下HttpSelfHostServer本身就是消息管道的組成部分。換句話說,以HttpSelfHostServer為“龍頭”的消息處理管道本身具有請求監聽、接收、處理和響應的能力。

   1: public sealed class HttpSelfHostServer : HttpServer
   2: {   
   3:     public HttpSelfHostServer(HttpSelfHostConfiguration configuration);
   4:     public HttpSelfHostServer(HttpSelfHostConfiguration configuration, HttpMessageHandler dispatcher);
   5:    
   6:     public Task OpenAsync();
   7:     public Task CloseAsync();
   8:    
   9:     protected override void Dispose(bool disposing);
  10: }

ASP.NET Web API的消息處理管道的配置利用通過HttpServer的Configuration屬性表示的HttpConfiguration對象來完成,對於HttpSelfHostServer來說,它的Configuration屬性返回一個HttpSelfHostConfiguration對象(HttpSelfHostConfiguration類型定義在 “System.Web.Http.SelfHost” 命名空間下)。如上麵的代碼片斷所示,當我們調用構造函數創建一個HttpSelfHostServer對象時,需要通過參數指定此HttpSelfHostConfiguration。

HttpSelfHostConfiguration

如下麵的代碼片斷所示,HttpSelfHostConfiguration直接繼承自HttpConfiguration。我們在創建一個HttpSelfHostConfiguration對象的時候需要指定一個Uri對象作為監聽基地址,這個地址通過隻讀屬性BaseAddress返回。

   1: public class HttpSelfHostConfiguration : HttpConfiguration
   2: {
   3:     public HttpSelfHostConfiguration(Uri baseAddress);
   4:     
   5:     public Uri                         BaseAddress { get; }
   6:     public HostNameComparisonMode      HostNameComparisonMode { get; set; }
   7:     public TransferMode                TransferMode { get; set; }
   8:  
   9:     public int      MaxBufferSize { get; set; }
  10:     public int      MaxConcurrentRequests { get; set; }
  11:     public long     MaxReceivedMessageSize { get; set; }
  12:  
  13:     public TimeSpan ReceiveTimeout { get; set; }
  14:     public TimeSpan SendTimeout { get; set; }
  15:  
  16:     public HttpClientCredentialType      ClientCredentialType { get; set; }
  17:     public UserNamePasswordValidator     UserNamePasswordValidator { get; set; }
  18:     public X509CertificateValidator      X509CertificateValidator { get; set; }
  19: }

由於Self Host寄宿模式下請求的監聽、接收和響應基本上全部是通過HttpBinding實現的,所以定義在HttpSelfHostConfiguration中的眾多屬性實際上基本都用於對創建的HttpBinding進行配置。從如下給出的代碼片斷可以看出HttpBinding類型與HttpSelfHostConfiguration具有類似的屬性定義。

   1: public abstract class Binding : IDefaultCommunicationTimeouts
   2: {
   3:     //其他成員
   4:     public TimeSpan ReceiveTimeout { get;  set; }
   5:     public TimeSpan SendTimeout { get;  set; }
   6: }
   7:  
   8: public class HttpBinding : Binding, IBindingRuntimePreferences
   9: {
  10:     //其他成員
  11:     public HostNameComparisonMode HostNameComparisonMode { get; set; }
  12:     public TransferMode TransferMode { get; set; }
  13:  
  14:     public long     MaxBufferPoolSize { get; set; }
  15:     public int      MaxBufferSize { get; set; }
  16:     public long     MaxReceivedMessageSize { get; set; }    
  17:  
  18:     public HttpBindingSecurity Security { get; set; }
  19: }

由於Binding在WCF是一個核心組件,其設計本身相對複雜,要深入了解定義在HttpBinding中的這些屬性需要相關的背景知識。篇幅所限,我們不能因為這些屬性將Binding相關的內容全部搬過來,所以在這裏我們僅僅通過下表對它們進行概括性的介紹。

屬性

描述

HostNameComparisonMode

如果請求URL沒有指定服務器的IP地址而是主機名稱,當從URL提取主機名稱後會按照相應的比較模式來最終確定匹配的主機名。該屬性的類型為System.ServiceModel.HostNameComparisonMode枚舉,用以確定主機名比較模式。

TransferMode
MaxBufferSize

消息傳輸具有Streamed和Buffered兩種模式,前者以流的形式進行消息傳輸,後者則將整個消息的內容先保存於內存緩衝區後一並傳輸。該屬性類型為System.ServiceModel.TransferMode枚舉,用以控製針對請求消息和響應消息的傳輸模式。在默認情況下,請求消息和響應消息均以Buffered模式進行傳輸。MaxBufferSize屬性表示在采用Buffered模式下消息最大緩衝大小,默認值為65536(0x10000)。

MaxConcurrentRequests
MaxReceivedMessageSize

這兩個是針對請求的限製,分別表示運行的最大並發訪問量和請求消息允許的最大尺寸。兩者的默認值分別為100和65536(0x10000)。值得一提的是MaxConcurrentRequests針對最大並發請求的限製是針對單個處理器設定的,對於多處理器或者多核處理來說,應該乘以處理器的數量。

ReceiveTimeout
SendTimeout

這兩個屬性分別表示接收請求消息和發送響應消息的超時時限,默認值分別為10分鍾和1分鍾。

ClientCredentialType
UserNamePasswordValidator
X509CertificateValidator

這是三個與安全認證相關的屬性。ClientCredentialType表示客戶端采用的用戶憑證類型,而UserNamePasswordValidator和X509CertificateValidator屬性值分別在用戶憑證為“用戶名+密碼”和“X.509證書”的情況下實施認證。

HttpSelfHostServer與消息處理管道

在采用Self Host模式寄宿Web API時,我們會根據指定的監聽基地址創建一個HttpSelfHostConfiguration對象,然後據此創建HttpSelfHostServer。當我們調用方法OpenAsync開啟它時,HttpSelfHostServer會創建一個HttpBinding對象,並根據指定的HttpSelfHostConfiguration對其作相應的設置。隨後HttpBinding會針對指定的監聽地址創建一個ChannelListener管道,並調用其BeginOpen方法以異步的方式開啟。除了OpenAsync方法外,HttpSelfHostServer還定義了一個CloseAsync方法使我們可以以異步的方式關閉已經開啟的ChannelListener管道。

一旦ChannelListener管道被成功開啟後,它便綁定到指定的監聽地址進行請求的監聽。當抵達的請求被探測到,它會利用創建的消息處理管道來接收該請求。通過上麵對HttpBinding的介紹我們知道,接收到的二進製數據經過解碼之後會生成一個HttpMessage對象,後者是對一個HttpRequestMessage的封裝。

接下來HttpSelfHostServer從生成的HttpMessage中提取被封裝的HttpRequestMessage對象,並直接分發給後續的HttpMessageHandler作進一步處理。對於最終返回的表示響應的HttpResponseMessage對象,HttpSelfHostServer將其封裝成一個HttpMessage對象並利用消息處理管道返回給客戶端。在通過傳輸層發送響應消息之前,HttpMessage會先編碼。通過上麵的介紹我們知道整個編碼工作完全是針對被HttpMessage封裝的HttpResponseMessage對象進行的,在HttpResponseMessage中保存的響應內容就是客戶端接收到的內容。左圖基本揭示了Self Host寄宿模式下整個消息處理管道的結構。

 

實例演示:創建自定義HttpServer模擬HttpSelfHostServer的工作原理

通過上麵的介紹,我想讀者朋友們應該對Self Host模式下消息處理管道如何進行請求的監聽、接收、處理和響應已經有了全麵的了解。如果我們能夠創建一個自定義的HttpServer來模擬HttpSelfHostServer的工作原理,我想大家對此的印象一定更加深刻。在本章內容即將完結之前,我們就來完成這麼一個演示實例。

我們通過繼承HttpServer創建如下一個用於模擬HttpSelfHostServer的MyHttpSelfHostServer類型。兩者對於請求的監聽、接收和響應的實現原理是一致的,不同之處在於HttpSelfHostServer基本采用異步的操作方式,MyHttpSelfHostServer采用同步編程方式。

   1: public class MyHttpSelfHostServer: HttpServer
   2: {
   3:     public Uri BaseAddress { get; private set; }
   4:     public IChannelListener<IReplyChannel> ChannelListener { get; private set; }
   5:  
   6:     public MyHttpSelfHostServer(HttpConfiguration configuration, Uri baseAddress)
   7:         : base(configuration)
   8:     {
   9:         this.BaseAddress = baseAddress;
  10:     }
  11:  
  12:     public void Open()
  13:     {
  14:         HttpBinding binding = new HttpBinding();
  15:         this.ChannelListener = binding.BuildChannelListener<IReplyChannel>(this.BaseAddress);
  16:         this.ChannelListener.Open();
  17:  
  18:         IReplyChannel channnel = this.ChannelListener.AcceptChannel();
  19:         channnel.Open();
  20:  
  21:         while (true)
  22:         {
  23:             RequestContext requestContext = channnel.ReceiveRequest(TimeSpan.MaxValue);
  24:             Message message = requestContext.RequestMessage;
  25:             MethodInfo method = message.GetType().GetMethod("GetHttpRequestMessage");
  26:             HttpRequestMessage request = (HttpRequestMessage)method.Invoke(message, new object[] {true});
  27:             Task<HttpResponseMessage> processResponse = base.SendAsync(request, new CancellationTokenSource().Token);
  28:             processResponse.ContinueWith(task =>
  29:                 { 
  30:                     string httpMessageTypeName = "System.Web.Http.SelfHost.Channels.HttpMessage, System.Web.Http.SelfHost";
  31:                     Type httpMessageType = Type.GetType(httpMessageTypeName);
  32:                     Message reply = (Message)Activator.CreateInstance(httpMessageType, new object[] { task.Result });
  33:                     requestContext.Reply(reply);
  34:                 });
  35:         }
  36:     }
  37:  
  38:     public void Close()
  39:     {
  40:         if (null != this.ChannelListener && this.ChannelListener.State == CommunicationState.Opened)
  41:         {
  42:             this.ChannelListener.Close();
  43:         }
  44:     }
  45: }

MyHttpSelfHostServer的隻讀屬性BaseAddress表示監聽基地址,該屬性直接在構造函數中指定。與HttpSelfHostServer不同的是,用於創建MyHttpSelfHostServer提供的配置對象是一個HttpConfiguration對象而不再是HttpSelfHostConfiguration。

在Open方法中,我們根據提供的監聽基地址利用HttpBinding對象創建一個ChannelListener對象,MyHttpSelfHostServer的隻讀屬性ChannelListener引用的也正是這個對象。在開啟該ChannelListener之後,我們調用其AccpetChannel方法創建信道棧,最終返回位於棧頂的Channel。在該Channel開啟的情況下,我們在一個“永不終止”的While循環中調用其ReceiveRequest方法進行請求的監聽。

當信道棧成功接收請求消息後(這是一個HttpMessage對象),我們從中提取出被封裝的HttpRequestMessage對象,並將其作為參數調用SendAsync方法,表示請求的HttpReuqestMessage自此進入了消息處理管道這個流水車間。

調用SendAsync方法返回的是一個Task<HttpResponseMessage>對象,我們執行這個Task對象並獲得表示響應的HttpResponseMessage對象,然後以反射的形式將其封裝成HttpMessage對象。我們最終利用此HttpMessage對象對請求作最終的響應。HttpSelfHostServer定義了OpenAsync和CloseAsync方法開啟和關閉監聽器,與之相匹配,我們也為Open方法定義了匹配的Close方法來關閉已經開啟的ChannelListener。

為了驗證我們自定義的MyHttpSelfHostServer是否能夠替代“原生”的HttpSelfHostServer,我們在一個控製台中定義了如下一個繼承自ApiController的ContactsController。它具有兩個重載的Action方法Get,前者用於返回所有的聯係人列表,後者返回指定ID的某個聯係人信息。

   1: public class ContactsController : ApiController
   2: {
   3:     private static List<Contact> contacts = new List<Contact>
   4:     {
   5:         new Contact{ Id="001", Name = "張三",  PhoneNo="123", EmailAddress="zhangsan@gmail.com"},
   6:         new Contact{ Id="002",Name = "李四", PhoneNo="456", EmailAddress="lisi@gmail.com"}
   7:     };
   8:  
   9:     public IEnumerable<Contact> Get()
  10:     {
  11:         return contacts;
  12:     }
  13:  
  14:     public Contact Get(string id)
  15:     {
  16:         return contacts.FirstOrDefault(c => c.Id == id);
  17:     }
  18: }
  19:  
  20: public class Contact
  21: {
  22:     public string Id { get; set; }
  23:     public string Name { get; set; }
  24:     public string PhoneNo { get; set; }
  25:     public string EmailAddress { get; set; }
  26: }

在作為入口的Main方法中我們編寫了如下一段簡單的“寄宿”程序。我們根據創建的HttpConfiguration對象和指定的監聽基地址(“https://127.0.0.1:3721”)創建了一個MyHttpSelfHostServer對象。在調用Open方法開始監聽之前,我們注冊了一個URL模板為“https://127.0.0.1:3721”的HttpRoute。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         using (MyHttpSelfHostServer httpServer = new MyHttpSelfHostServer(new HttpConfiguration(), new Uri("https://127.0.0.1:3721")))
   6:         {
   7:             httpServer.Configuration.Routes.MapHttpRoute(
   8:                 name             : "DefaultApi",
   9:                 routeTemplate    : "api/{controller}/{id}",
  10:                 defaults         : new { id = RouteParameter.Optional });
  11:  
  12:             httpServer.Open();
  13:             Console.Read();
  14:         }
  15:     }
  16: }

運行該程序之後,這個“宿主”程序便開始進行請求的監聽。現在我們直接利用瀏覽器對定義在ContactsController中的兩個Action方法Get發起請求,通過注冊的HttpRoute和“請求的HTTP方法直接作為Action名稱”的原理,我們使用的URL分別為“https://127.0.0.1:3721/api/contacts”和“https://127.0.0.1:3721/api/contacts/001”。如右圖所示,我們期望的聯係人信息直接以XML的形式顯示在瀏覽器中,由此可見我們自定義的MyHttpSelfHostServer“不辱使命”。



作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 15:04:12

  上一篇:go  如果調用ASP.NET Web API不能發送PUT/DELETE請求怎麼辦?
  下一篇:go  專訪深鑒CEO姚頌:把芯片的專用和通用看作兩極,那麼中間任何位置都可有所作為