实现ASP.NET MVC3 HtmlHelper 的 RadioButtonList 与CheckBoxList 扩展

ASP.NET MVC3也出来有一段时间了,对于没有RadioButtonList 与CheckBoxList的问题,网上也已经有很多解决方案了,可以for循环拼接出来,也可以引用ASP.NET MVC Toolkit,等等方法。其实本没有必要写出来的,不过看了WebGird中队format的实现方式,一时来了兴趣,就尝试这实现了一下,发现还是有不少机关的,于是就拿出来和大家分享一下。

 

首先清楚下要实现什么,For<TModel, TProperty> 这样的重载时必须的了,然后还要实现WebGrid的format类似的支持,可以自定义输出格式。当然输出正确的列表HTML是必须的,不过这个不难。

 

那么就先实现一个最基本的核心函数

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
namespace SymApplauseAward.MvcExtention
{
    public static class CheckBoxListExtension
    {  
      public static MvcHtmlString InputListInternal(
            this HtmlHelper html,
            string name,
            IEnumerable<SelectListItem> selectList,
            bool allowMultiple,     
           )
        {
            string fullHtmlFieldName = 
                      html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (string.IsNullOrEmpty(fullHtmlFieldName))
            {
                throw new ArgumentException("filed can't be null or empty !", 
                                            "name");
            }
            StringBuilder strBuilder = new StringBuilder();
            TagBuilder tagBuilder = new TagBuilder("input");
            foreach (var item in selectList)
            {   //Clear first
                tagBuilder.InnerHtml = string.Empty;
                if (allowMultiple)
                {
                    tagBuilder.MergeAttribute("type", "checkbox", true);
                }
                else
                {
                    tagBuilder.MergeAttribute("type", "radio", true);
                }
                tagBuilder.MergeAttribute("value", item.Value, true);
                if (item.Selected)
                    tagBuilder.MergeAttribute("selected", "selected", true);
             
                tagBuilder.MergeAttribute("name", fullHtmlFieldName, true);
                var s = tagBuilder.ToString()+item.Text+"<br/>"
                strBuilder.Append(s);
            }
            return new MvcHtmlString(strBuilder.ToString());
        }
}

这里通过TagBuilder创建Html,虽然也可以直接拼字符串,不过既然微软提供了相应的类,就直接用吧。 并且通过html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);获得了合法的name值,这步很重要,它会把“.”转化为“_”从而使name合法。

好了,基本功能实现了,如果用@Html.InputListInternal去调用,就可以显示相应的List了。

 

不过,这样还不太灵活,我们无法自定义格式,那么下面就实现自定义格式的功能。查看一下WebGrid的文档,原来format参数的类型是Func<dynamic,object>类型,那简单我们加一个好了。加完后代码如下:

 public static MvcHtmlString InputListInternal(
            this HtmlHelper html,
            string name,
            IEnumerable<SelectListItem> selectList,
            bool allowMultiple,
            Func<dynamic, object> format
           )
        {
           //...
            if (format == null)
                format = i => i.Button + i.Text + "<br/>";
            StringBuilder strBuilder = new StringBuilder();
            TagBuilder tagBuilder = new TagBuilder("input");
            foreach (var item in selectList)
            {   
                //...
                var inputItem = new { Button = tagBuilder.ToString(),Text = item.Text };
                var s = format(inputItem).ToString();
                strBuilder.Append(s);
            }
            return new MvcHtmlString(strBuilder.ToString());
}

赶快用一下@Html.InputListInternal(“Score”,scores,false,item=>item.Button+”>”+item.Text),事与愿违,报错了“object 不包含Button的定义”。奇怪了,明明传入的是new {Button=tagBuilder.ToString(), Text=item.Text},有Button属性啊!

 

于是开始查技术文档,原来是dynamic的循环不会涉及扩展方法,换句话说,声明的匿名对象对于dynamic来说是不知道的,于是就强制转换为Object了,Object当然没有Button属性,于是报错了。对于这种问题,有两种解决办法,方法一:不用扩展方法的调用方式,而是回归静态类静态方法调用的方式。方法二:在dynamic的上下文中引用类型。第一种方法不说了,要那样就不做这个扩展了,于是尝试第二种解决办法。

由于是在页面定义的Lamda表达式,因此Context是Page。想想的话,要用这个扩展必然要引用这个命名空间,那么只要传回的类型是这个空间的public类,那么dynamic就能找到相应的类型了。于是定义了一个新类InputListItem,将代码稍作改动:

   var btnHtmlString = new MvcHtmlString(tagBuilder.ToString());
   var inputItem = new InputListItem { Button = btnHtmlString, Text = item.Text };
   var s = format(inputItem).ToString();
   strBuilder.Append(s);

再试下,OK,显示正常了,而且也是自定义的格式,至此format事情是搞定了。

 

