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

  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的源码如下:

  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 :
  11. return null;
  12.  
  13. case :
  14. return finalMethods[];
  15.  
  16. default://如果找到的方法个数大于1,则抛出异常:方法之间存在不明确调用
  17. throw CreateAmbiguousMatchException(finalMethods, actionName);
  18. }
  19. }

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

  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()方法

  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,这个方法的代码如下:

  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 == ) {
  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处理,于是就写了下面这段代码:

  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下对应的代码如下:

  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代码如下:

  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,也就会抛出异常了。

MVC系统学习7—Action的选择过程的更多相关文章

  1. MVC系统学习3—ModelBinder

    在ASP.NET MVC中,每个请求都被映射到一个Action方法,我们可以在action的方法中定义相应类型的参数,View中通过post.get方式提交的request参数,只要名称一致就会对应到 ...

  2. MVC系统学习1—MVC执行流程

    用MVC来做开发也有一段时间了,但是感觉一直没入门,就徘徊在似懂非懂的层次,和去年刚毕业学习WebForm时一样,当时通过张子阳老兄的几篇文章,明白了请求处理流程,页面生命周期才真正明白了WebFor ...

  3. MVC系统学习6—Filter

    Mvc的过滤器是特性类,可以使我们在执行Action之前,执行Action之后,执行Action发生异常时,编写相关的处理代码实现某些逻辑.下面是四个基本的Filter接口. 上面这四个基本的Filt ...

  4. Mvc系统学习9——Areas学习

    在Mvc2.0中,新增加了一个特性就是Areas.在没有有使用Areas的情况下,我们的Mvc项目组织是下面这样的.当项目庞大的时候,Controllers,Model,View文件下下面势必会有很多 ...

  5. MVC系统学习2—MVC路由

    在MVC下不是通过对物理文件的映射来实行访问的,而是通过定义后的路由Url来实现访问的.在前一篇讲到我们是在全局文件下进行路由配置. routes.MapRoute(                & ...

  6. MVC系统学习5——验证

    其实关于Mvc的验证在上一篇已经有讲过一些了,可以通过在我们定义的Model上面添加相应的System.ComponentModel.DataAnnotations空间下的验证属性.在服务器端通过Mo ...

  7. MVC系统学习4—ModelMetaData

    在Mvc R2中,新引入了一些扩展方法,如后面带一个for的方法,这些扩展方法会根据Model的属性自定生成相应的Html元素,如Html.EditFor(Model=>Model.IsAppr ...

  8. MVC系统学习8——AsyncController

    关于为什么使用异步Controller,这里不做备忘,三岁小孩都懂.主要的备忘是如何使用AsyncController. //这个action以Async结尾,并且返回值是void public vo ...

  9. WebApi官网学习记录---webapi中controller与action的选择

    如果framework找到一个匹配的URI,创建一个包含占位符值的字典,key就是这些占位符(不包括大括号),value来自URI或者默认值,这个字典存储在IHttpRouteData对象中.默认值可 ...

随机推荐

  1. 数论(GCD) HDOJ 4320 Arcane Numbers 1

    题目传送门 题意:有一个A进制的有限小数,问能否转换成B进制的有限小数 分析:0.123在A进制下表示成:1/A + 2/(A^2) + 3 / (A^3),转换成B进制就是不断的乘B直到为0,即(1 ...

  2. 题解报告:hdu 1160 FatMouse's Speed(LIS+记录路径)

    Problem Description FatMouse believes that the fatter a mouse is, the faster it runs. To disprove th ...

  3. 题解报告:hdu 1114 Piggy-Bank(完全背包恰好装满)

    Problem Description Before ACM can do anything, a budget must be prepared and the necessary financia ...

  4. RabbitMQ五:生产者--队列--多消费者

    一.生成者-队列-多消费者(前言) 上篇文章,我们做了一个简单的Demo,一个生产者对应一个消费者,本篇文章就介绍 生产者-队列-多个消费者,下面简单示意图 P 生产者    C 消费者  中间队列 ...

  5. WinForm 对话框,流

    private void button1_Click(object sender, EventArgs e) { //显示颜色选择器 colorDialog1.ShowDialog(); //把取到的 ...

  6. 用pycharm+django开发web项目

    pycharm是python的一个商业的集成开发工具,本人感觉做python开发还是很好用的,django是一个很流行的python web开源框架,本文就是使用pycharm+django来开发py ...

  7. iOS Programming Camera 2

    iOS Programming Camera  2  1.1 Creating BNRImageStore The image store will fetch and cache the image ...

  8. nutz配置druid监控

    druid 提供了一个web端的监控页面, 搭建起来不算麻烦, 建议添加. 打开web.xml, 在nutz的filter之前, 加入Web监控的配置 <filter> <filte ...

  9. Oracle反向字符截取逗號分隔字符串

    DECLARE M ); BEGIN FOR I IN ( WITH T AS (SELECT REVERSE('i,am,a,test,hahahhah') AS STR FROM DUAL) SE ...

  10. caffe实现自己的层

    http://blog.csdn.net/xizero00/article/details/52529341 将这篇博客所讲进行了实现 1.LayerParameter也在caffe.proto文件中 ...