先看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. Eclipse迅速执行:Exception in thread &quot;main&quot; java.lang.OutOfMemoryError: Java heap space

    问题叙述性说明: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 问题原因: 程序中对 ...

  2. java.lang.ExceptionInInitializerError异常分析

    今天在项目开发时遇到一个问题,整个项目是使用Spring等框架搭建起来的在运行项目时不报任何的异常信息,就是找不到某个类信息,各方查找该类确实是存在的,最后通过断点跟踪时在异常栈内发现java.lan ...

  3. Android中的后台邮件发送

    一.调用邮件发送工具类进行邮件发送 new Thread(){ public void run() { try { GMailSender sender = new GMailSender( &quo ...

  4. cordova/phoneGap 开发调试工具

    原文地址 一.前言 Ripple Emulate:使用Google模拟器,可以帮到大部分的调试,除了需要调用手机设备方面的功能除外.GapDebug:真机安装apk,电脑端和真机端同步调试,适用的项目 ...

  5. chrome浏览器***

    chrome浏览器***: "红杏"是一款 Chrome 浏览器插件.(PS:不用 Chrome 的同学赶紧去下载安装.那个***大家都懂得.)相较于其它***方式,"红 ...

  6. RadioButton分组的实现

    原文:RadioButton分组的实现     XAML如下 <StackPanel> <RadioButton GroupName="colorgrp"> ...

  7. sklearn 文本处理

    from sklearn.feature_extraction.text import ** 1. 向量的统计.tf-idf 的计算 考虑如下预料,三行 ⇒ 三个文档,不重复的单词共有 8 个, co ...

  8. P和P1指向了O和O1两个变量(对象)的地址, 而不是O和O1的内容(对象的实际地址)——充分证明@是取变量(对象)的地址,而不是变量里面的内容,够清楚!

    如图,为什么这样取出来的p,p1的值不一样呢?   165232328群友庾伟洪告诉我:  P和P1指向了O和O1两个变量(对象)的地址, 而不是O和O1的内容(对象的实际地址) ,你想P指向真正的对 ...

  9. Python算法(含源代码下载)

    关键字:Python Algorithms Python算法  Mastering Basic Algorithms in the Python Language 使用Python语言掌握基本算法 P ...

  10. Python3获取股票行情数据(中国个股/中国指数/全球指数)

    #!/usr/local/bin/python3 #coding=utf-8 #source http://www.cnblogs.com/txw1958/ import os, io, sys, r ...