今天在ASP.NET MVC代码时用到了Html.RenderAction,代码如下:

@{Html.RenderAction("RecentNews")}

通过字符串指定Action的名称,有两点不爽:

1. 输入时不能智能感知;

2. 输错了不能实时提示。

有这两点不爽,写代码的乐趣就大减。有享受感觉的代码应该是这样的: 

@{Html.RenderAction<AggSiteController>(c => c.RecentNews());}

是的,Lamda,给你写代码带来畅快感觉的Lamda!

微软不让我们享受,我们就自己动手,丰衣足食。自己写一个支持Lamda表达式的Html.RenderAction,代码如下:

[ASP.NET MVC]让Html.RenderAction支持Lamda表达式
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Linq.Expressions;

namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        public static void RenderAction<TController>(this HtmlHelper htmlHelper, 
            Expression<Action<TController>> operation) 
            where TController : Controller
        {
            var actionName = ((MethodCallExpression)operation.Body).Method.Name;
            htmlHelper.RenderAction(actionName);
        }
    }
}
[ASP.NET MVC]让Html.RenderAction支持Lamda表达式

注:其中"((MethodCallExpression)operation.Body).Method.Name"代码来自Get Method Name From Action

顺带分享一篇文章When to use Html.RenderPartial and Html.RenderAction in ASP.NET MVC Razor Views,通过这篇文章你可以清楚的知道Html.RenderPartial与Html.RenderAction之间的区别。

比如:博客园首页的最新随笔列表就适合用Html.RenderPartial,而右侧的“新闻列表”就适合用Html.RenderAction。

简单的理解就是:Html.RenderPartial用的到PartialView只用一次(虽然实际可以多次使用,但比较麻烦,每次都要传Model),Html.RenderAction用的到PartialView被多个视图使用(有自己的Action提供Model)。

在评论中,向晚提到了Html.RenderAction的一个优点:

ChildAciton的优势是可以应用OutputCahce特性实现局部缓存。

我们当时用Html.RenderAction,而不用Html.RenderPartial,这也是一个重要原因。

总结一下ChildAciton+PartialView的优势:

  1. 可以在“不同Action相同View”中方便地被共享
  2. 可以在“不同Action不同View”中方便地被共享
  3. “可以应用OutputCahce特性实现局部缓存”

我们在解决另外一个问题时,发现它还有一个优势:

  4. 可以用ChildAciton直接处理Ajax请求。

比如博客园首页加载“最新随笔列表”的应用场景,在用户打开页面时直接显示最新随笔(非ajax加载);在用户点击页面上的“刷新”链接时,通过ajax更新“最新随笔列表”。

[ASP.NET MVC]让Html.RenderAction支持Lamda表达式

使用ChildAciton+PartialView,在首页页面视图中只需Html.RenderAction即可。

@{ Html.RenderAction<AggSiteController>(c => c.PostList(1,20)); }

同样的ChildAction可以直接服务于Ajax请求,实现了ChildAciton+PartialView的重用。

这时,我们遇到了一个问题,在RenderAction的时候需要向Action传递参数,之前实现的简陋的Html.RenderAction并没有对此提供支持,需要改进一下。

评论中获知,ASP.NET MVC 4中已经在实现Lamda版Html.RenderAction。签出ASP.NET MVC 4的源代码一下,果然实现了,向Action传参数的问题自然也被微软解决了。参照那段代码,精简一下就能解决我们的问题。

向Action传参数,需要通过RouteValueDictionary,我们所要做的工作就是从Lamda表达式中获取参数名称与参数值,并还添加至RouteValueDictionary。

完整代码如下:

[ASP.NET MVC]让Html.RenderAction支持Lamda表达式
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Linq.Expressions;
using System.Web.Routing;

namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        public static void RenderAction<TController>(this HtmlHelper htmlHelper,
            Expression<Action<TController>> operation)
            where TController : Controller
        {
            var controllerName = typeof(TController).Name;
            if (controllerName.EndsWith("Controller"))
            {
                controllerName = controllerName.Substring(0, 
                    controllerName.Length - "Controller".Length);
            }

            var call = operation.Body as MethodCallExpression;
            if (call != null)
            {
                var actionName = call.Method.Name;
                var parameters = call.Method.GetParameters();
                if (parameters.Length > 0)
                {
                    var routeValues = new RouteValueDictionary();
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        var ce = call.Arguments[i] as ConstantExpression;
                        if (ce != null)
                        {
                            routeValues.Add(parameters[i].Name, ce.Value);
                        }
                        else
                        {
                            var lambda = Expression.Lambda(call.Arguments[i],
                                                         operation.Parameters);
                            Delegate d = lambda.Compile();
                            var value = d.DynamicInvoke(new object[1]);
                            routeValues.Add(parameters[i].Name, value);
                        }
                        
                    }
                    htmlHelper.RenderAction(actionName, controllerName, routeValues);
                }
                else
                {
                    htmlHelper.RenderAction(actionName, controllerName);
                }
            }
        }
    }
}
[ASP.NET MVC]让Html.RenderAction支持Lamda表达式

注:目前的这个实现不支持可空类型的参数,比如:public ActionResult PostList(int? pageIndex)。

开源,真好! 

分类:

技术点:

相关文章: