【问题标题】:MVC5 Custom Binding: List of Strings to List of Complex TypeMVC5 自定义绑定:字符串列表到复杂类型列表
【发布时间】:2016-04-19 20:30:08
【问题描述】:

在我目前正在编写的 ASP.Net MVC 5 应用程序中,一个 Material 可以有多个名称 (MaterialNames)。我已经构建了一个 Web 界面来添加、编辑和删除材料的 EditCreate 页面上的材料名称。为了实现这一点,我在那些名为Material.MaterialNames 的页面上有多个text-inputs。可以使用 javascript 在客户端添加和删除这些以更改名称的数量。我不需要inputs 的names 中的索引,因为用户应该查看和编辑的数据只是字符串的平面列表。

这些是我的模型的相关部分:

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.MaterialNames 的类型为List&lt;string&gt;,现在一切都会很好。在我的情况下,模型绑定器无法从作为表单数据传递的多个字符串值创建MaterialNames。我相信解决此问题的默认方法是

编写自定义模型绑定器

in this answer(= 覆盖BindProperty 并仅更改一个 PropertyType 的行为)这样的做法是个好主意吗?我会将它与globally registering the binder 结合起来,用于MaterialVM 类型。

我错过了一个更简单的选择吗?

难道不能只为默认绑定器提供一种将简单类型转换为复杂类型的方法吗?我的方法如何编写自定义模型活页夹正确吗?是否还有其他我遗漏的选项可以让这更直观?

【问题讨论】:

  • 参考答案 herehere 以从集合中动态添加和删除复杂对象
  • 我知道使用索引的可能性。我一直在寻找没有它们的解决方案,因为我认为它们会产生很多 html-overhead 而没有任何需要,因为我每个索引只有一个字符串。
  • 由于您编辑复杂对象的集合,因此您必须使用索引器(没有办法匹配 Name 属于哪个 IdMaterialId 属性否则,特别是如果您动态即使您要创建自定义 ModelBinder). You can only avoid the indexers if your property is public List MaterialNames { get; set; },也可以添加和删除项目
  • 理论上我不需要比我拥有的更多信息。我将不得不为活页夹添加索引。我的想法是实现即overridden cast 以使活页夹能够将字符串转换为MaterialNames。这就引出了一个问题:活页夹支持的非复杂类型是硬编码的吗?活页夹是否使用任何类型的标准反序列化方法以便我可以在那时添加转换逻辑?
  • 这样做没有任何意义。假设您有现有的项目 M1、M2 和 M3,并且在视图中删除 M2 并添加 M4 并将 M3 的名称更改为 M5。除非您具有关联的 ID 属性,否则您无法确定需要添加哪些项目,更新数据库中的删除,并且唯一的方法是提供索引器。

标签: c# asp.net asp.net-mvc-5 model-binding


【解决方案1】:

您可以使用默认模型绑定器绑定复杂类型,但您需要将.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>
}

此示例应导致它们出现在您的视图模型中:

【讨论】:

  • 我做了一些与您的解决方案类似的事情,只是为了使 asp.net MVC 模型绑定与复杂对象列表一起工作,因为发布的值必须具有按顺序排列的索引值。最后,我在表单发布之前动态添加了那些隐藏的输入字段。
  • @KMC 索引实际上不必是连续的。例如,在上面的代码中,我们可以使用index += 2 来故意跳过值——它仍然会绑定。不过,它们确实都需要具有积极和独特的价值。
  • 我记得有随机索引,比如 account.name[0] 和 account.name[10]。我再试一次。谢谢。
【解决方案2】:

视图模型应该迎合视图,所以它应该有一个

List&lt;string&gt; materialNames

MVC 绑定将绑定到视图模型。

然后在控制器或您处理映射的任何层中,您可以将 MaterialVM 映射到 Material

【讨论】:

  • 我不太喜欢这个想法,因为我需要复制数据和结构/对象,只是为了满足活页夹的需要。此外,我需要在我想要展示的所有地方添加额外的逻辑,或者让用户更改这些名称。可能这是最好的选择:/
  • 我同意你的观点,这就是为什么我不是 MVC(作为一个框架)或任何决定如何构建应用程序的框架的忠实粉丝。我通常构建服务,或者在服务器端使用 websockets,在客户端使用纯 html 和 javascript 或 typescript。
【解决方案3】:

如果您将MaterialVM 缓存在会话变量中或将IdMaterialNames 缓存在Dictionary&lt;int, List&lt;string&gt;&gt; 中,那么您可以将idList&lt;string&gt; 的名称发回操作。使用id,您可以查找材料,然后更新模型。

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

【讨论】:

    猜你喜欢
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    相关资源
    最近更新 更多