前言

中秋歇了歇,途中也时不时去看看有关创建控制器的原理以及解析,时间拖得比较长,实在是有点心有余而力不足,但又想着既然诺下了要写完原理一系列,还需有始有终。废话少说,直入主题。

HttpControllerDispatcher

遗留问题 :在第六篇末尾所给图中有一个HttpControllerDispatcher未进行叙述,在本篇中去叙述正合时宜。

在第六篇我们也说过在Web API消息处理管道中的最后一个处理程序是HttpRoutingDispacher,它被用来激活控制器,这样说虽然没错,但是不够具体,实际上是利用隶属于它的HttpControllerDispatcher来激活控制器,而当我们调用HttpRoutingDispatcher中的SendAsync方法来处理请求,实际上是调用HttpControllerDispatcher中的一个私有方法 SendAsyncInternal 来处理请求,下面我们来看看这个方法的定义:

最重要的当属以上这三个部分,下面我们一一来进行解析(放松心情,容我娓娓道来)。

通过上面我们看到了CreateController这个方法,是的,你没看错,就是这个方法里面生成了APIController,那是不是就讲完了呢?当然不是,你得知道到底是怎样创建的,在此方法中只是进行了调用了方法来创建APIController控制器而已,其中的细节还得我们慢慢去摸索。

第一步:HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request)

我们首先看看这个属性ControllerSelector的定义:

private IHttpControllerSelector ControllerSelector
{
get
{
if (this._controllerSelector == null)
{
this._controllerSelector = this._configuration.Services.GetHttpControllerSelector();
}
return this._controllerSelector;
}
}

这个属性是通过返回值为HttpConfiguration的属性_configuration来获取,接下来我们来看看HttpConfiguration的定义,列举出下面最重要的两个属性:

public IDependencyResolver DependencyResolver { get; set; }

public ServicesContainer Services { get; internal set; }

ServicesContainer是服务的容器,在消息处理管道中每一个环节都是通过注册组件来完成相应的任务,而这些组件都是通过实现了某个接口而创建的,而服务容器正是这些接口的基点,ServicesContainer就是所谓的DI容器,将我们需要的服务注册到其中,但是它只是预定义了许多接口,(IDependencyResolver作用也类似于ServicesContainer)所以我们需要看其子类 DefaultServices 的具体实现,我们来看看其子类构造函数传入HttpConfiguration的比较重要的一个定义:

 this.SetSingle<IHttpControllerSelector>(new DefaultHttpControllerSelector(configuration));

从此句知,上述的IHttpControllerSelector接口所定义的属性ControllerSelector就是我们注册的 DefaultHttpControllerSelector ,同时依上述也知,SelectController是通过此类而实现,所以我们再来看看此类中关于此方法的定义及实现:

 public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor descriptor;
string controllerName = this.GetControllerName(request);
if (this._controllerInfoCache.Value.TryGetValue(controllerName, out descriptor))
{
return descriptor;
}
ICollection<Type> controllerTypes = this._controllerTypeCache.GetControllerTypes(controllerName);
}

我们首先看看是红色标记第一个,它是如何获得控制器名controllerName的,查看其GetControllerName方法,如下:

public virtual string GetControllerName(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
string str = null;
routeData.Values.TryGetValue<string>("controller", out str);
return str;
}

我们知道当请求过来时会与注册的路由进行匹配并解析并将其数据封装到RouData对象中,以路由模板中大括号的值为键,以请求的URL中对应的控制器为值,所以上述就获取RouteData中的键所对应的值并返回控制器名。  

接下来我们再来看红色标记第二个属性_controllerInfoCache,此属性存在于DefaultHttpControllerSelector,所以还是仔细看看该类定义:

 public class DefaultHttpControllerSelector : IHttpControllerSelector
{
// Fields
private readonly HttpConfiguration _configuration;
private readonly Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>> _controllerInfoCache;
private readonly HttpControllerTypeCache _controllerTypeCache;
private const string ControllerKey = "controller";
public static readonly string ControllerSuffix; // Methods
static DefaultHttpControllerSelector();
public DefaultHttpControllerSelector(HttpConfiguration configuration);
private static Exception CreateAmbiguousControllerException(IHttpRoute route, string controllerName, ICollection<Type> matchingTypes);
public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
public virtual string GetControllerName(HttpRequestMessage request);
private ConcurrentDictionary<string, HttpControllerDescriptor> InitializeControllerInfoCache();
public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request);
}

