在前面對管道、路由有了基礎的了解過後,本篇将帶大家一起學習一下在ASP.NET Web API中控制器的建立過程,這過程分為幾個部分下面的内容會為大家講解第一個部分,也是ASP.NET Web API架構跟ASP.NET MVC架構實作上存在不同的一部分。
ASP.NET Web API 控制器建立過程(一)
ASP.NET Web API 控制器建立過程(二)
未完待續
在項目運用中,我們大多數會把控制器部分從主程式抽離出來放置單獨的項目中,這種情況下在使用ASP.NET MVC架構的項目環境中是不會有什麼問題的,因為MVC架構在建立控制器的時候會加載目前主程式引用的所有程式集并且按照執行的搜尋規則(公共類型、實作IController的)搜尋出控制器類型并且緩存到xml檔案中。而這種方式如果在使用了預設的ASP.NET Web API架構環境下就會有一點點的問題,這裡就涉及到了Web API架構的控制器建立過程中的知識。來看一下簡單的示例。
(示例還是《ASP.NET Web API 開篇介紹示例》中的示例,不過做了略微的修改,符合上述的情況。)
我們還是在SelfHost環境下做示例,來看SelfHost環境下服務端配置:
示例代碼1-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<code> </code><code>classProgram</code>
<code> </code><code>{</code>
<code> </code><code>staticvoidMain(</code><code>string</code><code>[] args)</code>
<code> </code><code>{</code>
<code> </code><code>HttpSelfHostConfigurationselfHostConfiguration=</code>
<code> </code><code>newHttpSelfHostConfiguration(</code><code>"http://localhost/selfhost"</code><code>);</code>
<code> </code><code>using</code> <code>(HttpSelfHostServerselfHostServer=newHttpSelfHostServer(selfHostConfiguration))</code>
<code> </code><code>{</code>
<code> </code><code>selfHostServer.Configuration.Routes.MapHttpRoute(</code>
<code> </code><code>"DefaultApi"</code><code>, </code><code>"api/{controller}/{id}"</code><code>, </code><code>new</code> <code>{ id=RouteParameter.Optional });</code>
<code> </code>
<code> </code><code>selfHostServer.OpenAsync();</code>
<code> </code><code>Console.WriteLine(</code><code>"伺服器端服務監聽已開啟"</code><code>);</code>
<code> </code><code>Console.Read();</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
代碼1-1就是引用《ASP.NET Web API 開篇介紹示例》中的示例,在示例SelfHost項目中定義了API控制器,在這裡我們需要把它注釋掉,并且建立新的類庫項目命名為WebAPIController,并且引用System.Web.Http.dll程式集和Common程式集,然後再定義個API控制器,也就是把原先在SelfHost項目中的控制器移動到建立的類庫項目中。
示例代碼1-2
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code>usingSystem.Web.Http;</code>
<code>usingCommon;</code>
<code>namespaceWebAPIController</code>
<code>{</code>
<code> </code><code>publicclassProductController : ApiController</code>
<code> </code><code>privatestaticList<Product>products;</code>
<code> </code><code>staticProductController()</code>
<code> </code><code>products=newList<Product>();</code>
<code> </code><code>products.AddRange(</code>
<code> </code><code>newProduct[] </code>
<code> </code><code>{</code>
<code> </code><code>newProduct(){ ProductID=</code><code>"001"</code><code>, ProductName=</code><code>"牙刷"</code><code>,ProductCategory=</code><code>"洗漱用品"</code><code>},</code>
<code> </code><code>newProduct(){ ProductID=</code><code>"002"</code><code>, ProductName=</code><code>"《.NET架構設計—大型企業級應用架構設計藝術》"</code><code>, ProductCategory=</code><code>"書籍"</code><code>}</code>
<code> </code><code>});</code>
<code> </code><code>publicIEnumerable<Product>Get(stringid=</code><code>null</code><code>)</code>
<code> </code><code>returnfromproductinproductswhereproduct.ProductID==id||</code><code>string</code><code>.IsNullOrEmpty(id) selectproduct;</code>
<code> </code><code>publicvoidDelete(stringid)</code>
<code> </code><code>products.Remove(products.First(product=>product.ProductID==id));</code>
<code> </code><code>publicvoidPost(Productproduct)</code>
<code> </code><code>products.Add(product);</code>
<code> </code><code>publicvoidPut(Productproduct)</code>
<code> </code><code>Delete(product.ProductID);</code>
<code> </code><code>Post(product);</code>
<code> </code><code>}</code>
<code>}</code>
這個時候還要記得把SelfHost項目添加WebAPIController項目的引用,要保持跟MVC項目的環境一樣,然後我們在運作SelfHost項目,等待監聽開啟過後再使用浏覽器請求服務會發現如下圖所示的結果。
圖1
<a href="http://s3.51cto.com/wyfs02/M02/45/2C/wKiom1PkKBXzjhSQAAJmM9l206Y693.jpg" target="_blank"></a>
看到圖1中的顯示問題了吧,未找到比對的控制器類型。如果是MVC項目則不會有這樣的問題,那麼問題出在哪呢?實作方式的差異,下面就為大家來解釋一下。
在上一篇中我們最後的示意圖裡可以清晰的看到ASP.NET Web API架構中的管道模型最後是通過HttpControllerDispatcher類型的對象來“生成”的APIController。我們現在就來看一下HttpControllerDispatcher類型的定義。
示例代碼1-3
<code> </code><code>publicclassHttpControllerDispatcher : HttpMessageHandler</code>
<code> </code><code>//Fields</code>
<code> </code><code>privatereadonlyHttpConfiguration_configuration;</code>
<code> </code><code>privateIHttpControllerSelector_controllerSelector;</code>
<code> </code><code>//Methods</code>
<code> </code><code>publicHttpControllerDispatcher(HttpConfigurationconfiguration);</code>
<code> </code><code>privatestaticHttpResponseMessageHandleException(HttpRequestMessagerequest, Exceptionexception);</code>
<code> </code><code>protectedoverrideTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest, CancellationTokencancellationToken);</code>
<code> </code><code>privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken);</code>
<code> </code><code>//Properties</code>
<code> </code><code>publicHttpConfigurationConfiguration { </code><code>get</code><code>; }</code>
<code> </code><code>privateIHttpControllerSelectorControllerSelector { </code><code>get</code><code>; }</code>
從示例代碼1-3中可以看到HttpControllerDispatcher類型繼承自HttpMessageHandler類型,由此可見,通過前面《ASP.NETWeb API 管道模型》篇幅的知識我們了解到在ASP.NET Web API管道的最後一個消息處理程式實際是HttpControllerDispatcher類型,在Web API架構調用HttpControllerDispatcher類型的SendAsync()方法時實際是調用了HttpControllerDispatcher類型的一個私有方法SendAsyncInternal(),而APIController就是在這個私有方法中生成的,當然不是由這個私有方法來生成它的。下面我們就來看一下SendAsyncInternal()的基礎實作。
示例代碼1-4
<code> </code><code>privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken)</code>
<code> </code><code>IHttpRouteDatarouteData=request.GetRouteData();</code>
<code> </code><code>HttpControllerDescriptordescriptor=</code><code>this</code><code>.ControllerSelector.SelectController(request);</code>
<code> </code><code>IHttpControllercontroller=descriptor.CreateController(request);</code>
代碼1-4很清晰的說明了APIController的生成過程,這隻是片面的,這裡的代碼并不是全部,我們現在隻需關注APIController的生成過程,暫時不去關心它的執行過程。
首先由HttpControllerDispatcher類型中的一個類型為IHttpControllerSelector的屬性ControllerSelector(實則是DefaultHttpControllerSelector類型)根據HttpRequestMessage參數類型來調用IHttpControllerSelector類型裡的SelectController()方法,由此擷取到HttpControllerDescriptor類型的變量descriptor,然後由變量descriptor調用它的CreateController()方法來建立的控制器。
說到這裡看似一個簡單的過程裡面蘊含的知識還是有一點的,我們首先來看ControllerSelector屬性:
示例代碼1-5
<code> </code><code>privateIHttpControllerSelectorControllerSelector</code>
<code> </code><code>get</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>._controllerSelector==</code><code>null</code><code>)</code>
<code> </code><code>this</code><code>._controllerSelector=</code><code>this</code><code>._configuration.Services.GetHttpControllerSelector();</code>
<code> </code><code>returnthis._controllerSelector;</code>
這裡我們可以看到是由HttpConfiguration類型的變量_configuration中的Servieces(服務容器)來擷取的IHttpControllerSelector類型的對象。那我們擷取到的究竟執行個體是什麼類型的?
到這裡先暫停,大家先不用記住上面的一堆廢話中的内容,現在我們來看一下HttpConfiguration這個類型,我們現在隻看HttpConfiguration類型,忘掉上面的一切。
示例代碼1-6
<code> </code><code>publicclassHttpConfiguration : IDisposable</code>
<code> </code><code>privateIDependencyResolver_dependencyResolver;</code>
<code> </code><code>publicHttpConfiguration();</code>
<code> </code><code>publicHttpConfiguration(HttpRouteCollectionroutes);</code>
<code> </code><code>privateHttpConfiguration(HttpConfigurationconfiguration, HttpControllerSettingssettings);</code>
<code> </code><code>publicIDependencyResolverDependencyResolver { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicServicesContainerServices { </code><code>get</code><code>; internalset; }</code>
在這裡我們看到有字段、構造函數和屬性,對于_dependencyResolver字段和對應的屬性我們下面會有講到這裡就不說了。這裡主要就是說明一下ServicesContainer 類型的Services屬性。對于ServicesContainer類型沒用的朋友可能不太清楚,實際上可以把ServicesContainer類型想象成一個IoC容器,就和IDependencyResolver類型的作用是一樣的,在前面的《C#程式設計模式之擴充指令》一文中有涉及到ServicesContainer類型的使用,感興趣的朋友可以去看看。
回到主題,在構造函數執行時ServicesContainer類型執行個體實際是由它的子類DefaultServices類型執行個體化而來的。而在DefaultServices類型執行個體化的時候它的構造函數中會将一些服務和具體實作的類型添加到緩存裡。
圖2
在圖2中我們現在隻需關注第二個紅框中的IHttpControllerSelector對應的具體服務類型是DefaultHttpControllerSelector類型。
現在我們再來看代碼1-5中的GetHttpControllerSelector()方法,它是有ServicesExtensions擴充方法類型來實作的。
好了現在大家的思緒可以回到代碼1-4中,現在我們知道是由DefaultHttpControllerSelector類型的SelectController()方法來生成HttpControllerDescriptor類型的。暫時不用管HttpControllerDescriptor類型,我們先來看SelectController()方法中的實作細節。
示例代碼1-7
<code>publicvirtualHttpControllerDescriptorSelectController(HttpRequestMessagerequest)</code>
<code> </code><code>HttpControllerDescriptordescriptor;</code>
<code> </code><code>stringcontrollerName=</code><code>this</code><code>.GetControllerName(request);</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>._controllerInfoCache.Value.TryGetValue(controllerName, outdescriptor))</code>
<code> </code><code>returndescriptor;</code>
這裡可以看到controllerName是由路由資料對象RouteData來擷取的,然後由_controllerInfoCache變量根據控制器名稱擷取到HttpControllerDescriptor執行個體,這裡HttpControllerDescriptor執行個體我們暫且不管,後面的篇幅會有講到。
重點是我們看一下_controllerInfoCache變量中的值是怎麼來的?是從HttpControllerTypeCache類型的Cache屬性值而來。而Cache的值則是根據HttpControllerTypeCache類型的中的InitializeCache()方法得來的,我們看下實作。
執行個體代碼1-8
<code> </code><code>privateDictionary<</code><code>string</code><code>, ILookup<</code><code>string</code><code>, Type>>InitializeCache()</code>
<code> </code><code>IAssembliesResolverassembliesResolver=</code><code>this</code><code>._configuration.Services.GetAssembliesResolver();</code>
<code> </code><code>returnthis._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(assembliesResolver).GroupBy<Type, </code><code>string</code><code>>(t=>t.Name.Substring(0, t.Name.Length-DefaultHttpControllerSelector.ControllerSuffix.Length), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<</code><code>string</code><code>, Type>, </code><code>string</code><code>, ILookup<</code><code>string</code><code>, Type>>(g=>g.Key, g=>g.ToLookup<Type, </code><code>string</code><code>>(t=> (t.Namespace??</code><code>string</code><code>.Empty), StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);</code>
還記得文章的主題嗎?回想一下,在本篇幅的内容隻涉及到代碼1-8的第一句代碼,後面篇幅會繼續往下講解,我們就來看一下第一句代碼。
第一句是擷取IAssembliesResolver類型的執行個體assembliesResolver,而之對應的服務則是在圖2中顯示出來了,就是DefaultAssembliesResolver類型,DefaultAssembliesResolver類型控制着架構搜尋的程式集範圍,罪魁禍首在這。
示例代碼1-9
<code> </code><code>publicclassDefaultAssembliesResolver : IAssembliesResolver</code>
<code> </code><code>publicvirtualICollection<Assembly>GetAssemblies()</code>
<code> </code><code>returnAppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();</code>
看到這裡大家應該明白了,在SelfHost環境下的服務端啟動之後就沒有加載我們所想要的WebAPIController程式集,是以才會有圖1所示的問題。這個我們可以來檢視一下。
将1-1代碼修改如示例代碼1-10.
代碼1-10
<code> </code><code>foreach</code> <code>(AssemblyassemblyinAppDomain.CurrentDomain.GetAssemblies())</code>
<code> </code><code>Console.WriteLine(assembly.FullName.Substring(0,assembly.FullName.IndexOf(</code><code>"Version"</code><code>)));</code>
<code> </code><code>}</code>
<code> </code>
這個時候我們啟動SelfHost項目,就可以檢視到到底有沒有我們想要的程式集,如圖3。
圖3
<a href="http://s3.51cto.com/wyfs02/M02/45/2C/wKiom1PkKMHwSnOHAAJBou1umsg045.jpg" target="_blank"></a>
可以看一下,根本沒有我們所需的,那怎麼樣才能正常的運作起來如一開始所說的那樣呢?
示例代碼1-11
<code>usingSystem.Web.Http.Dispatcher;</code>
<code>usingSystem.Reflection;</code>
<code>namespaceSelfHost.CustomAssembliesResolver</code>
<code> </code><code>publicclassLoadSpecifiedAssembliesResolver : IAssembliesResolver</code>
<code> </code><code>publicICollection<Assembly>GetAssemblies()</code>
<code> </code><code>AppDomain.CurrentDomain.Load(</code><code>"WebAPIController"</code><code>);</code>
<code> </code><code>returnAppDomain.CurrentDomain.GetAssemblies();</code>
我們通過自定義AssembliesResolver來加載我們指定的程式集,并且最後要把我們實作的替換到Web API架構中,如下代碼。
示例代碼1-12
<code> </code><code>selfHostServer.Configuration.Services.Replace(</code><code>typeof</code><code>(IAssembliesResolver),</code>
<code> </code><code>newCustomAssembliesResolver.LoadSpecifiedAssembliesResolver());</code>
這個時候我們再運作SelfHost項目,并且使用浏覽器來請求,最後結果如圖4.
圖4
<a href="http://s3.51cto.com/wyfs02/M00/45/2D/wKioL1PkKfOgH3DqAAJrG3hjVgQ479.jpg" target="_blank"></a>
在WebHost環境中則不會發生上述所描述的問題,為什麼?
示例代碼1-13
<code> </code><code>publicclassGlobal : System.Web.HttpApplication</code>
<code> </code><code>protectedvoidApplication_Start(objectsender, EventArgse)</code>
<code> </code><code>GlobalConfiguration.Configuration.Routes.MapHttpRoute(</code>
<code> </code><code>"DefaultAPI"</code><code>, </code><code>"api/{controller}/{id}"</code><code>, </code><code>new</code> <code>{ controller=</code><code>"product"</code><code>,id=RouteParameter.Optional });</code>
代碼1-13表示着WebHost環境下的路由注冊,看起來比SelfHost環境要簡便的多。因為“簡便”封裝在GlobalConfiguration類型中了,前面的文章也對GlobalConfiguration類型做過介紹,不過并沒有把重點放到IAssembliesResolver服務上。現在我們來看一下實作。
示例代碼1-14
<code>privatestaticLazy<HttpConfiguration>_configuration=newLazy<HttpConfiguration>(</code><code>delegate</code> <code>{</code>
<code> </code><code>HttpConfigurationconfiguration=newHttpConfiguration(newHostedHttpRouteCollection(RouteTable.Routes));</code>
<code> </code><code>configuration.Services.Replace(</code><code>typeof</code><code>(IAssembliesResolver), newWebHostAssembliesResolver());</code>
<code> </code><code>configuration.Services.Replace(</code><code>typeof</code><code>(IHttpControllerTypeResolver), newWebHostHttpControllerTypeResolver());</code>
<code> </code><code>configuration.Services.Replace(</code><code>typeof</code><code>(IHostBufferPolicySelector), newWebHostBufferPolicySelector());</code>
<code> </code><code>returnconfiguration;</code>
<code> </code><code>});</code>
我們可以清楚的看到DefaultAssembliesResolver類型被替換成了WebHostAssembliesResolver類型。為什麼WebHost不會有這樣的問題就都在WebHostAssembliesResolver類型當中了。
示例代碼1-15
<code> </code><code>internalsealedclassWebHostAssembliesResolver : IAssembliesResolver</code>
<code> </code><code>ICollection<Assembly>IAssembliesResolver.GetAssemblies()</code>
<code> </code><code>returnBuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();</code>
把WebHost環境中的結構修改的和SelfHost環境中的一樣,然後請求則會發現不會遇到什麼問題。
圖5
<a href="http://s3.51cto.com/wyfs02/M01/45/2D/wKioL1PkKhzBy1ngAAJJEp_o2Bs199.jpg" target="_blank"></a>
下一篇将會講解一下APIController的生成過程,也就是代碼1-8的第二句代碼部分。
本文轉自jinyuan0829 51CTO部落格,原文連結:http://blog.51cto.com/jinyuan/1537264,如需轉載請自行聯系原作者