如何在 MVC 中保留复选框列表
How can I persist a check box list in MVC
我正在尝试构建一个 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 我认为这可能与模型活页夹有关,不知道什么时候没有选中复选框,但即使我已经完成了那个和其他各种 posts - 我不再前进了。
在一个 post 中,我看到隐藏字段经常与复选框结合使用以传递复选框的 'false' 状态,但我无法使用多个复选框 post 回到单个 属性。
任何人都可以阐明这一点吗?
已编辑:包括我在此 post 中突出显示的 demonstration project。希望这会帮助别人帮助我!
你的主要问题,以及当你取消选中所有项目时之前的选择是 'remembered' 的原因是你的模型中有一个构造函数调用 GetSessionIntArray()
来获取值您上次提交表格时存储的。 DefaultModelBinder
的工作原理是首先初始化您的模型(包括调用其默认构造函数),然后根据表单值设置其属性值。在以下场景中
第 1 步:导航至 Index()
方法
- 假设它是第一次调用并且没有任何项目被添加到
Session
,那么由 GetSessionIntArray()
编辑的 SelectedItems
return 的值为 int[0]
,它与 CheckItems
中的任何值都不匹配,因此未选中任何复选框。
第 2 步:选中前 2 个复选框并提交。
-
DefaultModelBinder
初始化 TestViewModel
的新实例并调用构造函数。 SelectedItems
的值再次变为 int[0]
(尚未向 Session
添加任何内容)。然后读取表单值,SelectedItems
的值现在是 int[1, 2]
(选中的复选框的值)。在 return 查看视图之前,调用方法中的代码并将 int[1, 2]
添加到 Session
。
第 3 步:取消选中所有复选框并再次提交。
- 您的模型再次初始化,但这次构造函数从
Session
读取值,而 SelectedItems
的值是 int[1,2]
。 DefaultModelBinder
读取 SelectedItems
的表单值,但有 none(未选中的复选框不提交值)因此没有任何设置和 [=15= 的值] 仍然是 int[1,2]
。然后您 return 视图和您的助手根据 SelectedItems
的值检查前 2 个复选框
您可以通过从模型中删除构造函数并修改扩展方法中的代码来测试 null
来解决此问题
if (SelectedItemArray != null && SelectedItemArray.Contains(item.Key))
{
....
但是您的实施还有其他问题,包括
您为每个复选框(您使用 builder.Attributes["id"] = PropertyName;
)生成重复的 id
属性,这是无效的 html.
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"
等)。
您使用 属性 的值进行绑定,但正确的方法(如所有内置扩展方法所使用的那样)是首先从 ModelState
获取值,然后从 ViewDataDictionary
开始,最后如果没有找到值,则实际模型 属性.
你永远不会使用 var metadata = ModelMetadata.....
的值,尽管你应该这样做(这样你就可以从方法中删除最后一个参数(int[] SelectedItemArray
),这实际上只是重复 expression
.
的值
旁注:隐藏字段的使用不适用于您的情况。 CheckboxFor()
方法生成额外的隐藏输入,因为该方法绑定到 bool
属性,并确保始终提交值。
我的建议是使用 MvcCheckBoxList 之类的包(我自己没有尝试过,因为我有自己的扩展方法),至少在你花一些时间研究 MVC 源代码之前更好地理解如何创建 HtmlHelper
扩展方法(如果听起来刺耳,我们深表歉意)。
我正在尝试构建一个 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 我认为这可能与模型活页夹有关,不知道什么时候没有选中复选框,但即使我已经完成了那个和其他各种 posts - 我不再前进了。
在一个 post 中,我看到隐藏字段经常与复选框结合使用以传递复选框的 'false' 状态,但我无法使用多个复选框 post 回到单个 属性。
任何人都可以阐明这一点吗?
已编辑:包括我在此 post 中突出显示的 demonstration project。希望这会帮助别人帮助我!
你的主要问题,以及当你取消选中所有项目时之前的选择是 'remembered' 的原因是你的模型中有一个构造函数调用 GetSessionIntArray()
来获取值您上次提交表格时存储的。 DefaultModelBinder
的工作原理是首先初始化您的模型(包括调用其默认构造函数),然后根据表单值设置其属性值。在以下场景中
第 1 步:导航至 Index()
方法
- 假设它是第一次调用并且没有任何项目被添加到
Session
,那么由GetSessionIntArray()
编辑的SelectedItems
return 的值为int[0]
,它与CheckItems
中的任何值都不匹配,因此未选中任何复选框。
第 2 步:选中前 2 个复选框并提交。
-
DefaultModelBinder
初始化TestViewModel
的新实例并调用构造函数。SelectedItems
的值再次变为int[0]
(尚未向Session
添加任何内容)。然后读取表单值,SelectedItems
的值现在是int[1, 2]
(选中的复选框的值)。在 return 查看视图之前,调用方法中的代码并将int[1, 2]
添加到Session
。
第 3 步:取消选中所有复选框并再次提交。
- 您的模型再次初始化,但这次构造函数从
Session
读取值,而SelectedItems
的值是int[1,2]
。DefaultModelBinder
读取SelectedItems
的表单值,但有 none(未选中的复选框不提交值)因此没有任何设置和 [=15= 的值] 仍然是int[1,2]
。然后您 return 视图和您的助手根据SelectedItems
的值检查前 2 个复选框
您可以通过从模型中删除构造函数并修改扩展方法中的代码来测试 null
if (SelectedItemArray != null && SelectedItemArray.Contains(item.Key))
{
....
但是您的实施还有其他问题,包括
您为每个复选框(您使用
builder.Attributes["id"] = PropertyName;
)生成重复的id
属性,这是无效的 html.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"
等)。您使用 属性 的值进行绑定,但正确的方法(如所有内置扩展方法所使用的那样)是首先从
ModelState
获取值,然后从ViewDataDictionary
开始,最后如果没有找到值,则实际模型 属性.你永远不会使用
var metadata = ModelMetadata.....
的值,尽管你应该这样做(这样你就可以从方法中删除最后一个参数(int[] SelectedItemArray
),这实际上只是重复expression
. 的值
旁注:隐藏字段的使用不适用于您的情况。 CheckboxFor()
方法生成额外的隐藏输入,因为该方法绑定到 bool
属性,并确保始终提交值。
我的建议是使用 MvcCheckBoxList 之类的包(我自己没有尝试过,因为我有自己的扩展方法),至少在你花一些时间研究 MVC 源代码之前更好地理解如何创建 HtmlHelper
扩展方法(如果听起来刺耳,我们深表歉意)。