如何在ASP.NET MVC中创建一个CheckBoxListFor扩展方法?

15

我知道ASP.NET MVC Html助手扩展方法中有一个ListBoxFor扩展方法,但我认为复选框列表比列表框更加用户友好。

在老旧的WebForms中,有一个非常方便的CheckBoxList控件,但显然现在已经过时了。

问题是,为什么ASP.NET MVC没有创建复选框列表的方式?我该如何编写自己的扩展方法,以创建复选框列表并像ListBoxFor一样运作?

4个回答

34

这里提供了一个强类型的HtmlHelper,用于处理CheckBoxListFor,并将所选项目作为数组保存在您的viewdata模型中。我选择不包装Html.CheckBox或Html.CheckBoxFor方法,因为我不希望在我的复选框列表中出现隐藏的“false”字段。

请随意进行改进并重新发布 :-)

//View

<%: Html.CheckBoxListFor(model => model.FreightTypeIds, FreightTypeMultiSelectList)  %>

//Controller

    public ActionResult SomeAction(int[] FreightTypeIds)
    {
       //...

       return View();
    }


//Extension
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList allOptions, object htmlAttributes = null)
{
    ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression<TModel, IEnumerable<TProperty>>(expression, htmlHelper.ViewData);

    // Derive property name for checkbox name
    string propertyName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.PropertyName);

    // Get currently select values from the ViewData model
    IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);

    // Convert selected value list to a List<string> for easy manipulation
    IList<string> selectedValues = new List<string>();

    if (list != null)
    {
        selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
    }

    // Create div
    TagBuilder divTag = new TagBuilder("div");
    divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);

    // Add checkboxes
    foreach (SelectListItem item in allOptions)
    {
        divTag.InnerHtml += string.Format(
                                          "<div><input type=\"checkbox\" name=\"{0}\" id=\"{1}_{2}\" " +
                                          "value=\"{2}\" {3} /><label for=\"{1}_{2}\">{4}</label></div>",
                                          propertyName,
                                          TagBuilder.CreateSanitizedId(propertyName),
                                          item.Value,
                                          selectedValues.Contains(item.Value) ? "checked=\"checked\"" : string.Empty,
                                          item.Text);
    }

     return MvcHtmlString.Create(divTag.ToString());
}

谢谢,这似乎是我正在寻找的。回家后会尝试一下。 - Venemo
1
经过一些修改,我成功地让它按照我的要求工作了。非常感谢您提供的代码! :) - Venemo
@RafaelSoares 你计划进行什么样的验证?至少要勾选一个或类似的操作吗? - Rob
1
在这种情况下,我倾向于使用一个隐藏字段,例如“CheckboxChecked”,它没有初始值,并且有一个“必填”验证消息。然后,我会将一些jquery与复选框连接起来,以便如果一个或多个复选框被“选中”,则隐藏字段中的值将设置为true | yes | ok或其他任何值。如果没有选中任何内容,请删除隐藏字段中的值,验证应该可以正常工作。 - Rob
1
如果您将此嵌套在EditorFor中,它将无法正确绑定。我必须从htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.PropertyName)设置propertyName变量;然后使用经过消毒的id与TagBuilder.CreateSanitizedId()。 - Michael

2

我还在试验中,但这似乎可以与默认绑定器配合使用,并在提交后保留用户的选择。隐藏字段,真的吗?在HTML5中会有用吗?这感觉很疯狂,但我宁愿这样做,也不想因为ModelState.IsValid为false而为下拉列表和复选框列表访问我的数据库。

        public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, List<SelectListItem> list, string ModelCollectionName)
    {
        var sb = new StringBuilder();

        if (list != null)
        {
            int i = 0;

            foreach (var l in list)
            {
                string collectionNameIndex = String.Format("{0}[{1}]", ModelCollectionName, i);

                var hiddenName = new TagBuilder("input");
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Text")));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("value", l.Text));

                var hiddenValue = new TagBuilder("input");
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Value")));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("value", l.Value));

                var checkBoxTag = htmlHelper.CheckBox(String.Format("{0}.{1}", collectionNameIndex, "Selected"), l.Selected);

                var labelTag = new TagBuilder("label");
                labelTag.Attributes.Add(new KeyValuePair<string, string>("for", String.Format("{0}.{1}", collectionNameIndex, "Name")));
                labelTag.SetInnerText(l.Text);

                sb.Append(hiddenName);
                sb.Append(hiddenValue);
                sb.Append(checkBoxTag);
                sb.Append(labelTag);
                sb.Append("<br/>");

                i++;
            }
        }

        return MvcHtmlString.Create(sb.ToString());
    }

1

虽然只有微软的员工才能回答为什么这种辅助方法不存在,但您可以尝试以下方法:

模型:

public class MyViewModel
{
    public bool[] Values { get; set; }
}

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel 
        { 
            Values = new[] { true, false, true, false }
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

视图:

<% using (Html.BeginForm()) { %>
    <%: Html.EditorFor(x => x.Values) %>
    <input type="submit" value="OK" />
<% } %>

正如您所看到的,EditorFor将处理所有必要的内容。


这是一个不错的想法,但正如我所说,我希望有类似于ListBoxFor的行为。 - Venemo
你能具体说明一下吗?ListBoxFor 生成 <select> 并接受不同类型。你需要什么? - Darin Dimitrov

0

你可能会对 Jeremiah Clark 的 MVC CheckBoxList Helper 文章感兴趣(不幸的是,它的日期是2008年11月,涉及MVC 1)。


这篇文章不错,但它并没有考虑到强类型的HTML助手。 - Venemo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接