通过扩展方法,可以让CheckBox水平排列,生成CheckBoxList,正如"MVC扩展生成CheckBoxList并水平排列"一文。但,如何对生成的CheckBoxList验证呢?比如要求至少勾选一项:     
□ 思路
在强类型视图页中,@Html.EditorFor(model => model.属性, "模版名称", new{ ...路由数据...}),模版名称对应Views/Shared/EditorTemplates/CheckBoxList.cshtml部分视图,路由数据用来传递生成CheckBoxList所需要的一切,并在CheckBoxList.cshtml中显示错误信息,最后CheckBoxList的生成交给扩展方法。
   
□ Model
    public class Course   
    {    
        public int Id { get; set; }    
        public string Name { get; set; }    
    }
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models   
{    
    public class Student    
    {    
        [Display(Name = "姓名")]    
        [Required(ErrorMessage = "必填")]    
        [StringLength(100, ErrorMessage = "最大长度100")]    
        public string Name { get; set; }
        [Display(Name = "所选课程")]   
        [Required(ErrorMessage = "至少选择一个课程")]    
        public string Courses{ get; set; }     
    }    
}
□ 在_Layout.cshtml中启用客户端异步验证
    @Scripts.Render("~/bundles/jquery")   
    @Scripts.Render("/bundles/jqueryval") //启用客户端异步验证    
    @RenderSection("scripts", required: false)
□ Home/Index.cshtml
@using MvcApplication1.Infrastructure.Enums
  @model MvcApplication1.Models.Student
@{
      ViewBag.Title = "Index";
      Layout = "~/Views/Shared/_Layout.cshtml";
  }
@using (Html.BeginForm())
  {
  @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
  <fieldset>
<legend>Student</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Courses)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Courses, "CheckBoxList", new
            {
                  TagName = "Courses",
  CheckBoxItems = ViewBag.Course,
Position = Position.Horizontal,
Numbers = 3
})
</div>
<p>
<input type="submit" value="创建"/>
</p>
</fieldset>
}
其中,用到了有关CheckBoxList水平或垂直排列的一个枚举:
namespace MvcApplication1.Infrastructure.Enums
  
{
  
    public enum Position
  
    {
  
        Horizontal,
  
        Vertical
  
    }
  
}   
生成CheckBoxList的路由数据,以及模版名称:
            @Html.EditorFor(model => model.Courses, "CheckBoxList", new
  
            {
  
                TagName = "Courses",
  
                CheckBoxItems = ViewBag.Course,
  
                Position = Position.Horizontal,
  
                Numbers = 3
  
            }) 
