先看ASP.NET Web API 讯息管线:

註:为了避免图片太大以至于超过版面,上图中的「HTTP 讯息处理程序」区块省略了 HttpRoutingDispatcher 处理路由分派的部分。「控制器」区块则省略了筛选条件(filter)的处理细节。微软网站有提供一份比较完整的 Web API 讯息处理流程图,网址是 http://www.microsoft.com/en-us/download/details.aspx?id=36476

此讯息管线架构图分为三层,由上至下,分别是装载(Hosting)、讯息处理程序(Message Handlers)、以及控制器(Controller)。图中的红色实心箭头代表 HTTP 请求讯息,虚线箭头代表 HTTP 响应消息。讯息处理流程如下:

  1. 当客户端对服务器发出的 HTTP 请求开始进入 ASP.NET Web API 框架时,该 HTTP 请求讯息会被包装成HttpRequestMessage对象,并且进入图中最顶端「装载」方块的HttpServer(web 装载)或HttpSelfHostServer(自我装载)。接着该讯息便流入管线的下一个阶段,直到整个讯息流程处理完毕,会得到一个代表 HTTP 响应消息的 HttpResponseMessage对象,并将此对象的讯息内容传回客户端。
  2. HttpRequestMessage对象进入「讯息处理程序」管线。在此阶段,HTTP 讯息行经数个讯息处理程序(message handlers),并且在返回 HTTP 响应消息时以相反的顺序执行。
  3. 在各个讯息处理程序之后,HTTP 请求讯息接着会传递给HttpControllerDispatcher,并且由这个对象来建立 Web API controller,然后将 HTTP 请求传递给 controller 对象(图中标示「(A) 建立 controller」的步骤)。
  4. Controller 会先决定目标动作方法(即图中标示「(B) 选择 action」的步骤),然后呼叫它。动作方法将负责产生响应内容,之后便依前述管线流程的反方向沿路返回。

以上便是 Web API HTTP 讯息管线的大致处理流程。

Web API Controller 是怎样建成的?

刚才只说明了 Web API HTTP 讯息管线的大致处理流程,而欲注入相依对象至 controller 类别的建构函式,或从中动些手脚来改变预设行为,必得了解 Web API 框架建立 controller 的内部过程。本节将进一步说明其中的复杂环节,其中会反复提及多个抽象接口,第一次阅读时可能略感吃力,并难免心生疑惑,但等到实际写过、跑过一遍后面的范例程序,再回头来看这一节的说明,整个拼图应该就会渐渐明朗了。

刚才提到,HttpControllerDispatcher会建立目标 controller 对象,亦即先前 ASP.NET Web

API 管线架构图中标示「(A) 建立 controller」的步骤。此步骤其实包含两件工作:

  1. 解析目标 controller。亦即决定该使用哪一个 controller 类别。
  2. 建立目标 controller 类别的实例,并将 HTTP 请求(HttpRequestMessage对象)传递给它,以便由 controller 进行后续处理。

首先,「解析目标 controller」的工作主要是从应用程序的 DLL 组件中寻找所有可用的 controller 类别,再从中选择一个与当前 HTTP request 匹配的。其处理逻辑如下图所示:

说明:

  • 图中下方的IAssembliesResolver对象的GetAssemblies方法将提供应用程序的组件列表,并由IHttpControllerTypeResolver对象的GetControllerTypes方法取得可用的 controller 类别清单。
  • IHttpControllerSelector负责决定要选择哪一个 controller 类别,然后返回一个包含其型别信息的HttpControllerDescriptor对象给HttpControllerDispatcher。

从确定目标 controller 型别之后,到建立完成 controller 实例的过程中,还有经过一些核心标准接口所提供的扩充点。底下再用一张 UML 活动图搭配 Web API 原始码的方式来解构其内部处理过程。

说明如下(与上图中的数字编号对应):

(1) HttpControllerDispatcher透过IHttpControllerSelector对象的SelectController方法来取得目标 controller 型别信息,这型别信息是包在一个HttpControllerDescriptor 对象里。

(2) HttpControllerDispatcher接着呼叫HttpControllerDescriptor对象的CreateController 方法,而该方法又会去呼叫ServicesContainer对象的GetHttpControllerActivator方法来取得IHttpControllerActivator对象。以下程序片段摘自 Web API 原始码,涵盖了此步骤至下一步骤的部分逻辑:

// HttpControllerDescriptor 类别的 CreateController 方法。
public virtual IHttpController CreateController(HttpRequestMessage request)
{
IHttpControllerActivator activator = Configuration.Services.GetHttpControllerActivator();
IHttpController instance = activator.Create(request, this, ControllerType);
return instance;
}

(3) 取得IHttpControllerActivator对象之后,便接着呼叫它的Create方法,而此方法会呼叫自己的GetInstanceOrActivator方法,以便取得 controller 实例。以下程序片段摘自DefaultHttpControllerActivator类别的原始码,我把错误处理以及快取机制的部分拿掉,并加上了中文批注:

// DefaultHttpControllerActivator 類別的 Create 方法(重點摘錄)
public IHttpController Create(HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
Func<IHttpController> activator; IHttpController controller =
GetInstanceOrActivator(request, controllerType, out activator);
if (controller != null)
{
// 註冊至 Web API 框架的 dependency resolver
// 已經建立此 controller 型別的執行個體。
return controller; // 那就直接使用此物件。
}
// 目標 controller 物件尚未建立
return activator(); // 那就用 GetInstanceOrActivator 方法傳回的委派來建立物件
}

(4) IHttpControllerActivator对象的GetInstanceOrActivator方法会呼叫HttpRequestMessage 的扩充方法GetDependencyScope来取得与当前 request 关联的IDependencyScope对象
(其实就是个 Service Locator),并利用它的GetService方法来取得 controller 对象。若 GetService方法并未传回 controller 对象,而是传回null(代表无法解析服务型别),则退而求其次,改用型别反射(reflection)机制来建立 controller 对象。一样搭配原始码来看:

// 摘自 DefaultHttpControllerActivator.cs
private static IHttpController GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, out Func<IHttpController>> activator)
{
// 若 dependency scope 有传回 controller 对象,便使用它。
IHttpController instance = (IHttpController)request.GetDependencyScope().GetService(controllerType);
if (instance != null)
{
activator = null;
return instance;
} // 否则,建立一个委派来创建此型别的实例。
activator = TypeActivator.Create<IHttpController>(controllerType);
return null;
}

其中的request.GetDependencyScope()就是对应到刚才说的「呼叫HttpRequestMessage 的扩充方法 GetDependencyScope 来取得与当前 request 关联的 IDependencyScope 对象。」而这里实际取得的IDependencyScope对象会是 Web API 框架提供的预设实作: EmptyResolver。从类别名称可知,这类别其实啥事也没做——它的GetService方法一律传回null。因此,在预设情况下,Web API 框架会一律使用型别反射(reflection)机制来建立 controller 对象,而这也就是为什么我们的 controller 类别一定要有预设建构函式(default constructor)的缘故。

大致上,controller 对象就是这么建成的。

本文摘自《.NET 相依性注入》一书的第 5 章。

最新文章

  1. C# 根据前台校验的值,决定是否执行后台方法
  2. Xml文件并发读写的解决方法
  3. jquery+bootstrap使用数字增减按钮
  4. Mybatis 保错:Mapped Statements collection already contains value for jaxrs.dch.projects.y
  5. log4j配置不同的类多个日志文件
  6. eclipse内存设置,tomcat内存设置,查看内存大小
  7. orcl
  8. Java字段初始化的规律
  9. 希赛网 &gt; 问答 &gt; 数据库 &gt; MySQL数据库 &gt; MySQL的管理与维护 &gt; MySql开启远程用户登录GRANTALLPRIVILEGESON*.*TO&#39;root&#39;@&#39;%&#39;I MySql开启远程用户登录GRANTALLPRIVILEGESON*.*TO&#39;root&#39;@&#39;%&#39;I
  10. Maximum Random Walk(概率dp)
  11. 修正android cocos2dx项目当点击属性时提示错误的问题
  12. 微信小程序入门(前言)
  13. 一:配置使用阿里云Maven库
  14. 业余草分享 Spring Boot 2.0 正式发布的新特性
  15. 20175201课下作业 MyCP
  16. numpy 和tensorflow 中的乘法
  17. oracle中计算百分比,并同时解决小数点前0不显示的问题
  18. sql语句中 “where 1=1” 的用处
  19. SAP BW: Replacement Path Variables
  20. Javascript-多个数组是否有一样值

热门文章

  1. 【hdu 2376】Average distance
  2. Android Studio如何删除module
  3. Python 标准库 —— uuid(生成唯一 ID)
  4. 自己动手编写一个VS插件(六)
  5. javascript常用的基础函数或方法——写给新手的我(持续补充)
  6. matlab 矢量化编程(四)—— 标量函数转化为能够处理矢量的函数
  7. hbase结合hive和sqoop实现数据指导mysql
  8. Easyui Tab刷新
  9. 如何加入该网站for Linux(绑定域名)
  10. MVC WebApi的两种访问方法