前言

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

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. vmware下的centos上网配置

    设置网络 1.查看网络配制 刚刚安装好的虚拟机,里面还是原始的网络配制.只有一个回环网卡. 通过ifconifg命令可以查看到.   2.添加网卡 从上面看到只有回环网卡,现在需要添加一块网卡来进行后 ...

  2. Angular初步

    一.angular.js是什么? angular.js是一个javascript的框架,与jquery是一个级别的,区别是jquery主要是擅长dom操作,而angular主要是擅长绑定数据显示数据. ...

  3. USER STORIES AND USE CASES - DON’T USE BOTH

    We’re in Orlando for a working session as part of the Core Team building BABOK V3 and over dinner th ...

  4. firefox浏览器无法显示bootstrap图标问题总结

    在学习bootstrap的时候,有一个问题始终非常疑惑和困扰,就是firefox无法显示bootstrap自带的那套名为“glyphicon”的图标,在图标的引用处显示的是一个小方块,如图所示(4前面 ...

  5. Python 爬虫5——爬取并下载网页指定规格的图片

    看完上篇文档之后,我们对于正则表达式已经有了基本的了解,其实学习最有效的办法就是带着问题和目的,这里我们假设有一个目标:获取某个网页上指定规格的图片的链接地址,并下载到本地. 一.实现步骤: 1.在浏 ...

  6. Struts 2的数据校验

    既然说到了Struts 2的数据校验,我们该怎么去实现呢?又是通过什么来实现呢? 就让我带着大家一起来走进Struts 2的数据校验吧. 首先我们会想到在Stuts 2的登录案例中我们定义了一个Act ...

  7. SQL语句经典大全

    一.基础 1.说明:创建数据库 CREATE DATABASE database-name  2.说明:删除数据库 drop database dbname 3.说明:备份sql server --- ...

  8. daima

    # -*- coding: utf-8 -*- import theano import theano.tensor as T import numpy as np from sklearn impo ...

  9. 数组和链表--Java学习笔记(一)

    版权声明: 本文由Faye_Zuo发布于http://www.cnblogs.com/zuofeiyi/, 本文可以被全部的转载或者部分使用,但请注明出处. 我是一个全职妈妈,两年前在上海一家人力资源 ...

  10. iOS 开发快速导引:iOS 程序框架【草】

    概要 待补充 App 生命周期 待补充 View Controller 生命周期 待补充 链接 Learn X in Y minutes —— swift 中文版 Learn X in Y minut ...