□ Views/Shared/EditorTemplates/CheckBoxList.cshtml
@using MvcApplication1.Infrastructure.Enums
  @{
      var required = false;
  object validationMessage = string.Empty;
    //获取客户端验证属性
      var validationAttributes = Html.GetUnobtrusiveValidationAttributes(""); 
  if (validationAttributes.ContainsKey("data-val") && validationAttributes.ContainsKey("data-val-required"))
    {
          required = true;
  if (!validationAttributes.TryGetValue("data-val-required", out validationMessage))
        {
              validationMessage = "This field is required.";
  }
validationAttributes.Add("required","required");
}
var tagName = ViewData["TagName"] == null ? "CheckBoxList" : (string)ViewData["TagName"];
var checkboxItems = ViewData["CheckBoxItems"] == null ? new List<SelectListItem>() : (IEnumerable<SelectListItem>)ViewData["CheckBoxItems"];
var position = ViewData["Position"] == null ? Position.Horizontal : (Position)ViewData["Position"];
var numbers = 0;
if (ViewData["Numbers"] == null)
    {
  numbers = 1;
}
else if (!int.TryParse(ViewData["Numbers"].ToString(), out numbers))
    {
  numbers = 1;
}
    else
      {
  numbers = (int)ViewData["Numbers"];
}
}
@Html.CheckBoxList(
tagName,
checkboxItems,
new RouteValueDictionary(validationAttributes),
  position,
numbers)
@if (required)
  {
  <span class="field-validation-valid" data-valmsg-for="@(tagName)" data-valmsg-replace="false">
@validationMessage
</span>
}
□ 生成CheckBoxList的扩展方法
using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using MvcApplication1.Infrastructure.Enums;
  namespace System.Web.Mvc
  {
  public static class CheckBoxListExtensions
    {
          #region 水平方向
          /// <summary>
          /// CheckBoxList
          /// </summary>
          /// <param name="htmlHelper">HTML helper</param>
          /// <param name="name"></param>
          /// <param name="listInfo"></param>
          /// <param name="htmlAttributes"></param>
          /// <param name="number">每行显式的个数</param>
          /// <returns></returns>
  public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper,
            string name,
  IEnumerable<SelectListItem> listInfo,
IDictionary<string, object> htmlAttributes,
            int number)
          {
  if (string.IsNullOrEmpty(name))
            {
  throw new ArgumentException("必须给CheckBoxList一个name值", "name");
}
if (listInfo == null)
            {
  throw new ArgumentNullException("listInfo", "List<SelectListItem> listInfo不能为null");
}
            var selectListItems = listInfo as SelectListItem[] ?? listInfo.ToArray();
              if (!selectListItems.Any())
              {
  throw new ArgumentException("List<SelectListItem> listInfo 至少有一组资料", "listInfo");
}
            var sb = new StringBuilder();
  var lineNumber = 0;
foreach (var info in selectListItems)
            {
  lineNumber++;
var builder = new TagBuilder("input");
                if (info.Selected)
                  {
  builder.MergeAttribute("checked", "checked");
}
builder.MergeAttributes(htmlAttributes);
builder.MergeAttribute("type", "checkbox");
                builder.MergeAttribute("value", info.Value);
                  builder.MergeAttribute("name", name);
  builder.MergeAttribute("id", string.Format("{0}_{1}", name, info.Value));
sb.Append(builder.ToString(TagRenderMode.Normal));
var labelBuilder = new TagBuilder("label");
labelBuilder.MergeAttribute("for", string.Format("{0}_{1}", name, info.Value));
labelBuilder.InnerHtml = info.Text;
sb.Append(labelBuilder.ToString(TagRenderMode.Normal));
                if (number == 0 || (lineNumber % number == 0))
                  {
                      sb.Append("<br />");
  }
}
            return MvcHtmlString.Create(sb.ToString());
  }
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper,
            string name,
  IEnumerable<SelectListItem> listInfo)
        {
  return htmlHelper.CheckBoxList(name, listInfo, (IDictionary<string, object>) null, 0);
}
        #endregion
          #region 垂直方向
  public static MvcHtmlString CheckBoxListVertical(this HtmlHelper htmlHelper,
            string name,
  IEnumerable<SelectListItem> listInfo,
IDictionary<string, object> htmlAttributes,
            int columnNumber = 1)
          {
  if (string.IsNullOrEmpty(name))
            {
  throw new ArgumentException("必须给CheckBoxList的name属性赋值", "name");
}
if (listInfo == null)
            {
  throw new ArgumentNullException("listInfo", "List<SelectListInfo> listInfo不能为null");
}
            var selectListItmes = listInfo as SelectListItem[] ?? listInfo.ToArray();
              if (!selectListItmes.Any())
              {
  throw new ArgumentException("List<SelectListItem> listInfo不能为null","listInfo");
}
var dataCount = selectListItmes.Count();
var rows = Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(dataCount)/Convert.ToDecimal(columnNumber)));
            if (dataCount <= columnNumber || dataCount - columnNumber == 1)
              {
  rows = dataCount;
}
var wrapBuilder = new TagBuilder("div");
wrapBuilder.MergeAttribute("style","float:left; line-height:25px; padding-right:5px;");
var wrapStart = wrapBuilder.ToString(TagRenderMode.StartTag);
            var wrapClose = string.Concat(wrapBuilder.ToString(TagRenderMode.EndTag),
                  " <div style=\"clear:both;\"></div>");
  var wrapBreak = string.Concat("</div>", wrapBuilder.ToString(TagRenderMode.StartTag));
            var sb = new StringBuilder();
  sb.Append(wrapStart);
var lineNumber = 0;
foreach (var info in selectListItmes)
            {
  var builder = new TagBuilder("input");
                if (info.Selected)
                  {
  builder.MergeAttribute("checked","checked");
}
builder.MergeAttributes(htmlAttributes);
builder.MergeAttribute("type","checkbox");
                builder.MergeAttribute("value", info.Value);
                  builder.MergeAttribute("name", name);
  builder.MergeAttribute("id", string.Format("{0}_{1}",name, info.Value));
sb.Append(builder.ToString(TagRenderMode.Normal));
var labelBuilder = new TagBuilder("label");
labelBuilder.MergeAttribute("for", string.Format("{0}_{1}", name, info.Value));
labelBuilder.InnerHtml = info.Text;
sb.Append(labelBuilder.ToString(TagRenderMode.Normal));
lineNumber++;
                if (lineNumber.Equals(rows))
                  {
  sb.Append(wrapBreak);
lineNumber = 0;
}
                else
                  {
                      sb.Append("<br />");
  }
}
sb.Append(wrapClose);
            return MvcHtmlString.Create(sb.ToString());
  }
        #endregion
          #region 水平或垂直方向
  public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper,
            string name,
  IEnumerable<SelectListItem> listInfo,