最后实现一下For<TModel, TProperty>的重载,这个比较简单,就是传入一个Expression<Func<TModel, TProperty>>的参数,这里多提示一下可以用ExpressionHelper.GetExpressionText来直接提取参数的内容。至于匿名类型的htmlAttributes的处理也可以用HtmlHelper.AnonymousObjectToHtmlAttributes简单完成。在经过进一步封装后,最终代码如下:

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
namespace Pride_Zhou.MvcExtention
{
    public class InputListItem
    {
        public MvcHtmlString Button { get; set; }
        public string Text { get; set; }
    }
    public static class CheckBoxListExtension
    {
        #region CheckBoxList
        public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(
            this HtmlHelper<TModel> html,
            Expression<Func<TModel, TProperty>> expression,
            IEnumerable<SelectListItem> selectList,
            Func<dynamic, object> format,
            object htmlAttributes)
        {
            return CheckBoxListFor(html, expression, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(
            this HtmlHelper<TModel> html,
            Expression<Func<TModel, TProperty>> expression,
            IEnumerable<SelectListItem> selectList,
            Func<dynamic, object> format = null,
            IDictionary<string, object> htmlAttributes = null)
        {
            return CheckBoxList(html, GetName(expression), selectList, format, htmlAttributes);
        }
        public static MvcHtmlString CheckBoxList(
          this HtmlHelper html,
          string name,
          IEnumerable<SelectListItem> selectList,
          Func<dynamic, object> format,
          object htmlAttributes)
        {
            return CheckBoxList(html, name, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString CheckBoxList(
           this HtmlHelper html,
           string name,
           IEnumerable<SelectListItem> selectList,
           Func<dynamic, object> format = null,
           IDictionary<string, object> htmlAttributes = null)
        {
            return InputListInternal(html, name, selectList, true, format, htmlAttributes);
        }
        #endregion
        #region RadioButtonList
        public static MvcHtmlString RadioButtonList(
         this HtmlHelper html,
         string name,
         IEnumerable<SelectListItem> selectList,
         Func<dynamic, object> format,
         object htmlAttributes)
        {
            return RadioButtonList(html, name, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString RadioButtonList(
         this HtmlHelper html,
         string name,
         IEnumerable<SelectListItem> selectList,
         Func<dynamic, object> format = null,
         IDictionary<string, object> htmlAttributes = null)
        {
            return InputListInternal(html, name, selectList, false, format, htmlAttributes);
        }
        public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
          this HtmlHelper<TModel> html,
          Expression<Func<TModel, TProperty>> expression,
          IEnumerable<SelectListItem> selectList,
          Func<dynamic, object> format,
          object htmlAttributes)
        {
            return RadioButtonList(html, GetName(expression), selectList, format, htmlAttributes);
        }
        public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
            this HtmlHelper<TModel> html,
            Expression<Func<TModel, TProperty>> expression,
            IEnumerable<SelectListItem> selectList,
            Func<dynamic, object> format = null,
            IDictionary<string, object> htmlAttributes = null)
        {
            return RadioButtonList(html, GetName(expression), selectList, format, htmlAttributes);
        }
        #endregion
        /*-------------------------------------
         * Core Function
         --------------------------------------*/
        private static MvcHtmlString InputListInternal(
            this HtmlHelper html,
            string name,
            IEnumerable<SelectListItem> selectList,
            bool allowMultiple,
            Func<dynamic, object> format,
            IDictionary<string, object> htmlAttributes
           )
        {
            string fullHtmlFieldName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (string.IsNullOrEmpty(fullHtmlFieldName))
            {
                throw new ArgumentException("filed can't be null or empty !", "name");
            }
            if (format == null)
                format = i => i.Button + i.Text + "<br/>";
            StringBuilder strBuilder = new StringBuilder();
            TagBuilder tagBuilder = new TagBuilder("input");
            foreach (var item in selectList)
            {   //Clear first
                tagBuilder.InnerHtml = string.Empty;
                if (allowMultiple)
                {
                    tagBuilder.MergeAttribute("type", "checkbox", true);
                }
                else
                {
                    tagBuilder.MergeAttribute("type", "radio", true);
                }
                tagBuilder.MergeAttribute("value", item.Value, true);
                if (item.Selected)
                    tagBuilder.MergeAttribute("selected", "selected", true);
                tagBuilder.MergeAttributes<string, object>(htmlAttributes);
                tagBuilder.MergeAttribute("name", fullHtmlFieldName, true);
                var btnHtmlString = new MvcHtmlString(tagBuilder.ToString());
                var inputItem = new InputListItem { Button = btnHtmlString, Text = item.Text };
                var s = format(inputItem).ToString();
                strBuilder.Append(s);
            }
            return new MvcHtmlString(strBuilder.ToString());
        }
        private static string GetName(LambdaExpression expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            return ExpressionHelper.GetExpressionText(expression);
        }
    }
}

好了,现在可以像WebGrid一样生成批量Html了,例如:

   <table style="border:0 solid #000;">
        <tbody>
        @Html.RadioButtonList(String.Format("{0}{1}", "S_", gId),
                              Model.GetQuestions(gId).ToList().Select(q => new SelectListItem { Value = q.Id.ToString(), Text = q.Content }),
                             @<tr style="border:0 solid #000;">
                               <td style="width:2em;border:0 solid #000;">@item.Button</td>
                               <td style="border:0 solid #000;">@item.Text</td>
                              </tr>, new { @class = "score"})
        </tbody>
   </table>

怎样,和WebGird一样吧,如果觉得item参数过少,扩展InputListItem类就可以了。 总体来说实现不难,就是Dynamic比较坑爹,这里很多代码是通过查看MVC源码的实现来写的,当然检查还不够完美,如果有兴趣可以仿照微软,加上相应的代码,这样用起来也更稳定。

 

 

 


posted @ 2011-07-12 18:17  Pride Zhou  阅读(3714)  评论(2编辑  收藏  举报