目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建
通过上面的介绍我们知道利用HttpControllerSelector可以根据表示当前请求的HttpRequestMessage得到描述目标HttpController的HttpControllerDescriptor对象。在前面介绍HttpControllerDescriptor的时候我们提到过:HttpControllerDescriptor自身具有创建对应HttpController的能力,具体体现在它的CreateController方法上。接下来我们就来着重介绍实现在这个CreateController方法中HttpController创建机制。[本文已经同步到《How ASP.NET Web API Works?》]
目录
HttpControllerActivator
DefaultHttpControllerActivator
DependencyResolver
HttpRequestMessage中的DependencyResolver
DependencyResolver在DefaultHttpControllerActivator中的应用
DependencyScope的释放
实例演示:伴随着HttpMessage的关闭对资源的释放
HttpControllerActivator
针对请求对目标HttpController的激活机制最终落实到一个名为HttpControllerActivator的对象上,所有的HttpControllerActivator均实现了IHttpControllerActivator接口。如下面的代码片断所示,IHttpControllerActivator具有唯一的方法Create方法根据指定的表示请求的HttpRequestMessage对象、描述激活HttpController的HttpControllerDescriptor以及目标HttpController的类型来创建对应的HttpController。
1: public interface IHttpControllerActivator
2: {
3: IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType);
4: }
忠实的读者朋友们一定知道像这样的“标准化组件”自然也是注册到当前HttpConfiguration的ServicesContainer上被HttpController激活系统使用的。我们可以通过ServicesContainer具有如下定义的扩展方法GetHttpControllerActivator直接获取注册的HttpControllerActivator对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerActivator GetHttpControllerActivator(this ServicesContainer services);
5: }
实际上HttpControllerDescriptor的CreateController方法就是调用上述这个扩展方法得到 注册的HttpControllerActivator对象,并调用它的Create方法来创建目标HttpController的。如下的代码体现CreateController方法真正的实现逻辑。
1: public class HttpControllerDescriptor
2: {
3: //其他成员
4: public virtual IHttpController CreateController(HttpRequestMessage request)
5: {
6: return this.Configuration.Services.GetHttpControllerActivator().Create(request, this, this.ControllerType);
7: }
8: }
DefaultHttpControllerActivator
我们照例利用通过DefaultServices的构造函数定义分析出默认注册的HttpControllerActivator是个怎样的对象。如下面的代码片断所示,当DefaultServices被创建的时候会创建并注册一个类型为DefaultHttpControllerActivator对象。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerActivator>(new DefaultHttpControllerActivator ());
8: }
9: }
接下来我们就来分析下在DefaultHttpControllerActivator类型的Create方法中是如何激活,不过要真正了解实现在DefaultHttpControllerActivator的HttpController激活机制,我们需要认识另一个名为DependencyResolver的对象。
DependencyResolver
说到DependencyResolver,我们又不得不谈到IoC的概念。我们知道IoC常和另一个术语“依赖注入(DI,Dependency Injection)”联系在一起。通过IoC容器激活的对象可能具有其他对象的依赖,而且被依赖的对象可能具有针对另一个对象的依赖,所以IoC容器需要在提供所需对象之前已经帮助我们解决了这些依赖。关于IoC以及它在HttpController激活过程中的应用,我们会在下一节中进行单独介绍。
从命名也可以看出来,这里介绍DependencyResolver与依赖注入有关,我们可以将它视为ASP.NET Web API内部使用的IoC容器。所有的DependencyResolver实现了具有如下定义的接口IDependencyResolver,这个接口的定义有点特别:它具有唯一个返回类型为IDependencyScope的BeginScope方法,IDependencyResolver接口本身同时也继承IDependencyScope这个接口,并且这两个接口又都继承自IDisposable接口。
1: public interface IDependencyResolver : IDependencyScope, IDisposable
2: {
3: IDependencyScope BeginScope();
4: }
5:
6: public interface IDependencyScope : IDisposable
7: {
8: object GetService(Type serviceType);
9: IEnumerable<object> GetServices(Type serviceType);
10: }
通过DependencyResolver的BeginScope方法创建的IDependencyScope类型的对象可以视为一个用于激活对象的上下文,我们可以通过调用它的GetService和GerServices方法根据指定的“服务接口类型”获取对应的服务实例。由于IDependencyScope继承自IDisposable,所以与此上下文关联的资源释放工作可以通过实现的Dispose方法来完成。
与上面我们介绍的那些“标准化组件”不同,默认使用的DependencyResolver并未注册到当前HttpConfiguration的ServicesContainers上,因为它直接注册到当前HttpConfiguration上面。如下面的代码片断所示,当前使用的DependencyResolver直接通过HttpConfiguration的DependencyResolver属性或者和设置。
1: public class HttpConfiguration : IDisposable
2: {
3: //其他成员
4: public HttpConfiguration(HttpRouteCollection routes)
5: {
6: this._dependencyResolver = EmptyResolver.Instance ;
7: }
8:
9: public IDependencyResolver DependencyResolver
10: {
11: get { return this._dependencyResolver; }
12: set { this._dependencyResolver = value; }
13: }
14: }
从上面的代码片断我们还可以看出默认注册到HttpConfiguration上的DependencyResolver是通过类型EmptyResolver的静态属性Instance返回的EmptyResolver对象。EmptyResolver是一个定义在程序集System.Web.Http.dll中的内部类型,其成员定义如下。之所以将它如此命名,原因在于它仅仅是一个“空”的IoC容器:它的BeginScope返回的是它自身,GetService和GetServices方法分别返回Null和一个空对象集合,而Dispose方法也没有任何资源释放工作要做。
1: internal class EmptyResolver : IDependencyResolver, IDependencyScope, IDisposable
2: {
3: public IDependencyScope BeginScope();
4: public void Dispose();
5: public object GetService(Type serviceType);
6: public IEnumerable<object> GetServices(Type serviceType);
7: public static IDependencyResolver Instance { get; }
8: }
HttpRequestMessage中的DependencyResolver
虽然当前使用的DependencyResolver是注册到当前HttpConfiguration上的,但是我们可以直接从表示当前请求的HttpRequestMessage对象中获取道通过它创建的DependencyScope对象。如下面的代码片断所示,HttpRequestMessage具有一个返回类型为IDependencyScope的GetDependencyScope方法。
1: public static class HttpRequestMessageExtensions
2: {
3: //其他成员
4: public static IDependencyScope GetDependencyScope(this HttpRequestMessage request);
5: }
其实这个扩展方法实现逻辑很简单:HttpRequestMessage的属性字典为放置DependencyScope对象预定了一个Key,其值为“MS_DependencyScope”,对应着HttpPropertyKeys具有如下定义的静态只读字段DependencyScope。
1: public static class HttpPropertyKeys
2: {
3: //其他成员
4: public static readonly string DependencyScope;
5: }
如果根据这个Key不能从HttpRequestMessage的属性字典中找到一个DependencyScope对象,则会通过当前的HttpConfiguration(实际上当前的HttpConfiguration也已经被HttpServer添加到了这个HttpRequestMessage中了)得到注册的DependencyResolver,然后利用它创建一个DependencyScope对象并添加到HttpRequestMessage的属性字典中,那么后续过程如果需要使用到此DependencyScope就可以直接从HttpRequestMessage中提取了。
DependencyResolver在DefaultHttpControllerActivator中的应用
在了解了DependencyResolver是怎样一个对象之后,我们再回头讨论实现在DefaultHttpControllerActivator的Create方法中针对请求的HttpController激活机制。其实实现机制非常简单:DefaultHttpControllerActivator先通过调用表示当前请求的HttpRequestMessage的扩展方法GetDependencyScope得到通过当前DependencyResolver创建的DependencyScope对象,然后将目标HttpController的类型作为参数调用其GetService方法。
如果该方法返回一个具体的HttpController对象,该对象就是Create方法的返回值,否则直接直接根据目标HttpController的类型进行反射创建一个HttpController对象并返回。如下所示的代码片断基本上体现了DefaultHttpControllerActivator的HttpController激活机制。
1: public class DefaultHttpControllerActivator : IHttpControllerActivator
2: {
3: public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
4: {
5: IDependencyScope depedencyScope = request.GetDependencyScope();
6: object httpController = depedencyScope.GetService(controllerType)?? Activator.CreateInstance(controllerType);
7: return httpController as IHttpController;
8: }
9: }
由于默认请求下注册到当前HttpConfiguration上的DependencyResolver是一个EmptyResolver对象,而它的GetService方法总是返回Null,所以默认情况下对HttpController的激活最终是对目标HttpController类型的反射实现的。
关于HttpController的激活,我还想强调一点:在默认情况下,解析出来的所有HttpController类型会被缓存,创建的用于描述HttpControllerDescriptor也会被缓存,但是ASP.NET Web API的HttpController激活系统并不会对创建的HttpController实施缓存。换言之,不管多个请求是否针对相同的HttpController类型,被激活的HttpController实例都是不同的。
DependencyScope的释放
这是一个很少人会意识到的问题:被添加到表示当前请求的HttpRequestMessage中用于根据类型激活相应实例(不仅限于HttpController对象)的DependencyScope类型实现了IDisposable接口,那么用于释放该DependencyScope的Dispose方法在什么时候被调用呢?
在HttpRequestMessage的属性字典中为需要释放的资源预留的存储空间,对应的Key为“MS_DisposableRequestResources”,HttpPropertyKeys具有如下定义的静态只读字段DisposableRequestResourcesKey返回这个Key。
1: public static class HttpPropertyKeys
2: {
3: //其他成员
4: public static readonly string DisposableRequestResourcesKey;
5: }
这个消息属性保存的是一个类型为List<IDisposable>的对象。我们可以调用HttpRequestMessage具有如下定义的扩展方法GetResourcesForDisposal得到这个列表,也可以调用扩展方法RegisterForDispose和RegisterForDispose将一个或者多个类型实现了IDisposable接口的对象放到这个列表中。在ASP.NET Web API的应用编程接口中还为释放这些附加到HttpRequestMessage上的对象定义了如下一个扩展方法DisposeRequestResources,那么这个方法究竟是在什么时候被调用的呢?
1: public static class HttpRequestMessageExtensions
2: {
3: //其他成员
4: public static IEnumerable<IDisposable> GetResourcesForDisposal(this HttpRequestMessage request);
5: public static void RegisterForDispose(this HttpRequestMessage request, IEnumerable<IDisposable> resources);
6: public static void RegisterForDispose(this HttpRequestMessage request, IDisposable resource);
7: public static void DisposeRequestResources(this HttpRequestMessage request);
8: }
具体释放这些资源的时机取决于采用的寄宿模式。对于Web Host来说,通过“ASP.NET Web API的消息处理管道: HttpRoutingDispatcher”的介绍我们知道ASP.NET Web API用于“处理请求、回复响应”的HttpMessageHandler管道是由HttpControllerHandler创建的,后者根据当前HTTP上下文创建一个表示当前请求的HttpRequestMessage对象并传入这个管道进行处理。在整个管道完成对请求的请求并最终对请求予以响应之后,HttpControllerHandler会负责完成如下三项与资源释放有关的工作:
- 调用HttpRequestMessage对象的扩展方法DisposeRequestResources释放附加在自身属性字典中的对象。
- 调用HttpRequestMessage对象的Dispose方法对请求消息本身作相应的释放工作。
- 调用返回的HttpResponseMessage对象对响应消息作相应的释放工作。
对于Self Host来说,通过《Self Host下的消息处理管道》的介绍我们知道请求的监听、接收和响应是通过HttpBinding创建的信道栈来完成的。该信道栈处理的消息类型为HttpMessage,具体代表请求消息和响应消息的HttpMessage分别是对HttpRequestMessage和HttpResponseMessage对象的封装。WCF中表示消息的Message本身就是一个需要最终被释放的对象,在针对它的处理结束之后会调用其Close或者Dispose对它进行资源释放的工作。
当一个HttpMessage对象的Close或者Dispose方法被调用的时侯,被其封装的HttpRequestMessage或者HttpResponseMessage会相应地得到释放。对应请求消息来说,具体的资源释放工作包括针对HttpRequestMessage自身的释放和对附加到属性字典中资源的释放。
实例演示:伴随着HttpMessage的关闭对资源的释放
我们不妨通过一个简单的实例来演示Self Host寄宿模式下伴随着HttpMessage对象的释放对被封装的HttpRequestMessage释放。我们在一个控制台应用中定义了如下三个需要被释放的类型Foo、Bar和Baz,它们共同的基类DispsoableObject实现了IDisposable接口,并在实现Dispose方法中通过输出一段文字以确定具体的释放操作是否被执行。
1: public class DisposableObject : IDisposable
2: {
3: public void Dispose()
4: {
5: Console.WriteLine("{0}.Dispose()", this.GetType().Name);
6: }
7: }
8:
9: public class Foo : DisposableObject{}
10: public class Bar : DisposableObject{}
11: public class Baz : DisposableObject{}
然后我们再Main方法中编写了如下一段简短的程序。我们分别创建了类型为Foo、Bar和Baz的三个对象,并通过调用扩展方法RegisterForDispose将它们注册到创建的HttpRequestMessage对象上。我们针对这个HttpRequestMessage对象利用反射的方式创建了一个HttpMessage对象,最终调用其Close方法对它作相应的释放工作。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: HttpRequestMessage request = new HttpRequestMessage();
6: request.RegisterForDispose(new Foo());
7: request.RegisterForDispose(new Bar());
8: request.RegisterForDispose(new Baz());
9:
10: Type httpMessageType = Type.GetType("System.Web.Http.SelfHost.Channels.HttpMessage, System.Web.Http.SelfHost");
11: Message httpMessage = (Message)Activator.CreateInstance(httpMessageType, new object[] { request });
12: httpMessage.Close();
13: }
14: }
运行这段程序后会控制台上将会产生如下的输出结果,由此可见通过调用扩展方法RegisterForDispose注册到某个HttpRequestMessage对象上的资源能够在它释放的时候得到释放。(S405)
1: Foo.Dispose()
2: Bar.Dispose()
3: Baz.Dispose()
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。