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實現方式。本文章所使用的源碼提供下載,轉載請註明出處

相關文章
相關標籤/搜索