在MvcFutures项目中提供了一个辅助方法,可以将一个表达式树对象转化成一个RouteValueDictionary集合。只可惜,这个辅助方法的毛病比较多。例如,它直接把方法名作为action的值,而忽略了其上标记的ActionNameAttribute。这导致了某个被“改名”的Action方法一旦用在了表达式树中,最终得到的URL便是错误的。例如有一个Action方法:

public class HomeController : Controller
{[ActionNamepublic ViewResult Index(){...}
}

如果您使用这样的方式来生成URL(ActionEx方法的实现请参考《使用表达式树构建DomainRoute的URL》):

<a href="<%= Url.ActionEx<HomeController>(c => c.Index()) %>">Home</a>

则最终得到的代码是:

<a href="/Home/Index">Home</a>

而我们需要的结果应该是:

<a href="/Home/Default">Home</a>

正是因为这个原因(以及一些其他因素),许多朋友放弃使用强类型的方式构造URL。不过,如果您继续看下去,就会发现这个功能其实非常简单。只要做稍微一点点修改就可以了。不过现在,让我们来观察MvcFutures是如何实现这部分功能的。我已经把相关的代码复制到自己的RouteExpression类中:

public static class RouteExpression
{public static RouteValueDictionary GetRouteValues<TController>(Expression<Action<TController>> action)where TController : Controller{if (action == null){throw new ArgumentNullException("action");}MethodCallExpression call = action.Body as MethodCallExpression;if (call == null){throw new ArgumentException("The action must be a method call.", "action");}string controllerName = typeof(TController).Name;if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)){throw new ArgumentException("The controller name must end with 'Controller'.", "action");}controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);if (controllerName.Length == 0){throw new ArgumentException("Cannot route to the Controller class", "action");}var rvd = new RouteValueDictionary();rvd.Add("controller", controllerName);rvd.Add("action", call.Method.Name);AddParameterValuesFromExpressionToDictionary(rvd, call);return rvd;}private static void AddParameterValues(RouteValueDictionary rvd, MethodCallExpression call){...}
}

这段代码大部分内容都是进行参数校验,一旦出现以下情况之一,便会抛出异常:

  • 表达式树为null。
  • 表达式树不是一个MethodCallExpression(应该是一个Action方法的调用)
  • 如果控制器类型的名称不以Controller结尾(破坏了约定)
  • 如果控制器类型的名称就是Controller

经过校验之后,这个方法根据控制器类型的名称计算出controller(HomeController => Home),再把所调方法的名称作为action(Index() => Index)。最后,再使用AddParameterValues方法获得参数,并填充RouteValueDictionary(关于这点我们下次再来讨论)。

不过,问题就出现在从Action方法的MethodInfo“直接获取”名称这个步骤上。这个MethodInfo可能还标记着ActionNameAttribute呢,它的Name属性可不是action的名称。为此,我们必须多做这么一步:

private static ReaderWriterLockSlim s_rwLock = new ReaderWriterLockSlim();
private static Dictionary<MethodInfo, string> s_actionNames = new Dictionary<MethodInfo, string>();private static string GetActionName(MethodInfo methodInfo)
{string actionName = null;s_rwLock.EnterReadLock();try{if (s_actionNames.TryGetValue(methodInfo, out actionName)){return actionName;}}finally{s_rwLock.ExitReadLock();}var attribute = (ActionNameAttribute)methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), false).SingleOrDefault();actionName = attribute == null ? methodInfo.Name : attribute.Name;s_rwLock.EnterWriteLock();try{s_actionNames[methodInfo] = actionName;}finally{s_rwLock.ExitWriteLock();}return actionName;
}

在GetActionName方法的中部则是获得action名称的代码。它会根据methodInfo上的ActionNameAttribute标记情况来确定。如果标记了ActionNameAttribute,则使用Attribute的Name属性作为action名称,否则就使用MethodInfo对象的Name属性。获得action名称之后,我们会将其保存在一个字典中。至于使用ReaderWriterLockSlim来控制并发读写的方式已经成为了标准,您甚至可以将其封装为一个组件避免重复编写相同的代码。

最后,我们把原来GetRouteValues方法中的一行代码加以替换即可:

public static RouteValueDictionary GetRouteValues<TController>(Expression<Action<TController>> action)where TController : Controller
{...rvd.Add("action", call.Method.Name);rvd.Add("action", GetActionName(call.Method));...
}

ASP.NET MVC给了我们充分的自由度定制需要的组件。从中我们也可以了解到如何在项目中编写合适的API。其实很多东西只要多走一步就会美好很多,例如这个例子,需要花费您超过半小时的时间吗?

