先看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 章。

ASP.NET Web API Controller 是怎么建成的的更多相关文章

  1. Professional C# 6 and .NET Core 1.0 - Chapter 42 ASP.NET Web API

    本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处: -------------------------------------------------------- ...

  2. [水煮 ASP.NET Web API2 方法论](1-1)在MVC 应用程序中添加 ASP.NET Web API

    问题 怎么样将 Asp.Net Web Api 加入到现有的 Asp.Net MVC 项目中 解决方案 在 Visual Studio 2012 中就已经把 Asp.Net Web Api 自动地整合 ...

  3. On the nightmare that is JSON Dates. Plus, JSON.NET and ASP.NET Web API

    Ints are easy. Strings are mostly easy. Dates? A nightmare. They always will be. There's different c ...

  4. ASP.NET Web API中的Controller

    虽然通过Visual Studio向导在ASP.NET Web API项目中创建的 Controller类型默认派生与抽象类型ApiController,但是ASP.NET Web API框架本身只要 ...

  5. ASP.NET Web API的Controller是如何被创建的?

    Web API调用请求的目标是定义在某个HttpController类型中的某个Action方法,所以消息处理管道最终需要激活目标HttpController对象.调用请求的URI会携带目标HttpC ...

  6. [ASP.NET Web API]如何Host定义在独立程序集中的Controller

    通过<ASP.NET Web API的Controller是如何被创建的?>的介绍我们知道默认ASP.NET Web API在Self Host寄宿模式下用于解析程序集的Assemblie ...

  7. 总体介绍ASP.NET Web API下Controller的激活与释放流程

    通过<ASP.NET Web API的Controller是如何被创建的?>我们已经对HttpController激活系统的核心对象有了深刻的了解,这些对象包括用于解析程序集和有效Http ...

  8. ASP.NET Web API 框架研究 Controller实例的销毁

    我们知道项目中创建的Controller,如ProductController都继承自ApiController抽象类,其又实现了接口IDisposable,所以,框架中自动调用Dispose方法来释 ...

  9. ASP.NET Web API下Controller激活

    一.HttpController激活流程 对于组成ASP.NET Web API核心框架的消息处理管道来说,处于末端的HttpMessageHandler是一个HttpRoutingDispatche ...

随机推荐

  1. SCM文章9类:外部中断示例程序

    JP3遇见P0口,JP5遇见P3口,P1接受该发光二极管,什么时候P1所有的都是高时,,全亮度发光二极管.因为外部中断0和1用同样的方法.这里只是外部中断0计划. #include<reg51. ...

  2. for循环中setTimeout,var与let的不同

    先看下面两段代码 for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i) }, 2000) } for (va ...

  3. WPF中MVVM模式的 Event 处理

    WPF的有些UI元素有Command属性可以直接实现绑定,如Button 但是很多Event的触发如何绑定到ViewModel中的Command呢? 答案就是使用EventTrigger可以实现. 继 ...

  4. Maven软件项目管理工具

    http://my.oschina.net/jgy/blog/125503 拷贝mavne安装文件夹conf以下的settings.xml到用户主文件夹下 改动改文件 <localReposit ...

  5. Linux开关命令(shutdown,reboot,halt,init)

    命令简短 shutdown,poweroff,reboot,halt,init都能够进行关机,大致使用方法. /sbin/halt     [-n] [-w] [-d] [-f] [-i] [-p] ...

  6. Mac版Visual Studio预览版

    来了,Mac版Visual Studio预览版开放下载 投递人 itwriter 发布于 2016-11-17 12:11 评论(7) 有1317人阅读 原文链接 [收藏] « » 微软前俩天宣布,推 ...

  7. HDU5187 zhx&#39;s contest(计数问题)

    主题链接: http://acm.hdu.edu.cn/showproblem.php?pid=5187 题意: 从1~n,有多少种排列 使得 a1~ai 满足单调递增或者单调递减. ai~an 满足 ...

  8. ANDROID-BOOTSTRAP开源项目使用方法

    1.将程序导入到工作空间,修改target=android-XX为本地android SDK版本. 2.在项目中点击右键选择Properties->Android Library,添加ANDRO ...

  9. css 浏览器兼容性问题解决

    一个.!important (功能有限)   随着IE7正确!important支持, !important 方法现在只IE6兼容.(注意措辞.我记得这句话需要推进的位置.)   : #example ...

  10. Java Swing编程接口(30)---列表框:JList

    列表框同时可以在信息呈现给用户的列表多个选项,使用JList能够建立一个列表框. package com.beyole.util; import java.awt.Container; import ...