在前面對管道、路由有了基礎的瞭解事後,本篇將帶你們一塊兒學習一下在ASP.NET Web API中控制器的建立過程,這過程分爲幾個部分下面的內容會爲你們講解第一個部分,也是ASP.NET Web API框架跟ASP.NET MVC框架實現上存在不一樣的一部分。編程
在項目運用中,咱們大多數會把控制器部分從主程序抽離出來放置單獨的項目中,這種狀況下在使用ASP.NET MVC框架的項目環境中是不會有什麼問題的,由於MVC框架在建立控制器的時候會加載當前主程序引用的全部程序集而且按照執行的搜索規則(公共類型、實現IController的)搜索出控制器類型而且緩存到xml文件中。而這種方式若是在使用了默認的ASP.NET Web API框架環境下就會有一點點的問題,這裏就涉及到了Web API框架的控制器建立過程當中的知識。來看一下簡單的示例。api
(示例仍是《ASP.NET Web API 開篇介紹示例》中的示例,不過作了略微的修改,符合上述的狀況。)瀏覽器
咱們仍是在SelfHost環境下作示例,來看SelfHost環境下服務端配置:緩存
示例代碼1-1服務器
class Program { static void Main(string[] args) { HttpSelfHostConfiguration selfHostConfiguration = new HttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration)) { selfHostServer.Configuration.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); selfHostServer.OpenAsync(); Console.WriteLine("服務器端服務監聽已開啓"); Console.Read(); } } }
代碼1-1就是引用《ASP.NET Web API 開篇介紹示例》中的示例,在示例SelfHost項目中定義了API控制器,在這裏咱們須要把它註釋掉,而且建立新的類庫項目命名爲WebAPIController,而且引用System.Web.Http.dll程序集和Common程序集,而後再定義個API控制器,也就是把原先在SelfHost項目中的控制器移動到新建的類庫項目中。框架
示例代碼1-2ide
using System.Web.Http; using Common; namespace WebAPIController { public class ProductController : ApiController { private static List<Product> products; static ProductController() { products = new List<Product>(); products.AddRange( new Product[] { new Product(){ ProductID="001", ProductName="牙刷",ProductCategory="洗漱用品"}, new Product(){ ProductID="002", ProductName="《.NET框架設計—大型企業級應用框架設計藝術》", ProductCategory="書籍"} }); } public IEnumerable<Product> Get(string id = null) { return from product in products where product.ProductID == id || string.IsNullOrEmpty(id) select product; } public void Delete(string id) { products.Remove(products.First(product => product.ProductID == id)); } public void Post(Product product) { products.Add(product); } public void Put(Product product) { Delete(product.ProductID); Post(product); } } }
這個時候還要記得把SelfHost項目添加WebAPIController項目的引用,要保持跟MVC項目的環境同樣,而後咱們在運行SelfHost項目,等待監聽開啓事後再使用瀏覽器請求服務會發現以下圖所示的結果。函數
圖1學習
看到圖1中的顯示問題了吧,未找到匹配的控制器類型。若是是MVC項目則不會有這樣的問題,那麼問題出在哪呢?實現方式的差別,下面就爲你們來解釋一下。ui
在上一篇中咱們最後的示意圖裏能夠清晰的看到ASP.NET Web API框架中的管道模型最後是經過HttpControllerDispatcher類型的對象來「生成」的APIController。咱們如今就來看一下HttpControllerDispatcher類型的定義。
示例代碼1-3
public class HttpControllerDispatcher : HttpMessageHandler { // Fields private readonly HttpConfiguration _configuration; private IHttpControllerSelector _controllerSelector; // Methods public HttpControllerDispatcher(HttpConfiguration configuration); private static HttpResponseMessage HandleException(HttpRequestMessage request, Exception exception); protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken); // Properties public HttpConfiguration Configuration { get; } private IHttpControllerSelector ControllerSelector { get; } }
從示例代碼1-3中能夠看到HttpControllerDispatcher類型繼承自HttpMessageHandler類型,因而可知,經過前面《ASP.NET Web API 管道模型》篇幅的知識咱們瞭解到在ASP.NET Web API管道的最後一個消息處理程序實際是HttpControllerDispatcher類型,在Web API框架調用HttpControllerDispatcher類型的SendAsync()方法時實際是調用了HttpControllerDispatcher類型的一個私有方法SendAsyncInternal(),而APIController就是在這個私有方法中生成的,固然不是由這個私有方法來生成它的。下面咱們就來看一下SendAsyncInternal()的基礎實現。
示例代碼1-4
private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken) { IHttpRouteData routeData = request.GetRouteData(); HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request); IHttpController controller = descriptor.CreateController(request); }
代碼1-4很清晰的說明了APIController的生成過程,這只是片面的,這裏的代碼並非所有,咱們如今只需關注APIController的生成過程,暫時不去關心它的執行過程。
首先由HttpControllerDispatcher類型中的一個類型爲IHttpControllerSelector的屬性ControllerSelector(實則是DefaultHttpControllerSelector類型)根據HttpRequestMessage參數類型來調用IHttpControllerSelector類型裏的SelectController()方法,由此獲取到HttpControllerDescriptor類型的變量descriptor,而後由變量descriptor調用它的CreateController()方法來建立的控制器。
說到這裏看似一個簡單的過程裏面蘊含的知識仍是有一點的,咱們首先來看ControllerSelector屬性:
示例代碼1-5
private IHttpControllerSelector ControllerSelector { get { if (this._controllerSelector == null) { this._controllerSelector = this._configuration.Services.GetHttpControllerSelector(); } return this._controllerSelector; } }
這裏咱們能夠看到是由HttpConfiguration類型的變量_configuration中的Servieces(服務容器)來獲取的IHttpControllerSelector類型的對象。那咱們獲取到的究竟實例是什麼類型的?
到這裏先暫停,你們先不用記住上面的一堆廢話中的內容,如今咱們來看一下HttpConfiguration這個類型,咱們如今只看HttpConfiguration類型,忘掉上面的一切。
示例代碼1-6
public class HttpConfiguration : IDisposable { // Fields private IDependencyResolver _dependencyResolver; // Methods public HttpConfiguration(); public HttpConfiguration(HttpRouteCollection routes); private HttpConfiguration(HttpConfiguration configuration, HttpControllerSettings settings); public IDependencyResolver DependencyResolver { get; set; } public ServicesContainer Services { get; internal set; } }
在這裏咱們看到有字段、構造函數和屬性,對於_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
public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request) { HttpControllerDescriptor descriptor; string controllerName = this.GetControllerName(request); if (this._controllerInfoCache.Value.TryGetValue(controllerName, out descriptor)) { return descriptor; } }
這裏能夠看到controllerName是由路由數據對象RouteData來獲取的,而後由_controllerInfoCache變量根據控制器名稱獲取到HttpControllerDescriptor實例,這裏HttpControllerDescriptor實例咱們暫且無論,後面的篇幅會有講到。
重點是咱們看一下_controllerInfoCache變量中的值是怎麼來的?是從HttpControllerTypeCache類型的Cache屬性值而來。而Cache的值則是根據HttpControllerTypeCache類型的中的InitializeCache()方法得來的,咱們看下實現。
實例代碼1-8
private Dictionary<string, ILookup<string, Type>> InitializeCache() { IAssembliesResolver assembliesResolver = this._configuration.Services.GetAssembliesResolver(); return this._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(assembliesResolver).GroupBy<Type, string>(t => t.Name.Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, Type>, string, ILookup<string, Type>>(g => g.Key, g => g.ToLookup<Type, string>(t => (t.Namespace ?? string.Empty), StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase); }
還記得文章的主題嗎?回想一下,在本篇幅的內容只涉及到代碼1-8的第一句代碼,後面篇幅會繼續往下講解,咱們就來看一下第一句代碼。
第一句是獲取IAssembliesResolver類型的實例assembliesResolver,而之對應的服務則是在圖2中顯示出來了,就是DefaultAssembliesResolver類型,DefaultAssembliesResolver類型控制着框架搜尋的程序集範圍,罪魁禍首在這。
示例代碼1-9
public class DefaultAssembliesResolver : IAssembliesResolver { // Methods public virtual ICollection<Assembly> GetAssemblies() { return AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); } }
看到這裏你們應該明白了,在SelfHost環境下的服務端啓動以後就沒有加載咱們所想要的WebAPIController程序集,因此纔會有圖1所示的問題。這個咱們能夠來查看一下。
將1-1代碼修改如示例代碼1-10.
代碼1-10
class Program { static void Main(string[] args) { HttpSelfHostConfiguration selfHostConfiguration = new HttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration)) { selfHostServer.Configuration.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); selfHostServer.OpenAsync(); foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { Console.WriteLine(assembly.FullName.Substring(0,assembly.FullName.IndexOf("Version"))); } Console.WriteLine("服務器端服務監聽已開啓"); Console.Read(); } } }
這個時候咱們啓動SelfHost項目,就能夠查看到到底有沒有咱們想要的程序集,如圖3。
圖3
能夠看一下,根本沒有咱們所需的,那怎麼樣才能正常的運行起來如一開始所說的那樣呢?
示例代碼1-11
using System.Web.Http.Dispatcher; using System.Reflection; namespace SelfHost.CustomAssembliesResolver { public class LoadSpecifiedAssembliesResolver : IAssembliesResolver { public ICollection<Assembly> GetAssemblies() { AppDomain.CurrentDomain.Load("WebAPIController"); return AppDomain.CurrentDomain.GetAssemblies(); } } }
咱們經過自定義AssembliesResolver來加載咱們指定的程序集,而且最後要把咱們實現的替換到Web API框架中,以下代碼。
示例代碼1-12
class Program { static void Main(string[] args) { HttpSelfHostConfiguration selfHostConfiguration = new HttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration)) { selfHostServer.Configuration.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); selfHostServer.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver.LoadSpecifiedAssembliesResolver()); selfHostServer.OpenAsync(); Console.WriteLine("服務器端服務監聽已開啓"); Console.Read(); } } }
這個時候咱們再運行SelfHost項目,而且使用瀏覽器來請求,最後結果如圖4.
圖4
在WebHost環境中則不會發生上述所描述的問題,爲何?
示例代碼1-13
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { GlobalConfiguration.Configuration.Routes.MapHttpRoute( "DefaultAPI", "api/{controller}/{id}", new { controller="product",id = RouteParameter.Optional }); } }
代碼1-13表示着WebHost環境下的路由註冊,看起來比SelfHost環境要簡便的多。由於「簡便」封裝在GlobalConfiguration類型中了,前面的文章也對GlobalConfiguration類型作過介紹,不過並無把重點放到IAssembliesResolver服務上。如今咱們來看一下實現。
示例代碼1-14
private static Lazy<HttpConfiguration> _configuration = new Lazy<HttpConfiguration>(delegate { HttpConfiguration configuration = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes)); configuration.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver()); configuration.Services.Replace(typeof(IHttpControllerTypeResolver), new WebHostHttpControllerTypeResolver()); configuration.Services.Replace(typeof(IHostBufferPolicySelector), new WebHostBufferPolicySelector()); return configuration; });
咱們能夠清楚的看到DefaultAssembliesResolver類型被替換成了WebHostAssembliesResolver類型。爲何WebHost不會有這樣的問題就都在WebHostAssembliesResolver類型當中了。
示例代碼1-15
internal sealed class WebHostAssembliesResolver : IAssembliesResolver { // Methods ICollection<Assembly> IAssembliesResolver.GetAssemblies() { return BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>(); } }
把WebHost環境中的結構修改的和SelfHost環境中的同樣,而後請求則會發現不會遇到什麼問題。
圖5
做者:金源
出處:http://www.cnblogs.com/jin-yuan/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面