根据_controllerInfoCache,我们从表面意思知是控制器信息缓存,这个得重点讲述下,为何?因为当请求过来时,我们会选择控制器,但是要选择哪个控制器呢,因为对于控制器的类型解析如果使用反射将需要耗费大量的时间,肯定是惨不忍睹的,所以我们就对解析出来的控制类型进行缓存,这将大大提高效率并节约时间,所以由上知,控制器选择器ControllerSelector的作用是两个:

  • 根据请求选择相应的选择控制器

  • 生成控制器缓存  

由上述知控制器的缓存类型,即属性_controllerInfoCache的返回类型  Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>>  ,该字典中字符串为控制器名称,而HttpControllerDescriptor是对控制器的相关描述类型,这个类型我们下面讲,那它是怎么获取到缓存的呢?这个时候我们就要看看上述红色标记中关于DefaultHttpControllerSelector的构造函数,查看如下:

 public DefaultHttpControllerSelector(HttpConfiguration configuration)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
this._controllerInfoCache = new Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>>(new Func<ConcurrentDictionary<string, HttpControllerDescriptor>>(this.InitializeControllerInfoCache));
this._configuration = configuration;
this._controllerTypeCache = new HttpControllerTypeCache(this._configuration);
}

由上知,是通过使用延迟加载技术来对控制器缓存进行了创建而创建则是通过 InitializeControllerInfoCache 方法来进行,下面我们继续看看该方法的实现:

 private ConcurrentDictionary<string, HttpControllerDescriptor> InitializeControllerInfoCache()
{
ConcurrentDictionary<string, HttpControllerDescriptor> dictionary = new ConcurrentDictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
HashSet<string> set = new HashSet<string>();
foreach (KeyValuePair<string, ILookup<string, Type>> pair in this._controllerTypeCache.Cache)
{
string key = pair.Key;
foreach (IGrouping<string, Type> grouping in pair.Value)
{
foreach (Type type in grouping)
{
if (dictionary.Keys.Contains(key))
{
set.Add(key);
break;
}
dictionary.TryAdd(key, new HttpControllerDescriptor(this._configuration, key, type));
}
}
}
foreach (string str2 in set)
{
HttpControllerDescriptor descriptor;
dictionary.TryRemove(str2, out descriptor);
}
return dictionary;
}

注意上述该方法中的红色标记知,最终是通过遍历控制器类型缓存而来,所以我们的重点就是控制器类型缓存 HttpControllerTypeCache 。

那问题来了,该控制器缓存类是如何而来的呢?  

这个时候就要用到服务容器ServiceContainer了即该子类DefaultServices注入的服务,如下:

this.SetSingle<IHttpControllerTypeResolver>(new DefaultHttpControllerTypeResolver());

再来看看此类的定义:

 public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
{
// Fields
private readonly Predicate<Type> _isControllerTypePredicate; // Methods
public DefaultHttpControllerTypeResolver();
public DefaultHttpControllerTypeResolver(Predicate<Type> predicate);
public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
internal static bool IsControllerType(Type t); // Properties
protected Predicate<Type> IsControllerTypePredicate { get; }
}

通过上述方法根据过滤条件等来加载指定程序集中所有符合条件的控制其类型(ControllerTypes)。这个时候控制器类型缓存就急不可耐了,一旦加载了所有控制器类型,它就会通过构造函数获得所有控制类型并进行分组。如下:

