ASP.NET Web API下的HttpController激活:程序集的解析
HttpController的激活是由处于消息处理管道尾端的HttpRoutingDispatcher来完成的,具体来说是HttpRoutingDispatcher利用HttpControllerDispatcher实现了针对目标HttpController的激活和执行。激活目标HttpController的前提是能够正确解析出HttpController的真实类型,而类型解析需要针对加载的程序集,所以我们需要先来了解一个用于解析程序集的对象AssembliesResolver。在ASP.NET Web API的HttpController激活系统中,AssembliesResolver为目标HttpController的类型解析提供候选的程序集。换句话说,候选HttpController类型的选择范围仅限于定义在通过AssembliesResolver提供的程序集中的所有实现了IHttpController接口的类型。[本文已经同步到《How ASP.NET Web API Works?》]
目录
AssembliesResolver
ServicesContainer
DefaultAssembliesResolver
实例演示:自定义AssembliesResolver
WebHostAssembliesResolver
AssembliesResolver
所有的AssembliesResolver均实现了接口IAssembliesResolver。如下面的代码片断所示,IAssembliesResolver接口中仅仅定义了一个唯一的GetAssemblies方法,该方法返回的正是提供的程序集列表。
public inte易做图ce IAssembliesResolver
{
ICollection<Assembly> GetAssemblies();
}默认使用的AssembliesResolver类型为DefaultAssembliesResolver。如下面的代码片断所示,DefaultAssembliesResolver在实现的GetAssemblies方法中直接返回的是当前应用程序域加载的所有程序集列表。
public class DefaultAssembliesResolver : IAssembliesResolver
{
public virtual ICollection<Assembly> GetAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
}
}我们说DefaultAssembliesResolver是默认使用的AssembliesResolver,那么默认的AssembliesResolver类型在ASP.NET Web API是如何确定的呢?要回答这个问题,就需要涉及到另一个重要的类型——ServicesContainer。
ServicesContainer
整个ASP.NET Web API框架就是一个处理请求的管道,这条类似于流水线的管道的每个环节都注册的相应的组件来完成某项独立的任务。这些“标准化”的组件一般都实现了某个预定义的接口。如果这些原生的组件不能满足我们的需求,我们完全可以通过实现相应的接口建自定义的组件,然后以某种形式将它们“注册安装”到这个管道上。我们可以将这些标准化的组件视为实现了某个接口的服务,那么ServicesContainer就是一个服务的容器。从某种意义上来说,我们可以将ServicesContainer理解为一个简单的IoC容器,它维护着一个服务接口类型与服务实例之间的映射,使我们可以可以根据服务接口类型获取对应的服务实例。
如下所示的代码片断列出了定义在抽象类ServicesContainer中的核心方法。我们可以通过调用方法Add、AddRange、Insert和Replace注册服务接口类型与具体服务实例对象之间的映射关系。针对服务接口类型获取对应的服务实例可以通过调用方法GetService或者GetServices方法来完成。方法FindIndex用于确定注册的目标服务实例在容器中的索引,另一个方法IsSingleService用于判断针对指定的服务接口类型是否仅仅注册了唯一一个服务实例。注册的匹配关系可以通过调用方法Remove、RmoveAll、RemoveAt和Clear方法进行删除。除此之外,ServicesContainer还实现了接口IDisposable,如果我们需要自定义ServicesContainer,可以通过重写虚方法Dispose完成对相应资源的释放。
public abstract class ServicesContainer : IDisposable
{
public void Add(Type serviceType, object service);
public void AddRange(Type serviceType, IEnumerable<object> services);
public void Insert(Type serviceType, int index, object service);
public void InsertRange(Type serviceType, int index, IEnumerable<object> services);
public void Replace(Type serviceType, object service);
public void ReplaceRange(Type serviceType, IEnumerable<object> services);
public abstract object GetService(Type serviceType);
public abstract IEnumerable<object> GetServices(Type serviceType);
public int FindIndex(Type serviceType, Predicate<object> match);
public abstract bool IsSingleService(Type serviceType);
public bool Remove(Type serviceType, object service);
public int RemoveAll(Type serviceType, Predicate<object> match);
public void RemoveAt(Type serviceType, int index);
public virtual void Clear(Type serviceType);
public virtual void Dispose();
}整个ASP.NET Web API的配置都是通过HttpConfiguration来完成,针对自定义“服务”的注册自然也不例外。如下面的代码片断所示,HttpConfiguration具有一个类型为ServicesContainer的只读属性Services,ASP.NET Web API的运行时框架使用的ServicesContainer正是它。除此之外,通过如下的代码片断我们还会发现:默认使用的ServicesContainer是一个类型为DefaultServices的对象,类型全名为System.Web.Http.Services.DefaultServices。
public class HttpConfiguration : IDisposable
{
//其他成员
public HttpConfiguration(HttpRouteCollection routes)
{
//其他操作
this.Services = new DefaultServices(this);
}
public ServicesContainer Services { get; }
}DefaultAssembliesResolver
再次将我们的关注点拉回到AssembliesResolver上面,作为ASP.NET Web API众多标准化组件之一,默认使用的AssembliesResolver自然应该注册在ServicesContainer上面。如下面的代码片断所示,默认使用DefaultServices在构造函数中默认注册的AssembliesResolver就是一个DefaultAssembliesResolver对象。
public class DefaultServices : ServicesContainer
{
//其他成员
public DefaultServices(HttpConfiguration configuration)
{
//其他操作
this.SetSingle<IAssembliesResolver>(new DefaultAssembliesResolver());
}
}如果我们需要获取注册的AssembliesResolver对象,可以直接调用ServicesContainer的GetService方法来获取,不过最方便的还是调用ServicesContainer具有如下定义的扩展方法GetAssembliesResolver。
public static class ServicesExtensions
{
//其他成员
public static IAssembliesResolver GetAssembliesResolver(this ServicesContainer services);
}实例演示:自定义AssembliesResolver
通过上面的介绍我们知道默认使用的DefaultAssembliesResolver只挥提供当前应用程序域已经加载的程序集。如果我们将HttpController定义在非寄宿程序所在的程序集中(实际上在采用Self Host寄宿模式下,我们基本上都会选择在独立的项目中定义HttpController类型),即使我们将它们部属在宿主程序运行的目录中,宿主程序启动的时候也不会主动去加载这些程序集。在这种情况下,由于当前应用程序域中并不曾加载这些定义了HttpController的程序集,针对这些HttpController类型的解析是不会成功的。
我们可以通过一个简单的实例来证实这个问题。我们在一个解决方案中定义了如下4个项目,其中Foo、Bar和Baz为类库项目,相应的HttpController就定义在里面。Hosting是一个作为宿主的控制台程序,它具有对上述3个项目的引用。我们分别在项目Foo、Bar和Baz中定义了三个继承自ApiController的HttpController类型FooController、BarController和BazController,它们具有相同的定义:在为一个Get方法中返回当前HttpController包含程序集名在内的类型名称。
public class FooController : ApiController
{
public string Get()
{
return this.GetType().AssemblyQualifiedName;
}
}
public class BarController : ApiController
{
public string Get()
{
return this.GetType().AssemblyQualifiedName;
}
}
public class BarController : ApiController
{
 
补充:Web开发 , ASP.Net ,