ASP.NET Web API下的HttpController激活:程序集的解析
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 interface 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
- {
- public string Get()
- {
- return this.GetType().AssemblyQualifiedName;
- }
- }
我们在作为宿主的Hosting程序中利用如下的代码以Self Host的模式来寄宿定义在上述3个HttpController中的Web API。我们针对基地址“http://127.0.0.1:3721”创建了一个HttpSelfHostServer,在开启之前我们注册了一个URL模板为“api/{controller}/{id}”的HttpRoute。
- class Program
- {
- static void Main(string[] args)
- {
- Uri baseAddress = new Uri("http://127.0.0.1:3721");
- using (HttpSelfHostServer httpServer = new HttpSelfHostServer( new HttpSelfHostConfiguration(baseAddress)))
- {
- httpServer.Configuration.Routes.MapHttpRoute(
- name : "DefaultApi",
- routeTemplate : "api/{controller}/{id}",
- defaults : new { id = RouteParameter.Optional });
- httpServer.OpenAsync().Wait();
- Console.Read();
- }
- }
- }

在启动宿主程序后,我们试图通过浏览器对分别定义在FooController、BarController和BazController中的Action方法Get发起访问,不幸的是我们会得到如下图所示的结果。从显示在浏览器中的消息我们很清楚问题的症结所在:根据路由解析得到HttpController名称并不能解析出匹配的HttpController类型。
导致上述这个问题的原因我们在上面已经分析过了:默认采用的DefaultAssembliesResolver仅仅提供当前应用程序域加载的程序集,那么我们可以通过自定义的AssembliesResolver来解决这个问题。我们的解决思路是:让需要预先加载的程序集可配置。具体的解决方案是利用具有如下结构的配置来设置需要预先加载的程序集。
- <configuration>
- <configSections>
- <section name="preLoadedAssemblies" type="Hosting.PreLoadedAssembliesSettings, Hosting"/>
- </configSections>
- <preLoadedAssemblies>
- <add assemblyName ="Foo.dll"/>
- <add assemblyName ="Bar.dll"/>
- <add assemblyName ="Baz.dll"/>
- </preLoadedAssemblies>
- </configuration>
所以在创建自定义的AssembliesResolver之前我们先得为这段配置定义相应的配置节和配置元素类型。相关的类型定义如下所示,由于配置结构比较简单,在这里我们不对它们作详细介绍了。
- public class PreLoadedAssembliesSettings: ConfigurationSection
- {
- [ConfigurationProperty("", IsDefaultCollection = true)]
- public AssemblyElementCollection AssemblyNames
- {
- get { return (AssemblyElementCollection)this[""]; }
- }
- public static PreLoadedAssembliesSettings GetSection()
- {
- return ConfigurationManager.GetSection("preLoadedAssemblies") as PreLoadedAssembliesSettings;
- }
- }
- public class AssemblyElementCollection : ConfigurationElementCollection
- {
- protected override ConfigurationElement CreateNewElement()
- {
- return new AssemblyElement();
- }
- protected override object GetElementKey(ConfigurationElement element)
- {
- AssemblyElement serviceTypeElement = (AssemblyElement)element;
- return serviceTypeElement.AssemblyName;
- }
- }
- public class AssemblyElement : ConfigurationElement
- {
- [ConfigurationProperty("assemblyName", IsRequired = true)]
- public string AssemblyName
- {
- get { return (string)this["assemblyName"]; }
- set { this["assemblyName"] = value; }
- }
- }
由于我们自定义的AssembliesResolver是对现有DefaultAssembliesResolver的扩展(尽管其程序集提供机制仅仅通过一句代码来实现),我们将类型命名为ExtendedDefaultAssembliesResolver。如下面的代码片断所示,ExtendedDefaultAssembliesResolver继承自DefaultAssembliesResolver,在重写的GetAssemblies方法中我们先通过分析上述的配置并主动加载尚未加载的程序集,最后才调用基类的同名方法来提供最终的程序集。
- public class ExtendedDefaultAssembliesResolver : DefaultAssembliesResolver
- {
- public override ICollection<Assembly> GetAssemblies()
- {
- PreLoadedAssembliesSettings settings = PreLoadedAssembliesSettings.GetSection();
- if (null != settings)
- {
- foreach (AssemblyElement element in settings.AssemblyNames)
- {
- AssemblyName assemblyName = AssemblyName.GetAssemblyName(element.AssemblyName);
- if(!AppDomain.CurrentDomain.GetAssemblies() .Any(assembly=>AssemblyName.ReferenceMatchesDefinition( assembly.GetName(),assemblyName)))
- {
- AppDomain.CurrentDomain.Load(assemblyName);
- }
- }
- }
- return base.GetAssemblies();
- }
- }
我们在作为宿主的Hosting程序中利用如下的代码将一个ExtendedDefaultAssembliesResolver对象注册到当前HttpConfiguration的ServicesContainer上。
- class Program
- {
- static void Main(string[] args)
- {
- Uri baseAddress = new Uri("http://127.0.0.1:3721");
- using (HttpSelfHostServer httpServer = new HttpSelfHostServer( new HttpSelfHostConfiguration(baseAddress)))
- {
- httpServer.Configuration.Services.Replace( typeof(IAssembliesResolver), new ExtendedDefaultAssembliesResolver());
- //其他操作
- }
- }
- }

