【问题标题】:How can I persist a check box list in MVC如何在 MVC 中保留复选框列表
【发布时间】:2016-07-25 17:24:10
【问题描述】:

我正在尝试构建一个 html 帮助程序来创建复选框列表,该列表将使用会话保持检查状态。它在大多数情况下都有效,当您选中和取消选中各种框并单击提交时会记住复选框状态。但是,如果您检查并提交了复选框,然后您返回并清除复选框并重新提交(当它们全部清除时) - 它似乎想要记住最后的选择。这是我写的……

[家庭控制器]

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。希望这会帮助有人帮助我!

【问题讨论】:

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


    【解决方案1】:

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

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

    • 假设它是第一次调用并且Session没有添加任何项目,那么GetSessionIntArray()返回的SelectedItems的值是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 的值仍为int[1,2]。然后您返回视图,您的助手会根据 SelectedItems 的值检查前 2 个复选框

    您可以通过从模型中删除构造函数并修改扩展方法中的代码来测试null来解决这个问题

    if (SelectedItemArray != null && SelectedItemArray.Contains(item.Key))
    {
        ....
    

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

    1. 您为每个复选框生成重复的id 属性(您使用builder.Attributes["id"] = PropertyName;)这是无效的html。

    2. 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 属性需要不同(即使用索引器 - name="[0].SelectedItems" 等)。

      李>
    3. 您使用属性的值进行绑定,但正确的方法(所有内置扩展方法都使用)是首先从ModelState 获取值,然后从ViewDataDictionary 获取值,最后如果没有找到值,然后是实际的模型属性。

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

    旁注:隐藏字段的使用不适用于您的情况。 CheckboxFor() 方法会生成额外的隐藏输入,因为该方法绑定到 bool 属性,并确保始终提交值。

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

    【讨论】:

    • 反应很好,几乎被理解了。您能否在答案中澄清 3 和 4。我知道(在 4 中)我没有使用表达式,但是您如何从表达式中获取值作为 int 数组?我听到了你的建议,但是我想学习如何自己做这些事情,而不是依赖包/黑匣子。几个月前才开始使用 MVC,所以我还在努力完成它。
    • 第 3 项:参考private static MvcHtmlString InputHelper(...) 方法,了解帮助程序通常如何设置value 属性和检查顺序。要了解为什么首先检查ModelState,请参阅this answer 的第二部分
    • 第四项:ModelMetadata的Model属性包含模型,所以你可以使用return CheckedList(htmlHelper, name, ListItems, metadata.Model);并去掉最后一个参数
    • 在你发帖之前,我终于明白了第 4 项。不过,有了上述信息,我就可以整理帮助程序并摆脱无效的 html,现在一切正常。非常感谢。
    猜你喜欢
    • 2012-07-15
    • 1970-01-01
    • 2016-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-27
    • 1970-01-01
    相关资源
    最近更新 更多