通过表达式树构建URL时正确识别ActionNameAttribute相关推荐

  1. C++ 学习笔记之(19) new、delete表达式、RTTI(运行时类型识别)、枚举、类成员指针、嵌套类、局部类、位域、volatile、extern C

    C++ 学习笔记之(19) new.delete表达式.RTTI(运行时类型识别).枚举.类成员指针.嵌套类.局部类.位域.volatile.extern C C++ 学习笔记之(19) new.de ...

  2. ASP.NET Core中使用表达式树创建URL

    当我们在ASP.NET Core中生成一个action的url会这样写: var url=_urlHelper.Action("Index", "Home"); ...

  3. 表达式树练习实践:入门基础

    什么是表达式树 来自微软官方文档的定义: 表达式树以树形数据结构表示代码. 它能干什么呢? 你可以对表达式树中的代码进行编辑和运算.这样能够动态修改可执行代码.在不同数据库中执行 LINQ 查询以及创 ...

  4. 关于Expression表达式树的拼接

    关于Expression表达式树的拼接 最近在做项目中遇到一个问题,需求是这样的: 我要对已经存在的用户进行检索,可以根据用户的id 或者用户名其中的一部分字符来检索出来,这样就出现了三种情况 只有i ...

  5. 表达式树 php,Linux_LINQ学习笔记:表达式树,构建查询表达式 本节中, 我们 - phpStudy...

    构建查询表达式 本节中, 我们假设我们拥有一个这样的实体类: 1: [Table] public partial class Product 2: 3: { 4: 5: [Column(IsPrima ...

  6. 波兰表达式 构建 表达式树

    这里提供一种将波兰表达式构建成表达式树的一种方法. 二叉树的节点有三个成员:数值(或者操作符)type,左节点lnode(被操作数),右节点rnode(操作数) //借助栈将波兰表达式 构建 表达式树 ...

  7. 解决AD22拼版导出Gerber时DFM无法正确识别板框

    网上大多方法到处Gerber时单板是可以正确识别板框的 但是 拼版时dfm软件会识别多个板框导致板厂没法做板子 于是为了少花点钱 想了个小法子来让CAM软件正确识别我的板框 就像这样子 要么是有多个o ...

  8. 为给定的Lambda表达式构建表达式树

    这是用于将给定的LINQ表达式转换为对应的表达式树的代码示例. //using LINQAlias = System.Linq.Expressions; List<Host> dinner ...

  9. C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

    Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...

  10. ai端到端_如何使用行为树构建端到端的对话式AI系统

    ai端到端 by Lior Messinger 由Lior Messinger 如何使用行为树构建端到端的对话式AI系统 (How to Build an End-to-End Conversatio ...

最新文章

  1. raid0、raid1、raid5、raid10 flash
  2. LINGO 12安装教程
  3. leader选举的源码分析-FastLeaderElection.starter
  4. 数学--数论--莫比乌斯函数
  5. 华为2018软件岗笔试题解题思路和源代码分享
  6. C++ 类访问控制public/private/protected探讨
  7. vba 根据分辨率 调整窗口显示比例_2020 如何选择适合自己的显示器?小白选购电脑显示器必看,附各类型显示器高性价比选购指南分析...
  8. HTML_CSS_JS_JSON
  9. Spring-@Value
  10. Recoil 是 React 的状态管理库
  11. frontend-tools
  12. Visio 2016专业版 激活方式
  13. 自然语言处理-错字识别(基于Python)kenlm、pycorrector
  14. MPC控制笔记(一)
  15. Java8新特性详解
  16. 游戏引擎架构 (Jason Gregory 著)
  17. 所谓语音合成 是计算机根据语言学,计算机语音合成在GAI中的应用
  18. 谷歌人工智能设计的芯片揭示什么是智能的本质
  19. service中间调用dao层方法时,出现Mapper method ‘dao.xxx‘ has an unsupported return type错误,但是sql执行修改数据成功,并没有回滚
  20. 华为畅享9额android9,华为畅享9

热门文章

  1. c语言 com组件,com组件 C语言基础.ppt
  2. python用时间戳给文件命名规则_关于时间戳:python复制文件但保持原始
  3. nginx 超时设置_Nginx最详细的反向代理配置步骤,拿去不谢
  4. SpringBoot在前端发送url时,不能识别特殊字符的问题
  5. mysql锁问题吗_Mysql锁的问题和解析
  6. USACO翻译:USACO 2013 DEC Silver三题
  7. HDU - 6297 CCPC直播
  8. SpringMVC拦截器(包括自定以拦截器--实现HandlerInterceptorAdapter)(资源和权限管理)...
  9. Git学习系列之Git基本操作推送项目(图文详解)
  10. 使用ELK在DC / OS中进行日志管理