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 的内容。

我查看了以下内容(还有更多,但这些是最值得注意的):

问题是所有这些答案看起来都很棒,但都超过 3-4 岁了。

这些方法还有效吗?

期望的结果

<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
}