Web APi之控制器创建过程及原理解析(八)
前言
中秋歇了歇,途中也时不时去看看有关创建控制器的原理以及解析,时间拖得比较长,实在是有点心有余而力不足,但又想着既然诺下了要写完原理一系列,还需有始有终。废话少说,直入主题。
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之控制器创建过程及原理解析(八)的更多相关文章
- Web APi之过滤器创建过程原理解析【一】(十)
前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...
- ASP.NET Web API 控制器创建过程(二)
ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病 ...
- ASP.NET Web API 控制器创建过程(一)
ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...
- 2.4使用属性在 ASP.NET Web API 2 路由创建一个 REST API
Web API 2 支持一种新型的路由,称为属性路由.属性路由的一般概述,请参阅属性路由 Web API 2 中.在本教程中,您将使用属性路由创建一个 REST API 集合的书.API 将支持以下操 ...
- ABP示例程序-使用AngularJs,ASP.NET MVC,Web API和EntityFramework创建N层的单页面Web应用
本片文章翻译自ABP在CodeProject上的一个简单示例程序,网站上的程序是用ABP之前的版本创建的,模板创建界面及工程文档有所改变,本文基于最新的模板创建.通过这个简单的示例可以对ABP有个更深 ...
- Web API 2 入门——创建ASP.NET Web API的帮助页面(谷歌翻译)
在这篇文章中 创建API帮助页面 将帮助页面添加到现有项目 添加API文档 在敞篷下 下一步 作者:Mike Wasson 创建Web API时,创建帮助页面通常很有用,以便其他开发人员知道如何调用A ...
- SSH免密登陆配置过程和原理解析
SSH免密登陆配置过程和原理解析 SSH免密登陆配置过很多次,但是对它的认识只限于配置,对它认证的过程和基本的原理并没有什么认识,最近又看了一下,这里对学习的结果进行记录. 提纲: 1.SSH免密登陆 ...
- Web APi之过滤器执行过程原理解析【二】(十一)
前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...
- Web APi之控制器选择Action方法过程(九)
前言 前面我们叙述了关于控制器创建的详细过程,在前面完成了对控制器的激活之后,就是根据控制器信息来查找匹配的Action方法,这就是本节要讲的内容.当请求过来时首先经过宿主处理管道然后进入Web AP ...
随机推荐
- Java_类似java.lang.VerifyError: Expecting a stackmap frame at branch target 22 in method的解决方法
报异常的方法内使用了Java 7的新特性:自动资源释放,类似于try(){},即在try后面跟一括号,在括号里面对一些资源赋值,try里面的代码块执行完毕之后会自动释放try后面的括号中声明的资源. ...
- web常见错误提示总结
在写web程序的时候,经常会出现一些网页错误的数字提示,如果能够明白这些提示的含义,那对于调试程序是有极大帮助的.网上有很多这方面的总结,但为了适应自己的阅读习惯,以及日后的查找方便,就做了一些修改并 ...
- jquery基本
对于jquery属性的访问: //对于bool值的属性,元素标签中如果写了这个属性,attr能够获取到,如果没有写,就获取不到. 如:<input type="checkbox&quo ...
- js控制台输出console
介绍: js的console你可以在firefox的firedbug或者ie和google的f12调试模式下看到,这些主流浏览器的调试模式的控制可以输出一些信息,你的一些js代码测试可以直接在cons ...
- JSP页面以及JSP九大隐式对象
JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP这门技术的最大的特点在于,写jsp就像在写html,但它相比 ...
- $.load()的用法
jquery load 事件用法 jquery load 事件用法 如果绑定给window对象,则会在所有内容加载后触发,包括窗口,框架,对象和图像.如果绑定在元素上,则当元素的内容加载完毕后触发. ...
- CSS 背景属性
background: 简写属性,作用是将背景属性置在一个声明中 background-attachment: 背景图像是否固定或者随着页面的其余部队滚动 background-color: 设置元素 ...
- dom初识
1什么是dom document object model文档对象模型 是将整个页面文档封装成了一个对象,就是一个文档对象 整个页面就是一个文档,是由很多的节点组成的节点又包括三部分: 元素 属性 文 ...
- shell脚本删除指定mobileprovision
由于某种原因,xcode帮我按照了几千个开发和上线证书,需要删除这部分证书: #dir="/Users/Ethan/Library/MobileDevice/Provisioning Pro ...
- 作业三: 代码规范、代码复审、PSP
分) 对于是否需要有代码规范,请考虑下列论点并反驳/支持: 这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率, 浪费时间的东西. 我是个艺术家,手艺人,我有自己的规范和原则. 规范不能 ...