重新启动宿主程序后再次在浏览器输入对应的地址来访问分别定义在FooController、BarController和BazController中的Action方法Get,这次我们可以得到我们期望的结果了。具体的输出结果如右图所示。
WebHostAssembliesResolver
由于DefaultAssembliesResolver在为HttpController类型解析提供的程序集仅限于当前应用程序域已经加载的程序集,如果目标HttpController定义在尚未加载的程序集中,我们不得不预先加载它们。但是这样的问题只会发生在Self Host模式下,Web Host则无此困扰,原因在于后者默认使用的是另一个AssembliesResolver。
我们知道在Web Host模式下用于配置ASP.NET Web API消息处理管道的是通过类型GlobalConfiguration的静态只读属性Configuration返回的HttpConfiguration。从如下的代码片断我们可以发现,当GlobalConfiguration的Configuration属性被第一次访问的时候,在ServicesContainer中注册的AssembliesResolver会被替换成一个类型为WebHostAssembliesResolver的对象。
- public static class GlobalConfiguration
- {
- //其他成员
- static GlobalConfiguration()
- {
- _configuration = new Lazy<HttpConfiguration>(delegate {
- HttpConfiguration configuration = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));
- configuration.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
- //其他操作
- return configuration;
- });
- //其他操作
- }
- public static HttpConfiguration Configuration
- {
- get
- {
- return _configuration.Value;
- }
- }
- }
WebHostAssembliesResolver是定义在程序集System.Web.Http.WebHost.dll中的一个内部类型。从如下的代码片断可以看出,WebHostAssembliesResolver在实现的GetAssemblies方法中直接通过调用BuildManager的GetReferencedAssemblies方法来获取最终提供的程序集。
- internal sealed class WebHostAssembliesResolver : IAssembliesResolver
- {
- ICollection<Assembly> IAssembliesResolver.GetAssemblies()
- {
- return BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();
- }
- }
由于BuildManager的GetReferencedAssemblies方法几乎返回了所有在运行过程中需要的所有程序集,所以在我们将HttpController定义在单独的程序集中,我们只要确保该程序集已经正常部属就可以了。如果读者朋友们感兴趣,可以试着将上面掩饰的实例从Self Host转换成Web Host,看看ASP.NET Web API的HttpController激活系统能够正常解析出分别定义在Foo.dll、Bar.dll和Baz.dll中的HttpController类型。
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
ASP.NET Web API下的HttpController激活:程序集的解析的更多相关文章
- 总体介绍ASP.NET Web API下Controller的激活与释放流程
通过<ASP.NET Web API的Controller是如何被创建的?>我们已经对HttpController激活系统的核心对象有了深刻的了解,这些对象包括用于解析程序集和有效Http ...
- ASP.NET Web API下Controller激活
一.HttpController激活流程 对于组成ASP.NET Web API核心框架的消息处理管道来说,处于末端的HttpMessageHandler是一个HttpRoutingDispatche ...
- How ASP.NET Web API 2.0 Works?[持续更新中…]
一.概述 RESTful Web API [Web标准篇]RESTful Web API [设计篇] 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 二.路由 ...
- 目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择
目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择 ASP.NET Web API能够根据请求激活目标HttpController ...
- 剖析Asp.Net Web API中HttpController的激活
在Asp.Net Web API中,请求的目标是定义在某个HttpController中的某个Action方法.当请求经过Asp.Net Web API消息处理管道到达管道"龙尾" ...
- ASP.NET Web API的核心对象:HttpController
ASP.NET Web API的核心对象:HttpController 对于ASP.NET Web API来说,所谓的Web API定义在继承自ApiController的类中,可能ApiContro ...
- Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?
构成ASP.NET Web API核心框架的消息处理管道既不关心请求消息来源于何处,也不需要考虑响应消息归于何方.当我们采用Web Host模式将一个ASP.NET应用作为目标Web API的宿主时, ...
- ASP.NET Web API 控制器创建过程(一)
ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
随机推荐
- WebView无法放大缩小解决方式
先看看我们之前所写的代码 1) 加入权限:AndroidManifest.xml中必须使用了许可"android.permission.INTERNET" 2) 使用了一个WebV ...
- 【百度地图API】情人节求爱大作战——添加标注功能
原文:[百度地图API]情人节求爱大作战--添加标注功能 任务描述: 2月2日是除夕,2月14立马来!即将到来的情人节,你想送TA一份什么礼物呢? 不如,在你们居住的地方,画个大大的桃心,表达你对TA ...
- iOS_文章3党库SDWebImage
1,下载的文章3党库SDWebImage代码包增加到project 2,进入project的Build Phases,将源代码包里面的所有.m文件所有加入到project 3,导入第3方类库依赖的两个 ...
- VS2012 update1 和 VS2012 Lang Pack 离线安装方法
原文:VS2012 update1 和 VS2012 Lang Pack 离线安装方法 最近有需要用VS2012打开ReactOS这个大项目,生成sln后打开用来导航代码什么的,但其代码都是ascii ...
- MonkeyRunner源码分析之工作原理图-attach
花了点时间整理了下MonkeyRunner的工作原理图: Item Description Warning Author 天地会珠海分舵 转载请注明出处! Blog Address http://bl ...
- C#获取本机所有用户名
using System.DirectoryServices; using System.Runtime.InteropServices; (需要添加引用) [StructLayout(LayoutK ...
- 【android】WebView缓存数据收集
Android WebView 缓存 Android高手进阶教程(二十四)之---Android WebView的缓存!!! Android webView 缓存 Cache + HTML5离线功能 ...
- Windows 7硬盘安装CentOS 6.4 双系统 (WIN7硬盘安装Linux(Fedora 16,CentOS 6.2,Ubuntu 12.04))
WIN7下硬盘安装Linux(Fedora 16,CentOS 6.2.Ubuntu 12.04) 近期在看<鸟哥私房菜:基础学习篇>.认为非常不错,想要用U盘装个windows 7 和 ...
- Erlang运行中的错误
Erlang运行时发生错误时,会返回一些错误信息,理解这些信息,对于学好.用好Erlang来说是必要. Erlang中的运行错误包括:badarg, badarith, badmatch, funct ...
- Inno Setup 网页显示插件 webctrl
原文:Inno Setup 网页显示插件 webctrl ; -- Example.iss -- ; restools ; http://restools.hanzify.org ; 插件名:webc ...