AssembliesResolver为HttpController类型的解析提供了可供选择的程序集,在ASP.NET Web API的HttpController激活系统中真正用于解析HttpController类型的是一个名为HttpControllerTypeResolver的对象。所有的HttpControllerTypeResolver类型均实现了具有如下定义的接口IHttpControllerTypeResolver,定义其中的唯一方法GetControllerTypes借助于提供的AssembliesResolver解析出所有的HttpController类型。[本文已经同步到《How ASP.NET Web API Works?》]
1: public interface IHttpControllerTypeResolver
2: {
3: ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
4: }
与AssembliesResolver类似,默认使用的HttpControllerTypeResolver同样是注册在当前HttpConfiguration的ServicesContainer上,我们可以通过ServicesContainer具有如下定义的扩展方法GetHttpControllerTypeResolver得到这个注册的HttpControllerTypeResolver对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerTypeResolver GetHttpControllerTypeResolver(this ServicesContainer services);
5: }
我们同样可以通过HttpConfiguration默认采用的DefaultServices的构造函数得到默认注册的HttpControllerTypeResolver对象的类型。如下面的代码片断所示,这个默认注册的HttpControllerTypeResolver是一个类型为DefaultHttpControllerTypeResolver的对象。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerTypeResolver>(new DefaultHttpControllerTypeResolver ());
8: }
9: }
默认使用的DefaultHttpControllerTypeResolver类型定义在System.Web.Http.Dispatcher命名空间下。如下面的代码片断所示,它具有一个只读的属性IsControllerTypePredicate返回一个类型为Predicate<Type>的委托,该委托对象用于判断指定的类型是否是一个有效的HttpController类型。
1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
2: {
3: public DefaultHttpControllerTypeResolver();
4: public DefaultHttpControllerTypeResolver(Predicate<Type> predicate);
5:
6: public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
7: protected Predicate<Type> IsControllerTypePredicate { get; }
8: }
作为IsControllerTypePredicate 属性值的Predicate<Type>对象可以直接通过构造函数的参数来指定。在默认情况下,这个自动初始化的Predicate<Type>对象体现了默认的有效HttpController类型判断规则。具体来说,默认情况下一个给定的类型必须同时满足如下的条件才是一个有效的HttpController类型。
用于获取所有有效HttpController类型的GetControllerTypes方法的实现逻辑其实很简单:它通过指定的AssembliesResolver得到所有定义了HttpController类型的潜在程序集。对于定义在这些程序集中的所有外部可见的公有类型,如果满足上述的要求就是返回的类型集合之一。定义在类型DefaultHttpControllerTypeResolver中的HttpController类型的解析逻辑基本上体现在如下所示的代码中。
1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
2: {
3: //其他成员
4: public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
5: {
6: List<Type> types = new List<Type>();
7: foreach (Assembly assembly in assembliesResolver.GetAssemblies())
8: {
9: foreach (Type type in assembly.GetExportedTypes())
10: {
11: if (this.IsControllerTypePredicate(type))
12: {
13: types.Add(type);
14: }
15: }
16: }
17: return types;
18: }
19: }
由于针对所有HttpController类型的解析需要大量使用到反射,所以是一个相对耗时的事情,所以ASP.NET Web API会对解析出来的HttpController类型进行缓存。具体的缓存实现在具有如下定义的HttpControllerTypeCache类型中,这是一个定义在程序集System.Web.Http.dll中的内部类型。
1: internal sealed class HttpControllerTypeCache
2: {
3: public HttpControllerTypeCache(HttpConfiguration configuration);
4: public ICollection<Type> GetControllerTypes(string controllerName);
5: internal Dictionary<string, ILookup<string, Type>> Cache { get; }
6: }
缓存的HttpController类型通过只读属性Cache获取,这是一个类型为Dictionary<string, ILookup<string, Type>>的字典对象。该字典的Key表示HttpController的名称(HttpController类型名称去除“Controller”后缀),其Value返回的Lookup<string, Type>包含一组具有相同名称的HttpController类型列表,自身的Key表示HttpController类型的命名空间。
当HttpControllerTypeCache对象的Cache属性第一次被提取的时候,它会通过构造时指定的HttpConfiguration从ServicesContainer中获取注册的HttpControllerTypeResolver得到所有的HttpController类型并构造一个Dictionary<string, ILookup<string, Type>>对象返回。HttpControllerTypeCache的另一个方法GetControllerTypes就是根据指定的HttpController名称从这个Cache属性中提取相应的HttpController类型列表。
虽然HttpControllerTypeCache仅仅是个内部类型,但是由它 实现的针对解析出来的HttpController类型的缓存对性能的改善具有重要的作用。为了让读者加深对此的认识,我们通过一个简单的实例来演示HttpControllerTypeCache对HttpController类型的缓存功能。
我们通过一个ASP.NET MVC应用来展示被HttpControllerTypeCache缓存的HttpController类型。除此之外,我们在上面介绍“程序集的解析”过程谈到在采用Web Host寄宿模式下无须手工加载定义HttpController的程序集,为了证实这一点我们在解决方案中额外添加了两个类库项目HttpControllers1和HttpControllers2。解决方案的具体结构如右图所示,MvcApp具有对HttpControllers1和HttpControllers2的项目引用。
我们分别在项目HttpControllers1和HttpControllers2中定义了如下两组同名的HttpController:FooController、BarController和BazController。简单起见,我们不曾定义任何类型成员。
1: namespace HttpControllers1
2: {
3: public class FooController : ApiController{}
4: public class BarController : ApiController{}
5: public class BazController : ApiController{}
6: }
7:
8: namespace HttpControllers2
9: {
10: public class FooController : ApiController{}
11: public class BarController : ApiController{}
12: public class BazController : ApiController{}
13: }
接下来我们在MvcApp项目中定义了如下一个HttpControllerTypeCacheEntry类型,其三个属性ControllerName、Namespace和ControllerType分别表示HttpController的名称、命名空间和类型。这个HttpControllerTypeCacheEntry对象实际上代表了被HttpControllerTypeCache缓存的“条目”。
1: public class HttpControllerTypeCacheEntry
2: {
3: public string ControllerName { get; set; }
4: public string Namespace { get; set; }
5: public Type ControllerType { get; set; }
6: }
我们在MvcApp项目中定义了如下一个HomeController。在默认的Action方法Index中,我们通过反射创建了HttpControllerTypeCache对象并返回其属性Cache表示的Dictionary<string, ILookup<string, Type>>对象,然后将它转换为一个HttpControllerTypeCacheEntry对象的列表由默认的View呈现出来。
1: public class HomeController : Controller
2: {
3: public ActionResult Index()
4: {
5: Type typeCacheType = Type.GetType("System.Web.Http.Dispatcher.HttpControllerTypeCache, System.Web.Http");
6: object typeCache = Activator.CreateInstance(typeCacheType, new object[]{GlobalConfiguration.Configuration});
7: PropertyInfo cacheProperty = typeCacheType.GetProperty("Cache", BindingFlags.Instance | BindingFlags.NonPublic);
8: Dictionary<string, ILookup<string, Type>> cachedTypes = (Dictionary<string, ILookup<string, Type>>)cacheProperty.GetValue(typeCache, null);
9: List<HttpControllerTypeCacheEntry> list = new List<HttpControllerTypeCacheEntry>();
10:
11: foreach (var item in cachedTypes)
12: {
13: foreach (string key in item.Value.Select(group=>group.Key).Distinct())
14: {
15: foreach (Type type in item.Value[key])
16: {
17: HttpControllerTypeCacheEntry entry = new HttpControllerTypeCacheEntry
18: {
19: ControllerName = item.Key,
20: Namespace = key,
21: ControllerType = type
22: };
23: list.Add(entry);
24: }
25: }
26: }
27:
28: return View(list);
29: }
30: }
如下所示的是Action方法Index对应View的定义,这是一个Model类型为IEnumerable<HttpControllerTypeCacheEntry>的强类型View。在该View中,我们将作为Model的HttpControllerTypeCacheEntry列表以表格的形式呈现出来。
1: @model IEnumerable<HttpControllerTypeCacheEntry>
2: <html>
3: <head>
4: <title>HttpController类型缓存</title>
5: </head>
6: <body>
7: <table>
8: <thead>
9: <tr>
10: <th>Controller Name</th>
11: <th>Namespace</th>
12: <th>Controller Type</th>
13: </tr>
14: </thead>
15: <tbody>
16: @foreach (HttpControllerTypeCacheEntry entry in Model)
17: {
18: <tr>
19: <td>@entry.ControllerName</td>
20: <td>@entry.Namespace</td>
21: <td>@entry.ControllerType.Name</td>
22: </tr>
23: }
24: </tbody>
25: </table>
26: </body>
27: </html>直接运行该程序后会在浏览器中呈现出如下图所示的输出结果,由此可见HttpControllerTypeCache能自动地解析并缓存所有的HttpController类型,而且这个被缓存的数据具有我们上面分析的结果。