通过表达式树构造URL时忽略部分参数
2016-07-29 00:00
471 查看
您的使用ASP.NET MVC的时候,一定遇到过使用Post接受数据的Action方法。例如:
于是乎,客户端只要像Home/List这样的URL中Post数据,这个Controller便可以从请求的Body中获得keyword和page的值。为了实现这个功能,我们必须在客户端准备一个form,把它的Action——也就是Post的目标URL写为Home/List。但是这个URL改怎么生成呢?按照传统的做法,我们会使用表达式树来构造这个URL:
但是您会发现,上面这条语句最终生成的URL是:
这是因为ASP.NET Routing在处理配置规则中没有标明的Route Values时,会将它们作为Query String拼接在URL后面。这也是可以预料到的,因为作为Form的URL,我们又如何明确指定一个参数的值呢?无论指定什么值都是不合适的,我们必须将它们忽略掉——或者说,我们需要找一种可以表示“任意”参数的方式。
接下来的做法还是接着上次的结果继续改进。您会发现这种做法有明显的Moq框架的影子,因为我们要使用这样的方式来表示参数的忽略:
这需要我们准备一个简单的It.IsAny方法的“结构”:
这个方法不是用来直接调用的,它只是作为表达式树的一部分存在——这也再次说明,表达式树的构造,并不意味着一定执行。表达式树是一种表示方式,用来说明我们的“意图”,仅此而已。
在原先的代码中,我们是这样向一个RouteValueDictionary里填充数据的:
如今,call.Arguments[i]可能是一个It.Any<…>()表达式,它不能直接用于求值(Eval)。因此,我们要将代码修改为以下这样:
我们在求值之前,需要判断这个表达式是否是It.IsAny方法的调用。如果不是,才将其加入RouteValueDictionary中。就这样,修改结束了,总共也就10多行代码的改动而已。
为了检验我们的成果,最好的方法进行单元测试。首先,我们准备一个测试用的Controller类和Action方法:
然后检查在普通情况下,所有的Route Value都被正常捕获到:
以及,如果我们想要忽略到一个参数时,它就不会出现在RouteValueDictionary中:
就这样,我们通过表达式树生成URL的功能又前进了一小步。
public class HomeController : Controller { [AcceptVerbs(HttpVerbs.Post)] public ViewResult List(string keywords, int page) { ... } }
于是乎,客户端只要像Home/List这样的URL中Post数据,这个Controller便可以从请求的Body中获得keyword和page的值。为了实现这个功能,我们必须在客户端准备一个form,把它的Action——也就是Post的目标URL写为Home/List。但是这个URL改怎么生成呢?按照传统的做法,我们会使用表达式树来构造这个URL:
<%= Url.ActionEx<HomeController>(c => c.List("hello", 3)) %>
但是您会发现,上面这条语句最终生成的URL是:
Home/List?keywords=hello&page=3
这是因为ASP.NET Routing在处理配置规则中没有标明的Route Values时,会将它们作为Query String拼接在URL后面。这也是可以预料到的,因为作为Form的URL,我们又如何明确指定一个参数的值呢?无论指定什么值都是不合适的,我们必须将它们忽略掉——或者说,我们需要找一种可以表示“任意”参数的方式。
接下来的做法还是接着上次的结果继续改进。您会发现这种做法有明显的Moq框架的影子,因为我们要使用这样的方式来表示参数的忽略:
<%= Url.ActionEx<HomeController>(c => c.List(It.IsAny<string>(), It.IsAny<int>())) %>
这需要我们准备一个简单的It.IsAny方法的“结构”:
public static class It { public static T IsAny<T>() { string message = "Use for expression construction only, " + "please DO NOT execute directly"; throw new InvalidOperationException(message); } }
这个方法不是用来直接调用的,它只是作为表达式树的一部分存在——这也再次说明,表达式树的构造,并不意味着一定执行。表达式树是一种表示方式,用来说明我们的“意图”,仅此而已。
在原先的代码中,我们是这样向一个RouteValueDictionary里填充数据的:
private static void AddParameterValues(RouteValueDictionary rvd, MethodCallExpression call) { ParameterInfo[] parameters = call.Method.GetParameters(); for (int i = 0; i < parameters.Length; i++) { rvd.Add(parameters[i].Name, Eval(call.Arguments[i])); } }
如今,call.Arguments[i]可能是一个It.Any<…>()表达式,它不能直接用于求值(Eval)。因此,我们要将代码修改为以下这样:
private static void AddParameterValues(RouteValueDictionary rvd, MethodCallExpression call) { ParameterInfo[] parameters = call.Method.GetParameters(); for (int i = 0; i < parameters.Length; i++) { var arg = call.Arguments[i]; if (!IsParameterShouldBeIgnored(arg)) { rvd.Add(parameters[i].Name, Eval(arg)); } } } private static bool IsParameterShouldBeIgnored(Expression arg) { var call = arg as MethodCallExpression; if (call == null) return false; if (call.Method.DeclaringType != typeof(It)) return false; if (call.Method.Name != "IsAny") return false; return true; }
我们在求值之前,需要判断这个表达式是否是It.IsAny方法的调用。如果不是,才将其加入RouteValueDictionary中。就这样,修改结束了,总共也就10多行代码的改动而已。
为了检验我们的成果,最好的方法进行单元测试。首先,我们准备一个测试用的Controller类和Action方法:
private class TestController : Controller { public ActionResult Index(string s, int i) { return null; } }
然后检查在普通情况下,所有的Route Value都被正常捕获到:
[Fact] public void Get_Route_Values_With_Arguments() { var routeValues = RouteExpression.GetRouteValues<TestController>( c => c.Index("abc", 5)); Assert.Equal("Test", routeValues["controller"]); Assert.Equal("Index", routeValues["action"]); Assert.Equal("abc", routeValues["s"]); Assert.Equal(5, routeValues["i"]); }
以及,如果我们想要忽略到一个参数时,它就不会出现在RouteValueDictionary中:
[Fact] public void Get_Route_Values_With_Ignored_Arguements() { var routeValues = RouteExpression.GetRouteValues<TestController>( c => c.Index("abc", It.IsAny<int>())); Assert.Equal("Test", routeValues["controller"]); Assert.Equal("Index", routeValues["action"]); Assert.Equal("abc", routeValues["s"]); Assert.False(routeValues.ContainsKey("i"), "This arg should be ignored!"); }
就这样,我们通过表达式树生成URL的功能又前进了一小步。
相关文章推荐
- 似乎有关Atlas的书也渐渐多起来了
- 对Action方法的参数进行双向转化
- 创业公司招聘前台开发人员(Front-end Web Developer)
- 如果是能简单解决的问题,就不用想得太复杂了
- 深入Atlas系列:探究序列化与反序列化能力(下) - JavaScriptSerializer
- 讲座展示:TechEd Europe DEV 411 - AJAX Patterns with ASP.NET AJAX(1)
- 书籍推荐:国内第一本ASP.NET 3.5 MVC技术专著
- 深入JavaScript与.NET Framework中的日期时间(1):基本概念与概述
- WPF/E CTP Quick Start - 第九部分:动画(翻译)
- WPF/E CTP Quick Start - 第四部分:绘图与填充(翻译)
- MongoDB与Tokyo Tyrant性能比较(1):基础CRU操作
- 数组排序方法的性能比较(4):LINQ方式的Array排序
- 我犯了一个错误,您能指出吗?(结论)
- 浅谈代码的执行效率(4):汇编优化
- Silverlight与微软技术(下):微软技术与技术学习
- 从Atlas到Microsoft ASP.NET AJAX(2) - Class、JavaScript Extensions (Client BCL)
- 盼望着可以早点装Vista
- 综述:编程语言的发展趋势及未来方向
- 并发环境下的缓存容器性能优化(上):不可变的哈希表
- 谈吉日嘎拉的《白话反射技术》及其他(技术篇)