如何将复选框列表中的 post 数据绑定回自定义 class

How to bind post data from checkbox list back to custom class

这个问题与之前提出的其他问题类似,但我的用法(和 MVC 知识)非常不同,所以我不知道如何调整其他答案以满足我的需要。

我有一个表格,用户可以在其中请求产品价格。该产品有多个 可选 模块,这会影响整体价格。然后,控制器应向我发送一封电子邮件,其中包含所选模块' DisplayName

我可以在请求表单中渲染这些模块标题,但在提交表单时无法读回它们。调试显示!ModelState.IsValid,model state内部出现转换异常:

The parameter conversion from type 'System.String' to type 'MyNamspace.Models.MyProductModule' failed because no type converter can convert between these types.

我的整个方法可能是错误的,但我已经在产品模型中定义了模块(SO 的简化示例),从 this tutorial:

开始工作
public class MyProductModule
{
    public string ModuleName { get; set; }
    public bool Checked { get; set; }
}

public class ProductRequest
{

    public ProductRequest()
    {
        Modules = LoadModules();
    }

    private List<MyProductModule> LoadModules()
    {
        return new List<MyProductModule>()
        {
            new MyProductModule() { ModuleName = "Module One", Checked = false },
            new MyProductModule() { ModuleName = "Module Two", Checked = false },
            new MyProductModule() { ModuleName = "Module Three", Checked = false }
        };
    }

    [DisplayName("MyProduct Modules")]
    public List<MyProductModule> Modules { get; set; }
}

这是我用来呈现复选框列表的代码:

@model MyNamespace.Models.ProductRequest

@foreach (var item in Model.Modules)
{
    <label>
        <input type="checkbox" name="Modules" value="@item.ModuleName" checked="@item.Checked" />
        @item.ModuleName
    </label>
}

以下是我尝试收集已发布表单数据的方式:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ProcessRequest(ProductRequest qd)
{
    if (!ModelState.IsValid)
    {
        return View("Index", qd); // Code exits here with ModelState error
    }
    else
    {
        StringBuilder sb = new StringBuilder();
        // null checks removed for brevity...
        sb.Append("<ol>");
        var selectedModules = qd.Modules.Where(x => x.Checked).Select(x => x).ToList();
        foreach (MyProductModule sm in selectedModules)
        {
            sb.AppendFormat("<li>{0}</li>", sm.ModuleName);
        }
        sb.Append("</ol>");
    }   
    // ....
}

如有任何帮助或建议,我们将不胜感激。作为一名 long-term webforms 开发人员,我发现 MVC 学习曲线是无情的!

解决方案

查看发布的答案。

您可能希望像这样渲染视图:

for (var i = 0; i < Model.Modules.Count; i++ )
{
    var item = Model.Modules[i];
    <label>
        @Html.CheckBox(string.Format("Modules[{0}].Checked", i), item.Checked)
        @item.ModuleName
    </label>
    @Html.Hidden(string.Format("Modules[{0}].ModuleName", i), item.ModuleName)
}

感谢 Stephen Muecke...

如建议的链接中所述,可以在不使用数据模型实例的情况下引用数据模型。因此,这是呈现复选框的正确方法:

<div>
    @for (int i = 0; i < Model.Modules.Count; i++)
    {   
        @Html.CheckBoxFor(m => m.Modules[i].Checked)
        @Html.LabelFor(m => m.Modules[i].Checked, Model.Modules[i].ModuleName)                  
        <br />
    }
</div>

这确保列出所有模块,即使用户实际上没有 select 所有模块,并且验证失败。

模型绑定会在表单发布时自动完成,因此处理表单的控制器代码不需要修改需要稍微修改。获取所有可用模块的代码应该是静态的。这样所有模块都可以 "read"/方便地使用。然后将该静态列表与用户提交的列表(可能只包含一些或 none 可用模块)进行比较:

List<MyProductModule> allModules = ProductRequest.LoadModules();
foreach(MyProductModule sm in qd.Modules)
{
    if(sm.Checked)
    {
        sb.AppendFormat("<li>{0}</li>", allModules[qd.Modules.IndexOf(sm)].ModuleName);
    }
}