在前面对管道、路由有了基础的了解过后,本篇将带大家一起学习一下在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
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的第二句代码部分。