MVC5自定义绑定:将字符串列表绑定到复杂类型列表

3
在我目前编写的ASP.Net MVC 5应用程序中,一个Material可以有多个名称(MaterialName)。我已经构建了一个Web界面,在材料的EditCreate页面上添加、编辑和删除这些名称。为了实现这一点,我在这些页面上有多个名为Material.MaterialNamestext-input。这些可以通过javascript在客户端添加和删除来改变名称的数量。我不需要在inputname中使用索引,因为用户应该看到和编辑的数据只是一个平面字符串列表。
以下是我的模型的相关部分:
public class MaterialVM
{
    public Material Material { get; set; }
    // ...
}
public class Material
{
    public int Id { get; set; }
    public List<MaterialName> MaterialNames { get; set; }
    // ...
}
public class MaterialName
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int MaterialId { get; set; }
}

现在,如果Material.MaterialNamesList<string>类型,一切都会很好。但在我的情况下,模型绑定器无法从作为表单数据传递的多个字符串值中创建MaterialName。我认为修复这个问题的默认方法是编写自定义模型绑定器。像这个答案中那样(=覆盖BindProperty并仅更改一个PropertyType的行为),这样做是否明智?我将与全局注册该绑定器用于类型MaterialVM。是否有更简单的选择?难道不能仅提供默认绑定器一个将简单类型转换为复杂类型的方法吗?我的自定义模型绑定器编写方法正确吗?是否还有其他选项我错过了,可以使这更直观?

请参考以下答案:这里这里,了解如何动态添加和删除集合中的复杂对象。 - user3559349
我知道可以使用索引的可能性。我正在寻找一种不需要使用索引的解决方案,因为我认为它们会产生很多HTML开销,而且没有必要,因为每个索引只有一个字符串。 - Georg Jung
由于您正在编辑一组复杂对象,因此必须使用索引器(否则无法匹配哪个“Name”属于哪个“Id”和“MaterialId”属性,特别是如果您动态添加和删除项目,即使您创建自定义ModelBinder)。如果您的属性是“public List<string> MaterialNames { get; set; }”,则可以避免使用索引器。 - user3559349
理论上,我不需要比我现在拥有的更多信息。我只需要为绑定器添加索引即可。我的想法是实现一个重写转换,以使绑定器能够将字符串转换为MaterialName。这就引出了一个问题:绑定器支持的非复杂类型是否是硬编码的?绑定器是否使用任何标准反序列化方法,以便我可以在那个点添加转换逻辑? - Georg Jung
从性能角度来看,这将是糟糕的,但如果你确实想这样做,那就把你的模型属性List<MaterialName>改为List<string>。但是,如果用户更改了一个项目的值,并且你只比较字符串值,你将不知道该项目是新项目还是现有项目(因为数据库中不存在该名称)。 - user3559349
显示剩余3条评论
3个回答

2

您可以使用默认模型绑定器绑定复杂类型,但需要将.Index属性添加到重复控件的模板中。

这是我为您的情况想到的最简单的示例:

@model WebApplication25.Models.MaterialVM

@using (Html.BeginForm())
{
    @Html.HiddenFor(model => model.Material.Id)
    <table id="output">
        <tr>
            <th>Name</th>
            <th>MaterialId</th>
            <th>Id</th>
        </tr>

    </table>
    <button id="add-more" type="button">Add Another</button>
    <button type="submit">Submit</button>
}
@section scripts{
    <script>
        (function (window) {
            "use strict";
            var index = 0;
            $("#add-more").on("click", function () {
                $("#output").append(
                  "<tr><td>" +
                  "<input type='hidden' name='Material.MaterialNames.Index' value='" + index + "' />" + // add in the index
                  "<input type='text' name='Material.MaterialNames[" + index + "].Name' /></td>" +
                  "<td><input type='number' name='Material.MaterialNames[" + index + "].MaterialId' /></td>" +
                  "<td><input type='number' name='Material.MaterialNames[" + index + "].Id' />" +
                  "</td></tr>");
                index++;
            });

        }(window));
    </script>
}

这个例子应该会使它们出现在你的视图模型中: 绑定到模型的数据列表


1
我做了与您的解决方案类似的事情,只是为了使asp.net MVC模型绑定能够使用复杂对象列表,因为发布的值必须按顺序具有索引值。最后,在表单提交之前,我动态添加了那些隐藏的输入字段。 - dfdsfdsfsdf
@KMC 实际上索引并不需要连续。例如,在上面的代码中,我们可以使用 index += 2 来有意跳过某些值 - 它仍然会绑定。但是,它们都需要是正数且唯一的值。 - Will Ray
我记得有像account.name[0]和account.name[10]这样的随机索引。我会再试一次。谢谢。 - dfdsfdsfsdf

1
视图模型应该迎合视图,因此它可能需要一个 List<string> materialNames。MVC绑定将绑定到视图模型。然后在控制器或任何处理映射的层中,您可以将 MaterialVM 映射到 Material

我并不是很喜欢这个想法,因为我需要复制数据和结构/对象,以适应绑定器的需求。此外,我需要在所有想要呈现或让用户更改这些名称的地方添加额外的逻辑。虽然可能这是最好的选择 :/ - Georg Jung
我同意你的观点,这也是为什么我不太喜欢MVC(作为一个框架)或者任何规定应用程序构建方式的框架。通常我会构建服务,或在服务器端使用WebSockets,而在客户端则使用纯HTML和JavaScript或TypeScript。 - DonO

0
如果您将MaterialVM缓存到会话变量或Id中,并将MaterialNames存储在Dictionary<int,List<string>>中,则可以将id和名称的List<string>提交回操作。使用id,您可以查找材料,然后更新模型。

或者,在JS中,您可以JSON.stringify表单数据,然后在服务器端解析它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接