private Dictionary<string, ILookup<string, Type>> InitializeCache()
{
IAssembliesResolver assembliesResolver = this._configuration.Services.GetAssembliesResolver();
return this._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(assembliesResolver).GroupBy<Type, string>(t => t.Name.Substring(, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, Type>, string, ILookup<string, Type>>(g => g.Key, g => g.ToLookup<Type, string>(t => (t.Namespace ?? string.Empty), StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
}

但是此时还不是最终的缓存类型,上述已经说过会通过 DefaultHttpControllerSelector 中的方法 InitializeControllerInfoCache 遍历该控制器类型缓存所生成的类型为  Lazy<Dictionary<string, ILookup<string, Type>>>  而得到类型为  Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>> 。此上就是整个生成控制器缓存的过程。

接下来就是进入最后一个处理程序 HttpControllerDispatcher 中的方法 SendAsyncInternal 方法的第二步,在选择请求中的对应的控制器以及进行控制器缓存后并返回HttpControllerDescriptor来创建控制器以及激活控制器。请继续往下看。

HttpControllerDescriptor

第二步:IHttpController controller = descriptor.CreateController(request)

我们看看此类的定义:

 public class HttpControllerDescriptor
{
// Fields
private object[] _attrCached;
private HttpConfiguration _configuration;
private string _controllerName;
private Type _controllerType;
private readonly ConcurrentDictionary<object, object> _properties; // Methods
public HttpControllerDescriptor();
internal HttpControllerDescriptor(HttpConfiguration configuration);
public HttpControllerDescriptor(HttpConfiguration configuration, string controllerName, Type controllerType);
public virtual IHttpController CreateController(HttpRequestMessage request);
public virtual Collection<T> GetCustomAttributes<T>() where T: class;
public virtual Collection<IFilter> GetFilters();
private void Initialize();
internal void Initialize(HttpConfiguration configuration);
private static void InvokeAttributesOnControllerType(HttpControllerDescriptor controllerDescriptor, Type type); // Properties
public HttpConfiguration Configuration { get; set; }
public string ControllerName { get; set; }
public Type ControllerType { get; set; }
public virtual ConcurrentDictionary<object, object> Properties { get; }
}

此类最重要的当属上述红色标记的CreateController方法了,我们查看其定义:

public virtual IHttpController CreateController(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
return this.Configuration.Services.GetHttpControllerActivator().Create(request, this, this.ControllerType);
}

接下来调用GetHttpControllerActivator方法:

public static IHttpControllerActivator GetHttpControllerActivator(this ServicesContainer services)
{
return services.GetServiceOrThrow<IHttpControllerActivator>();
}

而注册实现IHttpControllerActivator接口,则依然是通过DefaultServices来实现,如下:

    this.SetSingle<IHttpControllerActivator>(new DefaultHttpControllerActivator());

DefaultHttpControllerActivator负责激活控制器,那么是怎么样激活控制器的呢?接下来看看DefaultHttpControllerActivator类的定义:

 public class DefaultHttpControllerActivator : IHttpControllerActivator
{
// Fields
private object _cacheKey;
private Tuple<HttpControllerDescriptor, Func<IHttpController>> _fastCache; // Methods
public DefaultHttpControllerActivator();
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType);
private static IHttpController GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, out Func<IHttpController> activator);
}

最重要的是上述GetInstanceOrActivator方法来生成并激活IHttpController,查看此方法的实现如下:

private static IHttpController GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, out Func<IHttpController> activator)
{
IHttpController service = (IHttpController) request.GetDependencyScope().GetService(controllerType);
if (service != null)
{
activator = null;
return service;
}
activator = TypeActivator.Create<IHttpController>(controllerType);
return null;
}

由上知,此时DefaultHttpControllerActivator会从HttpConfiguration中获取DependencyResolver属性对应的容器,若此时容器为空,并通过反射调用TypeActivator来生成并激活控制器。以上就是整个创建控制器的整个过程。接下来我们继续来看文章开头  SendAsyncInternal 方法的第三步,请继续往下看!

HttpControllerContext

第三步:HttpControllerContext controllerContext = new HttpControllerContext(......)  

由此句知,利用构造函数将返回值为IHttpController的Controller属性以及返回值为HttpControllerDescriptor的属性ControllerDescriptor进行赋值,而得到一个有关控制器的上下文,下面我们来看看此类的定义:

public class HttpControllerContext
{
// Fields
private HttpConfiguration _configuration;
private IHttpController _controller;
private HttpControllerDescriptor _controllerDescriptor;
private HttpRequestMessage _request;
private IHttpRouteData _routeData; // Methods
public HttpControllerContext();
public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request); // Properties
public HttpConfiguration Configuration { get; set; }
public IHttpController Controller { get; set; }
public HttpControllerDescriptor ControllerDescriptor { get; set; }
public HttpRequestMessage Request { get; set; }
public IHttpRouteData RouteData { get; set; }
}

所谓的控制器上下文就是一个HttpControllerContext对象对应一个控制器的HttpContext,我们可以通过上述Controller属性类设置这个HttpControllerContext对象,不仅如此,我们还可以通过上述ControllerDescriptor属性来设置控制器的HttpControllerDescriptor对象。

我们通过上述创建了控制器上下文HttpControllerContext对象,并最终执行SendAsyncInternal方法的最后一句

return controller.ExecuteAsync(controllerContext, cancellationToken);

通过调用控制器上的ExecuteAsync方法将当前获得的控制器上下文传递进去最终做出响应。(这就涉及到控制器的执行过程下一节讲控制器的执行过程)  

总结

(1)简单回顾下控制器的创建的过程

