ControllerDescriptor类主要包含了对ASP.NET MVC中的Control的元数据的解析,在MVC的Model绑定以及数据处理过程中经常会遇到ControllerDescriptor类;
ControllerDescriptor类在ASP.NET MVC源码中是一个抽象类,它继承了IUniquelyIdentifiable接口(只包含了UniqueId属性的接口)和ICustomAttributeProvider接口(获取应用在相应参数上的特性);
ControllerDescriptor类主要有3个属性
说明 | ||
---|---|---|
ControllerName | 获取控制器的名称。 | |
ControllerType | 获取控制器的类型。 | |
UniqueId | 在派生类中实现时,通过使用延迟初始化来获取控制器描述符的唯一 ID。 |
ControllerName属性是通过 ControllerType.Name获取并去掉了Controller后缀;
UniqueId属性主要是确定唯一性,在ControllerDescriptor类中通过ControllerName,ControllerType,ControllerDescriptor本身的类型进行创建(通过源码可以发现主要是通过字符串的长度+字符串已[{0}]{1}的格式叠加起来,具体的实现可以参考源码中DescriptorUtil.CreateUniqueId方法);
在ControllerDescriptor类中含有重要的方法FindAction的抽象方法,这个方法的目的为使用指定的名称和控制器上下文来查找操作方法。
ReflectedControllerDescriptor
ReflectedControllerDescriptor类是MVC框架中继承ControllerDescriptor的实现类;ReflectedControllerDescriptor类的构造函数接收一个Controller的Type,并且在构造函数中会组件一个ActionMethodSelector只读类;
ReflectedControllerDescriptor类覆盖了抽象基类ControllerDescriptor的GetCanonicalActions,GetCustomAttributes,GetFilterAttributes,IsDefined,FindAction方法;
IsDefined方法的作用是返回一个是否为此成员定义某个自定义特性类型的一个或多个实例的布尔值;函数内部直接调用MemberInfo类的bool IsDefined(Type attributeType, bool inherit);可以通过IsDefined来进行判断是否有一个特性值作用于Controller;
GetCustomAttributes方法时获取所有的自定义特性的数组;当有特性作用于Controller时,可以通过GetCustomAttributes方法来获取这些特性;
IsDefined方法与GetCustomAttributes方法都来源于ICustomAttributeProvider接口;
GetFilterAttributes方法实现了FilterAttribute接口的所有特性;函数的内部还是调用GetCustomAttributes方法(GetCustomAttributes方法参数Type为typeof(FilterAttribute));
GetCanonicalActions方法目的是返回控制器内所有的Action的列表,返回值为ActionDescriptor[] ;
FindAction方法时ControllerDescriptor中最重要的方法,这个方法通过ControllerContext(控制器上下文)与actionName来筛选出相应的Action,返回ActionDescriptor;在这个函数内部首先通过_selector(类型为ActionMethodSelector)类的FindActionMethod方法获取到匹配的方法元数据信息(返回值为MethodInfo),然后调用ReflectedActionDescriptor类(基类:ActionDescriptor)的构造函数;
ActionMethodSelectorBase
ActionMethodSelector类继承了ActionMethodSelectorBase基类,这个类主要的功能就是Action的筛选;
ActionMethodSelectorBase类中含有ControllerType,(类型:MethodInfo[])ActionMethods( 所有的action方法),
(类型:HashSet<MethodInfo>)StandardRouteMethods(直接路由的方法),
(类型:MethodInfo[])AliasedMethods(在MVC框架中的action的名字可以通过特性ActionNameSelectorAttributes来进行重新命名,因此这个属性是存储含有别名的方法信息),
(类型:ILookup<string, MethodInfo>)NonAliasedMethods(存储没有别名信息的action的方法信息,key action的名称);
在ActionMethodSelectorBase类中还对于所有的action方法做了缓存,存储在StandardRouteCache属性(私有)中,StandardRouteCache属性的类型为StandardRouteActionMethodCache类,对于StandardRouteActionMethodCache类很简单,有2个属性,一个是存储没有别名信息方法信息的ILookup<string, MethodInfo> NonAliasedMethods和含有别名信息的MethodInfo[] AliasedMethods;在CreateStandardRouteCache方法中创建缓存的逻辑也很简单,首先在StandardRouteMethods列表中筛选出方法中含有ActionNameSelectorAttribute的方法,然后在与StandardRouteMethods做差集获得没有别名信息的方法集合;
在ReflectedControllerDescriptor类的构造函数中会创建ActionMethodSelector类,ActionMethodSelector在构造函数中调用基类的Initialize来完成初始化,在初始化过程中首先会获取ControllerType类中的所有的公共方法,然后通过ActionMethodSelector类中的IsValidActionMethod方法来筛选出所有的action方法;并写入到StandardRouteMethods属性中;对于action的筛选方法可以通过代码看到
protected override bool IsValidActionMethod(MethodInfo methodInfo) { return !(methodInfo.IsSpecialName ||methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller))); }
在进行FindAction方法时,直接调用了基类的FindActionMethod方法,在这个方法中首先会从缓存中的别名方法中帅选,由于别名方法列表的是数组,逐个遍历每一项,获取每一项中的继承了ActionNameSelectorAttribute类的子类列表,由于ActionNameSelectorAttribute是一个抽象类,含有 bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)的抽象方法;在进行别名匹配的时候调用IsValidName来进行校验;
protected static bool IsMatchingAliasedMethod(MethodInfo method, ControllerContext controllerContext, string actionName) { // return if aliased method is opting in to this request // to opt in, all attributes defined on the method must return true ReadOnlyCollection<ActionNameSelectorAttribute> attributes = ReflectedAttributeCache.GetActionNameSelectorAttributes(method); // Caching count is faster for ReadOnlyCollection int attributeCount = attributes.Count; // Performance sensitive, so avoid foreach for (int i = 0; i < attributeCount; i++) { if (!attributes[i].IsValidName(controllerContext, actionName, method)) { return false; } } return true; }
当遍历完所有的别名列表时,在进行没有别名方法列表的筛选,由于没有别名方法的列表在缓存中的存储格式为ILookup<string, MethodInfo>格式,因此直接通过key(action的名字)来进行获取;
当缓存中的所有数据都查找完成后会得到一个方法元数据的集合,然后在调用RunSelectionFilters方法进行最后的过滤,当过滤完成后如果列表中没有元素时,直接返回为空,当为一个元素时,返回当前元素,当为多个元素时,就会throw 一个AmbiguousMatchException错误;
RunSelectionFilters方法主要对结果进行筛选,
第一个是根据ActionMethodSelectorAttribute特性进行过滤,首先获取方法的ActionMethodSelectorAttribute的特性列表,如果方法没有这个MethodSelectionAttribute,如果已经方法列表中已经存在了标记有ActionMethodSelectorAttribute特性的方法时就要移除掉这个方法;
ActionMethodSelectorAttribute中含有一个抽象方法bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);
第二个是调用IsValidMethodSelector,这个方法也是调用ActionMethodSelectorAttribute的IsValidForRequest方法;
protected static void RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos) { // Filter depending on the selection attribute. // Methods with valid selection attributes override all others. // Methods with one or more invalid selection attributes are removed. bool hasValidSelectionAttributes = false; // loop backwards for fastest removal for (int i = methodInfos.Count - 1; i >= 0; i--) { MethodInfo methodInfo = methodInfos[i]; ReadOnlyCollection<ActionMethodSelectorAttribute> attrs = ReflectedAttributeCache.GetActionMethodSelectorAttributesCollection(methodInfo); if (attrs.Count == 0) { // case 1: this method does not have a MethodSelectionAttribute if (hasValidSelectionAttributes) { // if there is already method with a valid selection attribute, remove method without one methodInfos.RemoveAt(i); } } else if (IsValidMethodSelector(attrs, controllerContext, methodInfo)) { // case 2: this method has MethodSelectionAttributes that are all valid // if a matching action method had a selection attribute, consider it more specific than a matching action method // without a selection attribute if (!hasValidSelectionAttributes) { // when the first selection attribute is discovered, remove any items later in the list without selection attributes if (i + 1 < methodInfos.Count) { methodInfos.RemoveFrom(i + 1); } hasValidSelectionAttributes = true; } } else { // case 3: this method has a method selection attribute but it is not valid // remove the method since it is opting out of this request methodInfos.RemoveAt(i); } } } protected static bool IsValidMethodSelector(ReadOnlyCollection<ActionMethodSelectorAttribute> attributes, ControllerContext controllerContext, MethodInfo method) { int attributeCount = attributes.Count; Contract.Assert(attributeCount > 0); for (int i = 0; i < attributeCount; i++) { if (!attributes[i].IsValidForRequest(controllerContext, method)) { return false; } } return true; }