如何在MVC中保留复选框列表

时间:2016-07-25 17:24:11

标签: asp.net-mvc asp.net-mvc-5 html-helper checkboxlist

我正在尝试构建一个html帮助器来创建一个复选框列表,这些复选框将使用会话保持检查状态。它大部分都有效,在您选中并取消选中各个框并单击“提交”时,请记住复选框状态。但是,如果您已选中并提交了框,则返回并清除复选框并重新提交(当它们全部清除时) - 它似乎想要记住最后的选择。这是我写的......

[HomeController的]

public ActionResult Index()
{
    TestViewModel tvm = new TestViewModel();
    return View(tvm);
}

[HttpPost]
public ActionResult Index(TestViewModel viewModel)
{
    viewModel.SessionCommit();
    return View(viewModel);
}

[索引视图]

@model TestApp.Models.TestViewModel

@{
    ViewBag.Title = "Index";
}

@using (Html.BeginForm())
{
    <p>Checkboxes:</p>
    @Html.CheckedListFor(x => x.SelectedItems, Model.CheckItems, Model.SelectedItems)

    <input type="submit" name="Submit form" />
}

[TestViewModel]

// Simulate the checklist data source
public Dictionary<int, string> CheckItems
{
    get
    {
        return new Dictionary<int, string>()
        {
            {1, "Item 1"},
            {2, "Item 2"},
            {3, "Item 3"},
            {4, "Item 4"}
        };
    }
}

// Holds the checked list selections
public int[] SelectedItems { get; set; }


// Contructor
public TestViewModel()
{
    SelectedItems = GetSessionIntArray("seld", new int[0] );
}


// Save selections to session
public void SessionCommit()
{
    System.Web.HttpContext.Current.Session["seld"] = SelectedItems;
}


// Helper to get an int array from session
int[] GetSessionIntArray(string sessionVar, int[] defaultValue)
{
    if (System.Web.HttpContext.Current.Session == null || System.Web.HttpContext.Current.Session[sessionVar] == null)
        return defaultValue;

    return (int[])System.Web.HttpContext.Current.Session[sessionVar];
}

[HTML帮助者]

public static MvcHtmlString CheckedList(this HtmlHelper htmlHelper, string PropertyName, Dictionary<int, string> ListItems, int[] SelectedItemArray)
{
    StringBuilder result = new StringBuilder();
    foreach(var item in ListItems)
    {
        result.Append(@"<label>");
        var builder = new TagBuilder("input");
        builder.Attributes["type"] = "checkbox";
        builder.Attributes["name"] = PropertyName;
        builder.Attributes["id"] = PropertyName;
        builder.Attributes["value"] = item.Key.ToString();
        builder.Attributes["data-val"] = item.Key.ToString();
        if (SelectedItemArray.Contains(item.Key))
            builder.Attributes["checked"] = "checked";

        result.Append(builder.ToString(TagRenderMode.SelfClosing));
        result.AppendLine(string.Format(" {0}</label>", item.Value));
    }
    return MvcHtmlString.Create(result.ToString());
}
public static MvcHtmlString CheckedListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Dictionary<int, string> ListItems, int[] SelectedItemArray)
{
    var name = ExpressionHelper.GetExpressionText(expression);
    var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    return CheckedList(htmlHelper, name, ListItems, SelectedItemArray);
}

我已经阅读了this SO question,我认为这可能与模型绑定器有关,不知道什么时候没有选中复选框,但即使我已经浏览了那个和其他各种帖子 - 我是没有进一步前进。

在一篇文章中,我看到隐藏字段经常与复选框结合使用以传递复选框的“错误”状态,但我无法使用多个复选框发布回单个属性。

有人能说清楚这个吗?

已编辑:要包含我在此帖中突出显示的demonstration project。希望这能帮助别人帮助我!

1 个答案:

答案 0 :(得分:4)

您的主要问题以及在取消选中所有项目时“记住”之前选择的原因是您的模型中有一个构造函数调用GetSessionIntArray(),它会获取您上次存储的值你提交了表格。 DefaultModelBinder首先初始化模型(包括调用其默认构造函数),然后根据表单值设置其属性的值。在以下场景中

第1步:导航到Index()方法

  • 假设第一次通话并且没有项目添加到Session,则SelectedItems返回的GetSessionIntArray()的值为int[0],与任何值都不匹配在CheckItems中,因此不会选中复选框。

第2步:检查前2个复选框并提交。

  • DefaultModelBinder初始化TestViewModel的新实例并调用构造函数。 SelectedItems的值再次为int[0](尚未向Session添加任何内容)。然后读取表单值,SelectedItems的值现在为int[1, 2](已选中复选框的值)。调用方法中的代码,并在返回视图之前将int[1, 2]添加到Session

第3步:取消选中所有复选框并再次提交。

  • 您的模型再次初始化,但这次构造函数从Session读取值,SelectedItems的值为int[1,2]DefaultModelBinder读取SelectedItems的表单值,但没有(未经检查的复选框不提交值),因此无需设置任何内容,SelectedItems的值仍为{{ 1}}。然后返回视图,助手根据int[1,2]
  • 的值检查前2个复选框

您可以通过从模型中删除构造函数并修改扩展方法中的代码来测试SelectedItems

来解决此问题。
null

但是,您的实施还存在其他问题,包括

  1. 您为每个复选框(使用if (SelectedItemArray != null && SelectedItemArray.Contains(item.Key)) { .... )生成了重复的id属性,这是无效的HTML。

  2. builder.Attributes["id"] = PropertyName;毫无意义(它生成builder.Attributes["data-val"] = item.Key.ToString();data-val="1"等)。假设您想要不显眼的客户端验证属性,那么属性将是data-val="1"。但是,您需要一个关联的占位符来显示错误消息(由data-val="true" data-val-required="The SelectedItems field is required."生成,并且每个复选框的@Html.ValidationMessageFor()属性需要是不同的(即使用索引器 - name等)。

  3. 您使用属性的值进行绑定,但正确的方法(因为所有内置的扩展方法都使用)是首先从name="[0].SelectedItems"获取值,然后从{{1}获取值最后如果没有找到值,那么实际的模型属性。

  4. 你永远不会使用ModelState的值,尽管你应该这样做(这样你就可以从方法中删除最后一个参数(ViewDataDictionary),这实际上只是重复了var metadata = ModelMetadata.....

  5. 附注:隐藏字段的使用不适用于您的情况。 int[] SelectedItemArray方法生成额外的隐藏输入,因为该方法绑定到expression属性,并确保始终提交值。

    我的建议是使用MvcCheckBoxList之类的包(我自己没有尝试过,因为我有自己的扩展方法),至少在你花一些时间研究MVC源代码以更好地理解之前如何创建CheckboxFor()扩展方法(如果听起来很苛刻则道歉)。