由隶属于HttpRoutingDispatcher类型的HttpControllerDispatcher中的一个返回值为IHttpControllerSelector的属性ControllerSelector,这个ControllerSelector就是DefaultHttpControllerSelector,并调用DefaultHttpControllerSelector中的SelectController()方法,然后由此方法返回的HttpControllerDescriptor的类型变量descriptor,最终调用此变量中的CreateController来创建并激活控制器。

(2)关于控制器创建的详细示意图如下:(来源:控制器创建示意图

 

Web APi之控制器创建过程及原理解析(八)的更多相关文章

  1. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  2. ASP.NET Web API 控制器创建过程(二)

    ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病 ...

  3. ASP.NET Web API 控制器创建过程(一)

    ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...

  4. 2.4使用属性在 ASP.NET Web API 2 路由创建一个 REST API

    Web API 2 支持一种新型的路由,称为属性路由.属性路由的一般概述,请参阅属性路由 Web API 2 中.在本教程中,您将使用属性路由创建一个 REST API 集合的书.API 将支持以下操 ...

  5. ABP示例程序-使用AngularJs,ASP.NET MVC,Web API和EntityFramework创建N层的单页面Web应用

    本片文章翻译自ABP在CodeProject上的一个简单示例程序,网站上的程序是用ABP之前的版本创建的,模板创建界面及工程文档有所改变,本文基于最新的模板创建.通过这个简单的示例可以对ABP有个更深 ...

  6. Web API 2 入门——创建ASP.NET Web API的帮助页面(谷歌翻译)

    在这篇文章中 创建API帮助页面 将帮助页面添加到现有项目 添加API文档 在敞篷下 下一步 作者:Mike Wasson 创建Web API时,创建帮助页面通常很有用,以便其他开发人员知道如何调用A ...

  7. SSH免密登陆配置过程和原理解析

    SSH免密登陆配置过程和原理解析 SSH免密登陆配置过很多次,但是对它的认识只限于配置,对它认证的过程和基本的原理并没有什么认识,最近又看了一下,这里对学习的结果进行记录. 提纲: 1.SSH免密登陆 ...

  8. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  9. Web APi之控制器选择Action方法过程(九)

    前言 前面我们叙述了关于控制器创建的详细过程,在前面完成了对控制器的激活之后,就是根据控制器信息来查找匹配的Action方法,这就是本节要讲的内容.当请求过来时首先经过宿主处理管道然后进入Web AP ...

随机推荐

  1. Windows安装mysql-5.7.17-winx64.zip方式

    1.去官网上下载.zip格式的文件. 2.解压到一个文件夹,这里我用D:\MySql表示 3.在D:\MySql\mysql-5.7.17-winx64下新建my.ini配置文件 黄色背景色的地方需要 ...

  2. mssql查询列名中包含特定字段的列

    CREATE TABLE itemdata_LANG ( itemno ) NOT NULL, itemname ), -- 产品名称 othername ), indications ), -- 适 ...

  3. android studio安卓项目出现Error: Default Activity Not Found错误无法编译的解决方案

    项目明明是没有问题的,有时候突然就出现Error: Default Activity Not Found错误,以前出现过我重新安装了android studio 都没有用,后来在网上(http://s ...

  4. 通读SDWebImage①--总体梳理、下载和缓存

    本文目录 下载操作SDWebImageDownloaderOptions和下载过程实现 下载管理SDWebImageDownloader 缓存SDImageCache SDWebImageManage ...

  5. Angularjs ng-if和ng-show的区别

    ng-if:判断条件,为true时向html中插入节点,否则从html中移除节点: ng-show: 简单的显示和隐藏(display) 坑点:ng-if会创建一个新的作用域(scope),如果内部元 ...

  6. js弹出框、对话框、提示框、弹窗总结

    一.JS的三种最常见的对话框 //====================== JS最常用三种弹出对话框 ======================== //弹出对话框并输出一段提示信息 funct ...

  7. 学习建模 - UML

    最轻量级的工具下载地址 http://staruml.io/download 下载解压依赖:libgcrypt11 https://pan.baidu.com/s/1i3wb6M5 学习地址 http ...

  8. laravel 生成验证码的方法

    在Laravel中有很多图片验证码的库可以使用,本篇介绍其中之一:gregwar/captcha,这个库比较简单,在Laravel中比较常用.下面我们就来介绍下使用细节: 首先, composer.j ...

  9. 用C语言编写生成小学四则运算程序

    使用软件——VS 2015 使用环境——C语言 早在上周我就开始使用C#语言做,由于最后一点问题而放弃,之后用C语言开始做,很顺利,但是也碰到了一些问题,但是通过了百度文库上的一些程序的借鉴和吴阿平同 ...

  10. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...