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


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

    public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) {
                if (controllerContext == null) {
                    throw new ArgumentNullException("controllerContext");
                }
                if (String.IsNullOrEmpty(actionName)) {
                    throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
                }
                //TODO:获取相应的描述action
                 MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
                if (matched == null) {
                    return null;
                }
    
                return new ReflectedActionDescriptor(matched, actionName, this);
            }
    View Code

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

    public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {
                //获取别名匹配的方法
                 List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
                //将没有别名的方法和别名匹配的方法添加到一起
                 methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
                //实现方法的筛选
                 List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
    
                switch (finalMethods.Count) {
                    case 0:
                        return null;
    
                    case 1:
                        return finalMethods[0];
    
                    default://如果找到的方法个数大于1,则抛出异常:方法之间存在不明确调用
                        throw CreateAmbiguousMatchException(finalMethods, actionName);
                }
            }
    View Code

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

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

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

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

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

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

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

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

    [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
        public class MulSubmitActionAttribute : ActionNameSelectorAttribute
        {
            private string _submitBtnName;
    
            public MulSubmitActionAttribute(string submitBtnName)
            {
                if (String.IsNullOrEmpty(submitBtnName))
                {
                    throw new ArgumentException("参数不能为空", "submitBtnName");
                }
                this._submitBtnName = submitBtnName;
               
            }
    
            public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
            {
                if (this._submitBtnName=="changehandler")//当是changehandler这个按钮提交时就返回真
                {
                    return controllerContext.HttpContext.Request[this._submitBtnName] != null;     
                }
                //其他按钮提交也返回真,错误就在这里,这就造成了这个函数的返回永远是真              //这里返回false才是正确的,而且TestAction上面不用加MulSubmitActionAttribute
                return true;
            }
        }
    View Code

          Controller下对应的代码如下:

    [HttpGet]
            public ActionResult Index()
            {
                ViewData["Message"] = "欢迎使用 ASP.NET MVC!";
                return View();
            }
     
            [MulSubmitActionAttribute("other")]
            public ActionResult TestAction(Person person)
            {
                return View();
            }
    
            [MulSubmitActionAttribute("changehandler")]
            public ActionResult ChangeHandler(Person person)
            {
                return View("ChangeHandler"); 
            }
    View Code

     View代码如下:

    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>
    View Code

         当运行代码的时,点击提交按钮时不会出错,点击更改经纪人按钮,想实现的功能是交给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,也就会抛出异常了。

         

    明确个目标,一直走下去
  • 相关阅读:
    系统结构实践——第一次作业
    个人作业——软件工程实践总结作业
    个人作业——软件评测
    软件工程第五次作业--结队编程
    软件工程第四次作业--结队作业
    第一次个人编程作业
    第一次软工作业
    java第五周上机练习
    Java作业5
    java4
  • 原文地址:https://www.cnblogs.com/fhlj/p/3615235.html
Copyright © 2020-2023  润新知