当捕获输入记录时,我如何正确地使用ViewModel与视图建立双向绑定?

本文关键字:视图 ViewModel 建立 绑定 正确地 记录 输入 | 更新日期: 2023-09-27 18:08:42

我有一个表单,需要从表单中的复选框中捕获值。每个复选框应该有一个整数值,当提交表单时,视图模型应该验证这些值,并且至少应该选择一个。

我还需要构建一个双向绑定,以便框架将自动选择在页面加载时所选择的选项。

这是我的模型的样子

public class SomeViewModel 
{
    [Required(ErrorMessage = "You must select at least one site.")]
    [Display(Name = "All site to have access too")]
    public int[] Sites { get; set; }
}

我将ViewModel封装在一个名为Presenter的Presentation类中,如下所示

public class Presenter
{
    public SomeViewModel Access { get; set; }
    public IEnumerable<Site> AvailableSites { get; set; }
}

现在,我将Presenter传递给我的视图,并希望渲染

我的视图是这样的

<div class="form-group">
    @Html.LabelFor(m => m.Access.Sites, new { @class = "col-sm-2 control-label" })
    <div class="col-sm-10">
        @for (int i = 0; i < Model.AvailableSites.Count(); i++ )
        {
            <label class="radio-inline">
                @Html.CheckBoxFor(m => m.Access.Sites[i]) 
            </label>
        }
    </div>
    @Html.ValidationMessageFor(m => m.Access.Sites)
</div>

由于@Html.CheckBoxFor接受bool值,并且我正在传递一个整数,我在视图内的@Html.CheckBoxFor(m => m.Access.Sites[i])行上得到错误。

如何纠正这个问题?以及如何在此视图中正确呈现复选框?

当捕获输入记录时,我如何正确地使用ViewModel与视图建立双向绑定?

正如您所发现的,您只能使用CheckBoxFor()绑定到bool属性。它有点不清楚为什么你有两个视图模型当你可以通过使用

来简化它
public class Presenter
{
    [Required(ErrorMessage = "You must select at least one site.")]
    [Display(Name = "All site to have access too")]
    public int[] Sites { get; set; 
    public IEnumerable<Site> AvailableSites { get; set; }
}

您可以考虑的一个选项(基于上述模型并假设Site类型包含属性int IDstring Name)

将视图更改为手动生成<input type="checkbox" />元素

@foreach(var site in Model.AvailableSites)
{
    // assumes your using Razor v2 or higher
    bool isSelected = Model.Sites.Contains(s.ID);
    <label>
        <input type="checkbox" name="Sites" value="@site.ID" checked = @isSelected />
        <span>@s.Name</span>
    </label>
}
@Html.ValidationMessageFor(m => m.Sites)

注意@Html.LabelFor(m => m.Sites)是不合适的。<label>是一个可访问性元素,用于将焦点设置为与其关联的表单控件,而Sites没有窗体控件。你可以使用<div>@Html.DisplayNameFor(m => m.Sites)</div>

一个更好的选择(和MVC方式)是创建一个视图模型来表示你想要在视图

public class SiteVM
{
    public int ID { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

和在GET方法中,基于所有可用站点(例如

)返回List<SiteVM>
List<SiteVM> model = db.Sites.Select(x => new SiteVM() { ID = x.ID, Name = x.Name };
return View(model);

并在视图中使用EditorTemplatefor循环(详细信息请参考此答案)来生成视图

@model List<SiteVM>
....
@using (Html.BeginForm())
{
    for(int i = 0; i < Model.Count; i++)
    {
        @Html.HiddenFor(m => m[i].ID)
        @Html.HiddenFor(m => m[i].Name)
        <label>
            @Html.CheckBoxFor(m => m[i].IsSelected)
            <span>@Model[i].Name</span>
        </label>
        @Html.ValidationMessage("Sites")
    }
    ....
}

,然后在POST方法

[HttpPost]
public ActionResult Edit(List<SiteVM> model)
{
    if (!model.Any(x => x.IsSelected))
    {
        ModelState.AddModelError("Sites", "Please select at least one site");
    }
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    ...
}

在任何一种情况下,您都不能使用jquery.validate.unobtrusive获得客户端验证,因为您不能(也不能)为集合属性创建表单控件。但是,您可以编写自己的脚本来显示验证消息并取消默认提交,例如(假设您使用@Html.ValidationMessageFor(m => m.Sites, "", new { id = "sitevalidation" }))

var sitevalidation = $('#sitevalidation');
$('form').submit(function() {
    var isValid = $('input[type="checkbox"]:checked').length > 0;
    if (!isValid) {
        sitevalidation.text('Please select at least one site');
        return false; // cancel the submit
    }
}
$('input[type="checkbox"]').click(function() {
    var isValid = $('input[type="checkbox"]:checked').length > 0;
    if (isValid) {
        sitevalidation.text(''); // remove validation message
    }
}