Mvc EditorTemplate 与 HtmlHelper 添加数据属性
Mvc EditorTemplate vs HtmlHelper for Adding Data Attributes
所以我四处搜索并没有找到任何 "new" 答案。不知道是因为答案还是正确的,还是最近没人问过。
我有以下 类(为简洁起见进行了压缩):
public class Address {
public int Id { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public int StateProvinceId { get; set; }
public StateProvince StateProvince { get; set; }
}
public class Country {
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class StateProvince {
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
我正在寻找的是为 Country
的列表创建 EditorFor
/DropDownList
的最简单但非常可定制的方法。具体来说,我想将 data attributes
添加到 select
的每个 option
,这样,通过 javascript,我可以为 StateProvince
重新填充 select
] 通过根据 data
值过滤 StateProvince
属于所选 Country
的内容。
我查看了以下内容(还有更多,但这些是最值得注意的):
- SelectListItem with data-attributes
- EditorTemplate for DropDownList(也许是选项 #3?)
- Adding html class tag under <option> in Html.DropDownList
问题是所有这些答案看起来都很棒,但都超过 3-4 岁了。
这些方法还有效吗?
- 如果是这样,我为什么要选择
Helper
而不是 Template
?
- 如果不是,有什么新的方法来处理这个问题?提供代码示例以与我最终所做的进行比较的奖励。
期望的结果
<select id="Country" name="Country">
<option value="1" data-country-code="US">United States</option>
<option value="2" data-country-code="CA">Canada</option>
...
</select>
因为您在这里真正想做的是避免调用 ajax 来填充基于第一个下拉列表的第二个下拉列表,那么 EditorTemplate
(您必须生成所有html 用于 <select>
和 <option>
标签手动),或者使用 HtmlHelper
扩展方法是特别好的解决方案,因为您必须编写大量代码来模拟DropDownListFor()
方法在内部做什么以确保正确的 2 向模型绑定、为客户端验证生成正确的 data-val-*
属性等
相反,您可以使用视图模型将所有 StateProvince
的集合传递给视图(您的编辑数据,因此始终使用视图模型),将其转换为 javascript 数组对象,然后在第一个下拉列表的 .change()
事件中,根据所选选项过滤结果,并使用结果生成第二个下拉列表中的选项。
您的视图模型看起来像
public class AddressVM
{
public int? Id { get; set; }
[Display(Name = "Country")]
[Required(ErrorMessage = "Please select a country")]
public int? SelectedCountry { get; set; }
[Display(Name = "State Province")]
[Required(ErrorMessage = "Please select a state province")]
public int? SelectedStateProvince { get; set; }
public IEnumerable<SelectListItem> CountryList { get; set; }
public IEnumerable<SelectListItem> StateProvinceList { get; set; }
public IEnumerable<StateProvinceVM> AllStateProvinces { get; set; }
}
public class StateProvinceVM
{
public int Id { get; set; }
public string Name { get; set; }
public int Country { get; set; }
}
视图将是
@using (Html.BeginForm())
{
@Html.LabelFor(m => m.SelectedCountry)
@Html.DropDownListFor(m => m.SelectedCountry,Model.CountryList, "Please select", new { ... })
@Html.ValidationMessageFor(m => m.SelectedCountry)
@Html.LabelFor(m => m.SelectedStateProvince)
@Html.DropDownListFor(m => m.SelectedStateProvince,Model.StateProvinceList, "Please select", new { ... })
@Html.ValidationMessageFor(m => m.SelectedStateProvince)
....
}
和脚本
// convert collection to javascript array
var allStateProvinces = @Html.Raw(Json.Encode(Model.AllStateProvinces))
var statesProvinces = $('#SelectedStateProvince');
$('#SelectedCountry').change(function() {
var selectedCountry = $(this).val();
// get the state provinces matching the selected country
var options = allStateProvinces.filter(function(item) {
return item.Country == selectedCountry;
});
// clear existing options and add label option
statesProvinces.empty();
statesProvinces.append($('<option></option>').val('').text('Please select'));
// add options based on selected country
$.each(options, function(index, item) {
statesProvinces.append($('<option></option>').val(item.Id).text(item.Name));
});
});
最后,在控制器中,您需要填充 SelectLists 并允许在 ModelState
无效时返回视图,或者当您编辑现有数据时(在这两种情况下,都需要填充 SelectLists) .为避免重复代码,创建一个私有辅助方法
private void ConfigureViewModel(AddressVM model)
{
IEnumerable<Country> countries = db.Countries;
IEnumerable<StateProvince> stateProvinces = db.StateProvinces;
model.AllStateProvinces = stateProvinces.Select(x => new StateProvinceVM
{
Id = x.Id,
Name = x.Name,
Country = x.CountryId
});
model.CountryList = new countries.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
if (model.SelectedCountry.HasValue)
{
model.StateProvinceList = stateProvinces.Where(x => x.CountryId == model.SelectedCountry.Value).Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
}
else
{
model.StateProvinceList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
然后控制器方法将是(对于 Create()
方法)
public ActionResult Create()
{
AddressVM model = new AddressVM();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(AddressVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // initailize an Address data model, map its properties from the view model
.... // save and redirect
}
所以我四处搜索并没有找到任何 "new" 答案。不知道是因为答案还是正确的,还是最近没人问过。
我有以下 类(为简洁起见进行了压缩):
public class Address {
public int Id { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public int StateProvinceId { get; set; }
public StateProvince StateProvince { get; set; }
}
public class Country {
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class StateProvince {
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
我正在寻找的是为 Country
的列表创建 EditorFor
/DropDownList
的最简单但非常可定制的方法。具体来说,我想将 data attributes
添加到 select
的每个 option
,这样,通过 javascript,我可以为 StateProvince
重新填充 select
] 通过根据 data
值过滤 StateProvince
属于所选 Country
的内容。
我查看了以下内容(还有更多,但这些是最值得注意的):
- SelectListItem with data-attributes
- EditorTemplate for DropDownList(也许是选项 #3?)
- Adding html class tag under <option> in Html.DropDownList
问题是所有这些答案看起来都很棒,但都超过 3-4 岁了。
这些方法还有效吗?
- 如果是这样,我为什么要选择
Helper
而不是Template
? - 如果不是,有什么新的方法来处理这个问题?提供代码示例以与我最终所做的进行比较的奖励。
期望的结果
<select id="Country" name="Country">
<option value="1" data-country-code="US">United States</option>
<option value="2" data-country-code="CA">Canada</option>
...
</select>
因为您在这里真正想做的是避免调用 ajax 来填充基于第一个下拉列表的第二个下拉列表,那么 EditorTemplate
(您必须生成所有html 用于 <select>
和 <option>
标签手动),或者使用 HtmlHelper
扩展方法是特别好的解决方案,因为您必须编写大量代码来模拟DropDownListFor()
方法在内部做什么以确保正确的 2 向模型绑定、为客户端验证生成正确的 data-val-*
属性等
相反,您可以使用视图模型将所有 StateProvince
的集合传递给视图(您的编辑数据,因此始终使用视图模型),将其转换为 javascript 数组对象,然后在第一个下拉列表的 .change()
事件中,根据所选选项过滤结果,并使用结果生成第二个下拉列表中的选项。
您的视图模型看起来像
public class AddressVM
{
public int? Id { get; set; }
[Display(Name = "Country")]
[Required(ErrorMessage = "Please select a country")]
public int? SelectedCountry { get; set; }
[Display(Name = "State Province")]
[Required(ErrorMessage = "Please select a state province")]
public int? SelectedStateProvince { get; set; }
public IEnumerable<SelectListItem> CountryList { get; set; }
public IEnumerable<SelectListItem> StateProvinceList { get; set; }
public IEnumerable<StateProvinceVM> AllStateProvinces { get; set; }
}
public class StateProvinceVM
{
public int Id { get; set; }
public string Name { get; set; }
public int Country { get; set; }
}
视图将是
@using (Html.BeginForm())
{
@Html.LabelFor(m => m.SelectedCountry)
@Html.DropDownListFor(m => m.SelectedCountry,Model.CountryList, "Please select", new { ... })
@Html.ValidationMessageFor(m => m.SelectedCountry)
@Html.LabelFor(m => m.SelectedStateProvince)
@Html.DropDownListFor(m => m.SelectedStateProvince,Model.StateProvinceList, "Please select", new { ... })
@Html.ValidationMessageFor(m => m.SelectedStateProvince)
....
}
和脚本
// convert collection to javascript array
var allStateProvinces = @Html.Raw(Json.Encode(Model.AllStateProvinces))
var statesProvinces = $('#SelectedStateProvince');
$('#SelectedCountry').change(function() {
var selectedCountry = $(this).val();
// get the state provinces matching the selected country
var options = allStateProvinces.filter(function(item) {
return item.Country == selectedCountry;
});
// clear existing options and add label option
statesProvinces.empty();
statesProvinces.append($('<option></option>').val('').text('Please select'));
// add options based on selected country
$.each(options, function(index, item) {
statesProvinces.append($('<option></option>').val(item.Id).text(item.Name));
});
});
最后,在控制器中,您需要填充 SelectLists 并允许在 ModelState
无效时返回视图,或者当您编辑现有数据时(在这两种情况下,都需要填充 SelectLists) .为避免重复代码,创建一个私有辅助方法
private void ConfigureViewModel(AddressVM model)
{
IEnumerable<Country> countries = db.Countries;
IEnumerable<StateProvince> stateProvinces = db.StateProvinces;
model.AllStateProvinces = stateProvinces.Select(x => new StateProvinceVM
{
Id = x.Id,
Name = x.Name,
Country = x.CountryId
});
model.CountryList = new countries.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
if (model.SelectedCountry.HasValue)
{
model.StateProvinceList = stateProvinces.Where(x => x.CountryId == model.SelectedCountry.Value).Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
}
else
{
model.StateProvinceList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
然后控制器方法将是(对于 Create()
方法)
public ActionResult Create()
{
AddressVM model = new AddressVM();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(AddressVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // initailize an Address data model, map its properties from the view model
.... // save and redirect
}