Mvc系统学习7—Action的选择过程

      在Mvc源码的ControllerActionInvoker的InvokeAction方法里面有一个FindAction方法,FindAction方法在ControllerDescriptor里面定义为虚方法,而ReflectedControllerDescriptor是继承自ControllerDescriptor。其FindAction方法如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) {
2 if (controllerContext == null ) {
3 throw new ArgumentNullException( " controllerContext " );
4 }
5 if (String.IsNullOrEmpty(actionName)) {
6 throw new ArgumentException(MvcResources.Common_NullOrEmpty, " actionName " );
7 }
8 // TODO:获取相应的描述action
9   MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
10 if (matched == null ) {
11 return null ;
12 }
13
14 return new ReflectedActionDescriptor(matched, actionName, this );
15 }

     查找Action的方法集中在 _selector.FindActionMethod(controllerContext, actionName)里面,_selector是一个ActionMethodSelector类型。FindActionMethod的源码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {
2 // 获取别名匹配的方法
3   List < MethodInfo > methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
4 // 将没有别名的方法和别名匹配的方法添加到一起
5   methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
6 // 实现方法的筛选
7   List < MethodInfo > finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
8
9 switch (finalMethods.Count) {
10 case 0 :
11 return null ;
12
13 case 1 :
14 return finalMethods[ 0 ];
15
16 default : // 如果找到的方法个数大于1,则抛出异常:方法之间存在不明确调用
17 throw CreateAmbiguousMatchException(finalMethods, actionName);
18 }
19 }

      GetMatchingAliasedMethods主要是用来获取别名匹配的方法,所谓的别名方法也就是方法的特性有继承自ActionNameSelectorAttribute类,其代码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 internal List < MethodInfo > GetMatchingAliasedMethods(ControllerContext controllerContext, string actionName) {
2 // find all aliased methods which are opting in to this request
3 // to opt in, all attributes defined on the method must return true
4 // 注意下面的AliasedMethods
5 var methods = from methodInfo in AliasedMethods
6 let attrs = (ActionNameSelectorAttribute[])methodInfo.GetCustomAttributes( typeof (ActionNameSelectorAttribute), true /* inherit */ )
7 where attrs.All(attr => attr.IsValidName(controllerContext, actionName, methodInfo))
8 select methodInfo;
9
10 return methods.ToList();
11 }

      注意上面方法中linq表达式的AliasedMethods,他是ActionMethodSelector的一个属性类型是MethInfo数组,对应的还有另外一个属性NonAliasedMethods,它们的命名是自解释的。对于这两个MethInfo数组的初始化是在ActionMethodSelector的构造函数中的PopulateLookupTables()方法

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 private void PopulateLookupTables() {
2 // 获取Controller下面的所有Action
3 MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
4 // 获取有效的方法IsValidActionMethod是一个Predicate委托,为什么还要在这里过滤一次还不是很明白
5 MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
6 // 获取定义有别名的函数,也就是方法的特性有ActionNameSelectorAttribute或者ActionNameSelectorAttribute的子类
7 // IsMethodDecoratedWithAliasingAttribute也是一个Predicate委托,代码:
8 // return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
9 AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
10 // 获取没有别名的函数,
11 NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);
12 }

      从上面的代码可以知道,当请求某个控制器下的action时,会获取所有的action作为筛选的对象。回到上面的GetMatchingAliasedMethods,当ActionNameSelectorAttribute的IsValidName方法为真时就会返回一个Action。而FindActionMethod的最后调用的是RunSelectionFilters,这个方法的代码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 private static List < MethodInfo > RunSelectionFilters(ControllerContext controllerContext, List < MethodInfo > methodInfos) {
2 // remove all methods which are opting out of this request
3 // to opt out, at least one attribute defined on the method must return false
4
5 List < MethodInfo > matchesWithSelectionAttributes = new List < MethodInfo > ();
6 List < MethodInfo > matchesWithoutSelectionAttributes = new List < MethodInfo > ();
7
8 foreach (MethodInfo methodInfo in methodInfos) {
9 ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])methodInfo.GetCustomAttributes( typeof (ActionMethodSelectorAttribute), true /* inherit */ );
10 if (attrs.Length == 0 ) {
11 matchesWithoutSelectionAttributes.Add(methodInfo);
12 }
13 // attr.IsValidForRequest判断是否有添加HttpPost或者HttpGet特性
14 else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo))) {
15 matchesWithSelectionAttributes.Add(methodInfo);
16 }
17 }

      RunSelectionFilters就是实现将具有别名action和不具有别名的action实现最后的筛选。

      一次错误的实践:今天要实现一个功能,就是当页面有多个submit按钮的时候,将其中一个submit按钮的提交转到一个特殊的action,而其他的submit提交,交由一个action处理,于是就写了下面这段代码:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 [AttributeUsage(AttributeTargets.Method, Inherited = true , AllowMultiple = false )]
2 public class MulSubmitActionAttribute : ActionNameSelectorAttribute
3 {
4 private string _submitBtnName;
5
6 public MulSubmitActionAttribute( string submitBtnName)
7 {
8 if (String.IsNullOrEmpty(submitBtnName))
9 {
10 throw new ArgumentException( " 参数不能为空 " , " submitBtnName " );
11 }
12 this ._submitBtnName = submitBtnName;
13
14 }
15
16 public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
17 {
18 if ( this ._submitBtnName == " changehandler " ) // 当是changehandler这个按钮提交时就返回真
19 {
20 return controllerContext.HttpContext.Request[ this ._submitBtnName] != null ;
21 }
22 // 其他按钮提交也返回真,错误就在这里,这就造成了这个函数的返回永远是真
//这里返回false才是正确的,而且TestAction上面不用加MulSubmitActionAttribute
23 return true ;
24 }
25 }

      Controller下对应的代码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 [HttpGet]
2 public ActionResult Index()
3 {
4 ViewData["Message"] = "欢迎使用 ASP.NET MVC!";
5 return View();
6 }
7
8 [MulSubmitActionAttribute("other")]
9 public ActionResult TestAction(Person person)
10 {
11 return View();
12 }
13
14 [MulSubmitActionAttribute("changehandler")]
15 public ActionResult ChangeHandler(Person person)
16 {
17 return View("ChangeHandler");
18 }

 View代码如下:

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
1 < form action ="Index" method ="post" >
2 <% = Html.EditorForModel() %>
3 < input type ="submit" value ="提交" name ="tijiao" />
4 < input type ="submit" value ="更改经纪人" name ="changehandler" />
5 </ form >

     当运行代码的时,点击提交按钮时不会出错,点击更改经纪人按钮,想实现的功能是交给ChangHandler这个action进行处理,但是却出错,抛出System.Reflection.AmbiguousMatchException,提示在Action:TestAction和ChangHandler之间调用不明确。错误的原因是这样的,当点击更改经纪人按钮时,在GetMatchingAliasedMethods方法中,会调用两次MulSubmitActionAttribute的IsValid方法,因为AliasedMethods有两项,分别对应着TestAction和ChangeHandler,这两个action都附加着MulSubmitActionAttribute,构造函数传入的值分别是other和changehandler因此,传入changehandler时,由于这时是点击changehandler按钮提交,所以这时controllerContext.HttpContext.Request[this._submitBtnName] != null为true,传入other时,直接返回true,因此就会找到两个action,也就会抛出异常了。

     

转载于:https://www.cnblogs.com/wuxiaoqian726/articles/2031719.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值