在前面对管道、路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内容会为大家讲解第一个部分,也是ASP.NET Web API框架跟ASP.NET MVC框架实现上存在不同的一部分。
在项目运用中,我们大多数会把控制器部分从主程序抽离出来放置单独的项目中,这种情况下在使用ASP.NET MVC框架的项目环境中是不会有什么问题的,因为MVC框架在创建控制器的时候会加载当前主程序引用的所有程序集并且按照执行的搜索规则(公共类型、实现IController的)搜索出控制器类型并且缓存到xml文件中。而这种方式如果在使用了默认的ASP.NET Web API框架环境下就会有一点点的问题,这里就涉及到了Web 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-2
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项目则不会有这样的问题,那么问题出在哪呢?实现方式的差异,下面就为大家来解释一下。
在上一篇中我们最后的示意图里可以清晰的看到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,
新闻热点
疑难解答