Expression Tree 扩展MVC中的 HtmlHelper 和 UrlHelper

        表达式树是LINQ To everything 的基础,同时各类类库的Fluent API也 大量使用了Expression Tree。还记得我在不懂expression tree时,各类眼花缭乱的API 看的我各类膜拜,当我熟悉expression tree 后恍然大悟,不用看代码也能知作别人的API 是如何设计的(^_^)。 接下来这篇博客就谈谈如何使用expression tree扩展MVC中的HtmlHelper和UrlHelper。html

场景express

        当咱们在MVC中生成一个action的url会这样写:var url=UrlHelper.Action("index", "Home"); 若是要render一个action时会这样写:Html.RenderAction("index", "Home");工具

      这样的写法瑕疵在于咱们传递了两个字符串类型的参数在代码中,而咱们又免不了对action和controller作重命名操做:index->default, 即使是你用resharper这样的工具重命名也没法将UrlHelper.Action("index", "Home"); 改变为UrlHelper.Action("default", "Home");性能

      vs甚至在编译时都不会检查出来这个错误。 因此咱们的目标是:设计出具备静态检查的API,让vs 提示出这个错误来,甚至是重命名时直接把相关代码都能重命名。优化

使用Expression Tree 从新设计这两组APIthis

目标:设计出相似的API:Url.Action((HomeController c) => c.Index());url

1.很明显咱们须要在UrlHelper上写个扩展方法:设计

    public static string Action<TController>(
        this UrlHelper url,
        Expression<Func<TController, ActionResult>> actionSelector,
        string protocol = null,
        string hostname = null)
        {
            var action = "Index"; //待解析
            var controller = "Home";//带解析
            var routeValues = new RouteValueDictionary();//待解析

            return url.Action(action, controller, routeValues, protocol, hostname);
        }

如今只须要根据表达式Expression<Func<TController, ActionResult>> actionSelector 解析出action,controller,还有routeValues便可orm

2.解析出controller 的名称htm

分析:controller的名称能够根据泛型方法中的泛型参数TController获得

         private static string GetControllerName(Type controllerType)
        {
            var controllerName = controllerType.Name.EndsWith("Controller")
                ? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
                : controllerType.Name;
            return controllerName;
        }

3.解析action的名称

分析:因为表达式Expression<Func<TController, ActionResult>> actionSelector 是一个MethodCallExpression, 因此能够很容易获得action的名称:var action=call.Method.Name;

这样已经完成了最简单的url构造方案, 可是咱们尚未处理带有参数的action类型,例如:Url.Action((HomeController c) => c.Detail(10, 20));

4.解析routeValues

分析:action中的参数实际上就是MethodCallExpression中的参数,咱们解析这个expression的参数便可,而后获得RouteValues

        private static RouteValueDictionary GetRouteValues(MethodCallExpression call)
        {
            var routeValues = new RouteValueDictionary();

            var args = call.Arguments;
            ParameterInfo[] parameters = call.Method.GetParameters();
            var pairs = args.Select((a, i) => new
            {
                Argument = a,
                ParamName = parameters[i].Name
            });
            foreach (var argumentParameterPair in pairs)
            {
                string name = argumentParameterPair.ParamName;
                object value = argumentParameterPair.Argument.GetValue();
                if (value != null)
                {
                    var valueType = value.GetType();
                    if (valueType.IsValueType)
                    {
                        routeValues.Add(name, value);
                    }
                    throw new NotSupportedException("unsoupported parameter type {0}".FormatWith(value.ToString()));
                }
            }
            return routeValues;
        }

如此一来,相似Url.Action((HomeController c) => c.Detail(10, 20));这样的action也能够构造出Url了。

     if (valueType.IsValueType)
      {
          routeValues.Add(name, value);
      }

此代码并不支持复杂类型的参数,对于action中传入复杂的类型,好比:

    public class User
    {
        public int Age { get; set; }
        public string Email { get; set; }
    }

若是Action中的参数使用了User类型:

        public ActionResult SayHelloToUser(User user)
        {
            return new EmptyResult();
        }

如何解析呢?

   var properties = PropertyInfoHelper.GetProperties(valueType);
   foreach (var propertyInfo in properties)
     {
        routeValues.Add(propertyInfo.Name, propertyInfo.GetValue(value));
     }

大功告成,如今已经完美解决了各类类型的参数传入。

一样的道理,咱们能够扩展HtmlHelper 的 RenderAction(), ActionLink()….

缺陷

      早在09年,jeffery zhao就发表了lambda方式生成url的博客,对比了几种方案的性能问题,而且给出了优化方案,固然,我在写这篇博客的时候尚未真正尝试去优化这个方案,只是再次拜读了大神的方案,记得早些年就读过这些文章,可是今天从新读过仍然获益匪浅,不禁得感叹几句,莫非跑题了;-);-)

      接下来我会思考这个优化的问题。

结束语:本文使用Expression tree 扩展了HtmlHelper和UrlHelper,给出了一个具备静态检查的API实现方式。本文章所使用的源码提供下载,转载请注明出处

相关文章
相关标签/搜索