在ASP.NET MVC中使用“RadioButtonList”和“CheckBoxList”_ASP.NET在《为HtmlHelper添加一个RadioButtonList扩展方法》中我通过对HtmlHelper和HtmlHelper的扩展使我们可以采用”RadioButtonList”的方式对一组类型为“radio”的<input>元素进行操作。昨天对对此进行了一些改进,并将“CheckBoxList”的功能添加进来。[源代码从这里下载]


在《​​为HtmlHelper添加一个RadioButtonList扩展方法​​​》中我通过对HtmlHelper和HtmlHelper<Model>的扩展使我们可以采用”RadioButtonList”的方式对一组类型为“radio”的<input>元素进行操作。昨天对对此进行了一些改进,并将“CheckBoxList”的功能添加进来。[源代码从​​这里​​下载]

一、有何特别之处?

和我的很多文章一样,旨在提供一种大体的解决方案,本解决方案旨在解决如下一些问题:


  • 通过独立的组件对绑定到ListControl(ASP.NET Web Form的说法)的列表进行单独管理;
  • 自动地调用上面这个组件获取列表信息进行相关Html的生成;
  • 支持ASP.NET MVC原生的Model Binding。

二、实例演示

我们还是以《​​为HtmlHelper添加一个RadioButtonList扩展方法​​》例子来演示RadioButtonList和CheckBoxList用法。下面是代表个人信息同时作为Model的Person类型,Gender、MaritalStatus 和Country分别代表性别、婚姻状况和国籍(这里支持多国籍)。


1: public class Person
2: {
3:     public string Name { get; set; }
4:     public string Gender { get; set; }
5:     [Display(Name = "Marital Status")]
6:     public string MaritalStatus { get; set; }
7:     public string[] Country { get; set; }
8: }


上述三个属性分别代表CodeManager这个独立组件维护的三个列表,CodeManager和代表列表选项的CodeDescription定义如下:


1: public class CodeDescription
2: {
3:     public string Code { get; set; }
4:     public string Description { get; set; }
5:     public string Category{get;set;}
6:
7:     public CodeDescription(string code, string description, string category)
8:     {
9:         this.Code = code;
10:         this.Description = description;
11:         this.Category = category;
12:     }
13: }
14: public static class CodeManager
15: {
16:     private static CodeDescription[] codes = new CodeDescription[]
17:     {
18:         new CodeDescription("M","Male","Gender"),
19:         new CodeDescription("F","Female","Gender"),
20:         new CodeDescription("S","Single","MaritalStatus"),
21:         new CodeDescription("M","Married","MaritalStatus"),
22:         new CodeDescription("CN","China","Country"),
23:         new CodeDescription("US","Unite States","Country"),
24:         new CodeDescription("UK","Britain","Country"),
25:         new CodeDescription("SG","Singapore","Country")
26:     };
27:     public static Collection<CodeDescription> GetCodes(string category)
28:     {
29:         Collection<CodeDescription> codeCollection = new Collection<CodeDescription>();
30:         foreach(var code in codes.Where(code=>code.Category == category))
31:         {
32:             codeCollection.Add(code);
33:         }
34:         return codeCollection;
35:     }
36: }


在默认的HomeController中,我们定义了如下两个Index方法,它们分别用于测试出栈数据(Model->UI)入栈数据(UI-〉Model)的绑定。


1: public class HomeController : Controller
2: {
3:     public ActionResult Index()
4:     {
5:         return View(new Person { Name = "Foo", Gender = "M", MaritalStatus = "S", Country = new string[]{"CN","US"} });
6:     }
7:     [HttpPost]
8:     public ActionResult Index(Person person)
9:     {
10:         return this.View(person);
11:     }
12: }


下面是Index操作对应的View的定义,这是一个Model类型为Person的强类型View。对于Person的三个基于列表的属性,我们分别调用了自定义的扩展方法RadioButtonListFor和CheckBoxListFor进行了绑定。方法的最后两个参数分别代表通过CodeManager维护的列表的组别(Gender、MaritalStatus和Country),和同组RadioButton和CheckBox布局方向(水平或者纵向)。


1: @using System.Web.UI.WebControls
2: @model Person
3: @{
4:     ViewBag.Title = "Index";
5: }
6: @using (Html.BeginForm())
7: {
8:     <table id="container">
9:         <tr>
10:             <td class="label">@Html.LabelFor(m => m.Name):</td>
11:             <td>@Html.EditorFor(m => m.Name)</td>
12:         </tr>
13:          <tr>
14:             <td class="label">@Html.LabelFor(m => m.Gender):</td>
15:             <td>@Html.RadioButtonListFor(m => m.Gender, "Gender")</td>
16:         </tr>
17:          <tr>
18:             <td class="label">@Html.LabelFor(m => m.MaritalStatus):</td>
19:             <td>@Html.RadioButtonListFor(m => m.MaritalStatus, "MaritalStatus")</td>
20:         </tr>
21:          <tr>
22:             <td class="label">@Html.LabelFor(m => m.Country):</td>
23:             <td>@Html.CheckBoxListFor(m => m.Country, "Country", RepeatDirection.Vertical)</td>
24:         </tr>
25:         <tr>
26:             <td colspan="2"><input type="submit" value="Save" /></td>
27:         </tr>
28:     </table>
29: }


下面是最终呈现出来的效果:

在ASP.NET MVC中使用“RadioButtonList”和“CheckBoxList”_MVC_02

三、两组扩展方法具体实现

现在我们简单地来看看RadioButtonList/RadioButtonListFor和CheckBoxList/CheckBoxListFor这两组扩展方法的实现。我们通过CodeManager得到列表集合,通过HtmlHelper结合 ModelMetadata得到当前数据,最终借助于ListControlUtil的GenerateHtml生成相关的Html。


1: public static class ListControlExtensions
2: {
3:     public static MvcHtmlString RadioButtonList(this HtmlHelper htmlHelper, string name, string codeCategory, RepeatDirection repeatDirection = RepeatDirection.Horizontal)
4:     {
5:         var codes = CodeManager.GetCodes(codeCategory);
6:         return ListControlUtil.GenerateHtml(name, codes, repeatDirection,"radio",null);
7:     }
8:     public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string codeCategory, RepeatDirection repeatDirection = RepeatDirection.Horizontal)
9:     {
10:         var codes = CodeManager.GetCodes(codeCategory);
11:         ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
12:         string name = ExpressionHelper.GetExpressionText(expression);
13:         string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
14:         return ListControlUtil.GenerateHtml(fullHtmlFieldName, codes, repeatDirection, "radio", metadata.Model);
15:     }
16:
17:     public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, string codeCategory, RepeatDirection repeatDirection = RepeatDirection.Horizontal)
18:     {
19:         var codes = CodeManager.GetCodes(codeCategory);
20:         return ListControlUtil.GenerateHtml(name, codes, repeatDirection, "checkbox", null);
21:     }
22:     public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string codeCategory, RepeatDirection repeatDirection = RepeatDirection.Horizontal)
23:     {
24:         var codes = CodeManager.GetCodes(codeCategory);
25:         ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
26:         string name = ExpressionHelper.GetExpressionText(expression);
27:         string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
28:         return ListControlUtil.GenerateHtml(fullHtmlFieldName, codes, repeatDirection, "checkbox", metadata.Model);
29:     }
30: }


ListControlUtil中生成相关Html的逻辑定义如下:


1: public static class ListControlUtil
2: {
3:     public static MvcHtmlString GenerateHtml(string name, Collection<CodeDescription> codes, RepeatDirection repeatDirection, string type, object stateValue)
4:     {
5:         TagBuilder table = new TagBuilder("table");
6:         int i = 0;
7:         bool isCheckBox = type == "checkbox";
8:         if (repeatDirection == RepeatDirection.Horizontal)
9:         {
10:             TagBuilder tr = new TagBuilder("tr");
11:             foreach (var code in codes)
12:             {
13:                 i++;
14:                 string id = string.Format("{0}_{1}", name, i);
15:                 TagBuilder td = new TagBuilder("td");
16:
17:                 bool isChecked = false;
18:                 if (isCheckBox)
19:                 {
20:                     IEnumerable<string> currentValues = stateValue as IEnumerable<string>;
21:                     isChecked = (null != currentValues && currentValues.Contains(code.Code));
22:                 }
23:                 else
24:                 {
25:                     string currentValue = stateValue as string;
26:                     isChecked = (null != currentValue && code.Code == currentValue);
27:                 }
28:
29:                 td.InnerHtml = GenerateRadioHtml(name, id, code.Description, code.Code, isChecked,type);
30:                 tr.InnerHtml += td.ToString();
31:             }
32:             table.InnerHtml = tr.ToString();
33:         }
34:         else
35:         {
36:             foreach (var code in codes)
37:             {
38:                 TagBuilder tr = new TagBuilder("tr");
39:                 i++;
40:                 string id = string.Format("{0}_{1}", name, i);
41:                 TagBuilder td = new TagBuilder("td");
42:
43:                 bool isChecked = false;
44:                 if (isCheckBox)
45:                 {
46:                     IEnumerable<string> currentValues = stateValue as IEnumerable<string>;
47:                     isChecked = (null != currentValues && currentValues.Contains(code.Code));
48:                 }
49:                 else
50:                 {
51:                     string currentValue = stateValue as string;
52:                     isChecked = (null != currentValue && code.Code == currentValue);
53:                 }
54:
55:                 td.InnerHtml = GenerateRadioHtml(name, id, code.Description, code.Code, isChecked, type);
56:                 tr.InnerHtml = td.ToString();
57:                 table.InnerHtml += tr.ToString();
58:             }
59:         }
60:         return new MvcHtmlString(table.ToString());
61:     }
62:
63:     private static string GenerateRadioHtml(string name, string id, string labelText, string value, bool isChecked, string type)
64:     {
65:         StringBuilder sb = new StringBuilder();
66:
67:         TagBuilder label = new TagBuilder("label");
68:         label.MergeAttribute("for", id);
69:         label.SetInnerText(labelText);
70:
71:         TagBuilder input = new TagBuilder("input");
72:         input.GenerateId(id);
73:         input.MergeAttribute("name", name);
74:         input.MergeAttribute("type", type);
75:         input.MergeAttribute("value", value);
76:         if (isChecked)
77:         {
78:             input.MergeAttribute("checked", "checked");
79:         }
80:         sb.AppendLine(input.ToString());
81:         sb.AppendLine(label.ToString());
82:         return sb.ToString();
83:     }
84: }