在前面对管道、路由有了基础的了解事后,本篇将带你们一块儿学习一下在ASP.NET Web API中控制器的建立过程,这过程分为几个部分下面的内容会为你们讲解第一个部分,也是ASP.NET Web API框架跟ASP.NET MVC框架实现上存在不一样的一部分。编程
ASP.NET Web API 控制器建立过程(一)c#
ASP.NET Web API 控制器建立过程(二)api
未完待续浏览器
在项目运用中,咱们大多数会把控制器部分从主程序抽离出来放置单独的项目中,这种状况下在使用ASP.NET MVC框架的项目环境中是不会有什么问题的,由于MVC框架在建立控制器的时候会加载当前主程序引用的全部程序集而且按照执行的搜索规则(公共类型、实现IController的)搜索出控制器类型而且缓存到xml文件中。而这种方式若是在使用了默认的ASP.NET Web API框架环境下就会有一点点的问题,这里就涉及到了Web API框架的控制器建立过程当中的知识。来看一下简单的示例。缓存
(示例仍是《ASP.NET Web API 开篇介绍示例》中的示例,不过作了略微的修改,符合上述的状况。)服务器
咱们仍是在SelfHost环境下作示例,来看SelfHost环境下服务端配置:框架
示例代码1-1ide
classProgram { staticvoidMain(string[] args) { HttpSelfHostConfigurationselfHostConfiguration= newHttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(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-2学习
usingSystem.Web.Http; usingCommon; namespaceWebAPIController { publicclassProductController : ApiController { privatestaticList<Product>products; staticProductController() { products=newList<Product>(); products.AddRange( newProduct[] { newProduct(){ ProductID="001", ProductName="牙刷",ProductCategory="洗漱用品"}, newProduct(){ ProductID="002", ProductName="《.NET框架设计—大型企业级应用框架设计艺术》", ProductCategory="书籍"} }); } publicIEnumerable<Product>Get(stringid=null) { returnfromproductinproductswhereproduct.ProductID==id||string.IsNullOrEmpty(id) selectproduct; } publicvoidDelete(stringid) { products.Remove(products.First(product=>product.ProductID==id)); } publicvoidPost(Productproduct) { products.Add(product); } publicvoidPut(Productproduct) { Delete(product.ProductID); Post(product); } } }
这个时候还要记得把SelfHost项目添加WebAPIController项目的引用,要保持跟MVC项目的环境同样,而后咱们在运行SelfHost项目,等待监听开启事后再使用浏览器请求服务会发现以下图所示的结果。
图1
看到图1中的显示问题了吧,未找到匹配的控制器类型。若是是MVC项目则不会有这样的问题,那么问题出在哪呢?实现方式的差别,下面就为你们来解释一下。
在上一篇中咱们最后的示意图里能够清晰的看到ASP.NET Web API框架中的管道模型最后是经过HttpControllerDispatcher类型的对象来“生成”的APIController。咱们如今就来看一下HttpControllerDispatcher类型的定义。
示例代码1-3
publicclassHttpControllerDispatcher : HttpMessageHandler { //Fields privatereadonlyHttpConfiguration_configuration; privateIHttpControllerSelector_controllerSelector; //Methods publicHttpControllerDispatcher(HttpConfigurationconfiguration); privatestaticHttpResponseMessageHandleException(HttpRequestMessagerequest, Exceptionexception); protectedoverrideTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest, CancellationTokencancellationToken); privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken); //Properties publicHttpConfigurationConfiguration { get; } privateIHttpControllerSelectorControllerSelector { get; } }
从示例代码1-3中能够看到HttpControllerDispatcher类型继承自HttpMessageHandler类型,因而可知,经过前面《ASP.NETWeb API 管道模型》篇幅的知识咱们了解到在ASP.NET Web API管道的最后一个消息处理程序实际是HttpControllerDispatcher类型,在Web API框架调用HttpControllerDispatcher类型的SendAsync()方法时实际是调用了HttpControllerDispatcher类型的一个私有方法SendAsyncInternal(),而APIController就是在这个私有方法中生成的,固然不是由这个私有方法来生成它的。下面咱们就来看一下SendAsyncInternal()的基础实现。
示例代码1-4
privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken) { IHttpRouteDatarouteData=request.GetRouteData(); HttpControllerDescriptordescriptor=this.ControllerSelector.SelectController(request); IHttpControllercontroller=descriptor.CreateController(request); }
代码1-4很清晰的说明了APIController的生成过程,这只是片面的,这里的代码并非所有,咱们如今只需关注APIController的生成过程,暂时不去关心它的执行过程。
首先由HttpControllerDispatcher类型中的一个类型为IHttpControllerSelector的属性ControllerSelector(实则是DefaultHttpControllerSelector类型)根据HttpRequestMessage参数类型来调用IHttpControllerSelector类型里的SelectController()方法,由此获取到HttpControllerDescriptor类型的变量descriptor,而后由变量descriptor调用它的CreateController()方法来建立的控制器。
说到这里看似一个简单的过程里面蕴含的知识仍是有一点的,咱们首先来看ControllerSelector属性:
示例代码1-5
privateIHttpControllerSelectorControllerSelector { get { if (this._controllerSelector==null) { this._controllerSelector=this._configuration.Services.GetHttpControllerSelector(); } returnthis._controllerSelector; } }
这里咱们能够看到是由HttpConfiguration类型的变量_configuration中的Servieces(服务容器)来获取的IHttpControllerSelector类型的对象。那咱们获取到的究竟实例是什么类型的?
到这里先暂停,你们先不用记住上面的一堆废话中的内容,如今咱们来看一下HttpConfiguration这个类型,咱们如今只看HttpConfiguration类型,忘掉上面的一切。
示例代码1-6
publicclassHttpConfiguration : IDisposable { //Fields privateIDependencyResolver_dependencyResolver; //Methods publicHttpConfiguration(); publicHttpConfiguration(HttpRouteCollectionroutes); privateHttpConfiguration(HttpConfigurationconfiguration, HttpControllerSettingssettings); publicIDependencyResolverDependencyResolver { get; set; } publicServicesContainerServices { get; internalset; } }
在这里咱们看到有字段、构造函数和属性,对于_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
publicvirtualHttpControllerDescriptorSelectController(HttpRequestMessagerequest) { HttpControllerDescriptordescriptor; stringcontrollerName=this.GetControllerName(request); if (this._controllerInfoCache.Value.TryGetValue(controllerName, outdescriptor)) { returndescriptor; } }
这里能够看到controllerName是由路由数据对象RouteData来获取的,而后由_controllerInfoCache变量根据控制器名称获取到HttpControllerDescriptor实例,这里HttpControllerDescriptor实例咱们暂且无论,后面的篇幅会有讲到。
重点是咱们看一下_controllerInfoCache变量中的值是怎么来的?是从HttpControllerTypeCache类型的Cache属性值而来。而Cache的值则是根据HttpControllerTypeCache类型的中的InitializeCache()方法得来的,咱们看下实现。
实例代码1-8
privateDictionary<string, ILookup<string, Type>>InitializeCache() { IAssembliesResolverassembliesResolver=this._configuration.Services.GetAssembliesResolver(); returnthis._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
publicclassDefaultAssembliesResolver : IAssembliesResolver { //Methods publicvirtualICollection<Assembly>GetAssemblies() { returnAppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); } }
看到这里你们应该明白了,在SelfHost环境下的服务端启动以后就没有加载咱们所想要的WebAPIController程序集,因此才会有图1所示的问题。这个咱们能够来查看一下。
将1-1代码修改如示例代码1-10.
代码1-10
classProgram { staticvoidMain(string[] args) { HttpSelfHostConfigurationselfHostConfiguration= newHttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(selfHostConfiguration)) { selfHostServer.Configuration.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new { id=RouteParameter.Optional }); selfHostServer.OpenAsync(); foreach (AssemblyassemblyinAppDomain.CurrentDomain.GetAssemblies()) { Console.WriteLine(assembly.FullName.Substring(0,assembly.FullName.IndexOf("Version"))); } Console.WriteLine("服务器端服务监听已开启"); Console.Read(); } } }
这个时候咱们启动SelfHost项目,就能够查看到到底有没有咱们想要的程序集,如图3。
图3
能够看一下,根本没有咱们所需的,那怎么样才能正常的运行起来如一开始所说的那样呢?
示例代码1-11
usingSystem.Web.Http.Dispatcher; usingSystem.Reflection; namespaceSelfHost.CustomAssembliesResolver { publicclassLoadSpecifiedAssembliesResolver : IAssembliesResolver { publicICollection<Assembly>GetAssemblies() { AppDomain.CurrentDomain.Load("WebAPIController"); returnAppDomain.CurrentDomain.GetAssemblies(); } } }
咱们经过自定义AssembliesResolver来加载咱们指定的程序集,而且最后要把咱们实现的替换到Web API框架中,以下代码。
示例代码1-12
classProgram { staticvoidMain(string[] args) { HttpSelfHostConfigurationselfHostConfiguration= newHttpSelfHostConfiguration("http://localhost/selfhost"); using (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(selfHostConfiguration)) { selfHostServer.Configuration.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new { id=RouteParameter.Optional }); selfHostServer.Configuration.Services.Replace(typeof(IAssembliesResolver), newCustomAssembliesResolver.LoadSpecifiedAssembliesResolver()); selfHostServer.OpenAsync(); Console.WriteLine("服务器端服务监听已开启"); Console.Read(); } } }
这个时候咱们再运行SelfHost项目,而且使用浏览器来请求,最后结果如图4.
图4
在WebHost环境中则不会发生上述所描述的问题,为何?
示例代码1-13
publicclassGlobal : System.Web.HttpApplication { protectedvoidApplication_Start(objectsender, EventArgse) { GlobalConfiguration.Configuration.Routes.MapHttpRoute( "DefaultAPI", "api/{controller}/{id}", new { controller="product",id=RouteParameter.Optional }); } }
代码1-13表示着WebHost环境下的路由注册,看起来比SelfHost环境要简便的多。由于“简便”封装在GlobalConfiguration类型中了,前面的文章也对GlobalConfiguration类型作过介绍,不过并无把重点放到IAssembliesResolver服务上。如今咱们来看一下实现。
示例代码1-14
privatestaticLazy<HttpConfiguration>_configuration=newLazy<HttpConfiguration>(delegate { HttpConfigurationconfiguration=newHttpConfiguration(newHostedHttpRouteCollection(RouteTable.Routes)); configuration.Services.Replace(typeof(IAssembliesResolver), newWebHostAssembliesResolver()); configuration.Services.Replace(typeof(IHttpControllerTypeResolver), newWebHostHttpControllerTypeResolver()); configuration.Services.Replace(typeof(IHostBufferPolicySelector), newWebHostBufferPolicySelector()); returnconfiguration; });
咱们能够清楚的看到DefaultAssembliesResolver类型被替换成了WebHostAssembliesResolver类型。为何WebHost不会有这样的问题就都在WebHostAssembliesResolver类型当中了。
示例代码1-15
internalsealedclassWebHostAssembliesResolver : IAssembliesResolver { //Methods ICollection<Assembly>IAssembliesResolver.GetAssemblies() { returnBuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>(); } }
把WebHost环境中的结构修改的和SelfHost环境中的同样,而后请求则会发现不会遇到什么问题。
图5
下一篇将会讲解一下APIController的生成过程,也就是代码1-8的第二句代码部分。