IDictionary<string, object> htmlAttributes,
Position position = Position.Horizontal,
            int number = 0)
          {
  if (string.IsNullOrEmpty(name))
            {
  throw new ArgumentException("必须给CheckBoxList的name属性赋值","name");
}
if (listInfo == null)
            {
  throw new ArgumentNullException("listInfo", "List<SelectListItem> listInfo不能为null");
}
            var selectListItems = listInfo as SelectListItem[] ?? listInfo.ToArray();
              if (!selectListItems.Any())
              {
  throw new ArgumentException("List<SelectListItem> listInfo至少要一组资料","listInfo");
}
            var sb = new StringBuilder();
  var lineNumber = 0;
            switch (position)
              {
                  case Position.Horizontal:
  foreach (var info in selectListItems)
                    {
  lineNumber++;
sb.Append(CreateCheckBoxItem(info, name, htmlAttributes));
                        if (number == 0 || (lineNumber%number == 0))
                          {
                              sb.Append("<br />");
  }
}
                    sb.Append("<br />");
                      break;
                  case Position.Vertical:
  var dataCount = selectListItems.Count();
var rows = Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(dataCount)/Convert.ToDecimal(number)));
                    if (dataCount <= number || dataCount - number == 1)
                      {
  rows = dataCount;
}
var wrapBuilder = new TagBuilder("div");
wrapBuilder.MergeAttribute("style","float:left; line-height:25px; padding-right:5px;");
var wrapStart = wrapBuilder.ToString(TagRenderMode.StartTag);
var wrapClose = string.Concat(wrapBuilder.ToString(TagRenderMode.EndTag), " <div style=\"clear:both;\"></div>");
var wrapBreak = string.Concat("</div>", wrapBuilder.ToString(TagRenderMode.StartTag));
sb.Append(wrapStart);
foreach (var info in selectListItems)
                    {
  lineNumber++;
sb.Append(CreateCheckBoxItem(info, name, htmlAttributes));
                        if (lineNumber.Equals(rows))
                          {
  sb.Append(wrapBreak);
lineNumber = 0;
}
                        else
                          {
  sb.Append(wrapClose);
}
}
sb.Append(wrapClose);
                    break;
  }
            return MvcHtmlString.Create(sb.ToString());
  }
internal static string CreateCheckBoxItem(SelectListItem info, string name,
IDictionary<string, object> htmlAttributes)
        {
              var sb = new StringBuilder();
  var builder = new TagBuilder("input");
            if (info.Selected)
              {
  builder.MergeAttribute("checked","checked");
}
builder.MergeAttributes(htmlAttributes);
builder.MergeAttribute("type", "checkbox");
            builder.MergeAttribute("value", info.Value);
              builder.MergeAttribute("name", name);
  builder.MergeAttribute("id", string.Format("{0}_{1}", name, info.Value));
sb.Append(builder.ToString(TagRenderMode.Normal));
var labelBuilder = new TagBuilder("label");
labelBuilder.MergeAttribute("for", string.Format("{0}_{1}", name, info.Value));
labelBuilder.InnerHtml = info.Text;
sb.Append(labelBuilder.ToString(TagRenderMode.Normal));
            return sb.ToString();
  }
        #endregion
  }
}
□ HomeController
using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Web;
  using System.Web.Mvc;
  using MvcApplication1.Infrastructure.Services;
using MvcApplication1.Models;
  namespace MvcApplication1.Controllers
  {
  public class HomeController : Controller
    {
  private readonly CourseService service = new CourseService();
        private List<SelectListItem> coursesSelectListItems
          {
  get
            {
                  var courses = this.service.GetCourses();
                  var items = new List<SelectListItem>();
  foreach (var c in courses)
                {
                      items.Add(new SelectListItem()
                      {
  Value = c.Id.ToString(),
Text = c.Name
});
}
                return items;
  }
}
        public ActionResult Index()
          {
              ViewBag.Course = this.coursesSelectListItems;
              return View();
  }
[HttpPost]
[ValidateAntiForgeryToken]
        public ActionResult Index(Student stu)
          {
              ViewBag.Course = this.coursesSelectListItems;
              if (ModelState.IsValid)
              {
                  return View();
  }
            return View(stu);
  }
}
}
□ CouseService.cs
using System.Collections.Generic;
  using MvcApplication1.Models;
  namespace MvcApplication1.Infrastructure.Services
  {
  public class CourseService
    {
          public IEnumerable<Course> GetCourses()
          {
  return new List<Course>()
            {
  new Course(){Id = 1, Name = "语文"},
new Course(){Id = 2, Name = "数学"},
new Course(){Id = 3, Name = "哲学"},
new Course(){Id = 4, Name = "英语"},
new Course(){Id = 5,Name = "法语"},
new Course(){Id = 6, Name = "物理"},
new Course(){Id = 7, Name = "化学"},
new Course(){Id = 8, Name = "工程"}
};
}
}
}
  
参考资料:
  
ASP.NET MVC - CheckBoxList与ValidationMessage