关于 c#:ASP.Net Core MVC 如何针对强类型模型发布未知数量的字段

ASP.Net Core MVC how to post unknown number of fields against strongly-typed model

完全被这件事难住了。我有一个页面,用户可以在其中单击"添加步骤"按钮,并且一些 javascript 将带有来自 <template> 标记的附加输入的行附加到表单中。从控制器调试时,值为空。

我查看了以下帖子,但这里的答案似乎都没有帮助(不确定是不是因为我正在使用 .Net 核心 - 可能不是):

在 ASP MVC 中获取未知数量变量的发布数据

使用可变数量的输入从表单传递数据 - MVC 5

模型(Procedure.cs)

1
2
3
4
5
6
7
8
public class Procedure
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Type { get; set; }
    public int MinNumber { get; set; }
    public int MaxNumber { get; set; }
}

控制器 (ProceduresController.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([FromForm]List<Procedure> model)
{
    if (ModelState.IsValid)
    {
        foreach(var field in model)
        {
            // Database things will happpen eventually, but at this point the Count = 0
        }
    }

    return View(model);
}

目前 Create 动作签名包括 [FromForm] 属性,但我不知道这里是否需要。我也试过 List<Procedure> model.

还有视图 (Create.cshtml)

我删除了很多 UI 和 javascript 以使内容更易于阅读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@model List<Procedure>
/* also tried @model Procedure */

@{
    ViewData["Title"] ="Create Procedure";
    ViewData["Sidebar"] ="App";
}

<form asp-action="Create" enctype="multipart/form-data">
   
   
       
           
                <label class="field-label">Instruction Name</label>
                <input type="text" name="Name" class="field type:text" placeholder="Enter an instruction name" />
           
           
                <label for="Type" class="field-label">Instruction Type</label>
                <select id="Type" name="Type" class="field type:select stretch">
                    /* removing for brevity */
                </select>
           
       
       
           
                <label class="field-label">Minimum Number</label>
                <input type="number" name="MinNumber" class="field type:number" />
           
           
                <label class="field-label">Maximum Number</label>
                <input type="number" name="MaxNumber" class="field type:number" />
           
       
   
   
       
            Add Step
       
   
   
       
            <input type="submit" value="Submit Procedures" class="button button-submit" />
       
   
</form>

<template id="template">
    <details class="accordion" open>
        <summary>Procedure Step</summary>
       
           
               
                    <label class="field-label">Instruction Name</label>
                    <input type="text" name="Name" class="field type:text" placeholder="Enter an instruction name" />
               
               
                    <label for="Type" class="field-label">Instruction Type</label>
                    <select id="Type" name="Type" class="field type:select stretch">
                        /* removing for brevity */
                    </select>
               
           
           
               
                    <label class="field-label">Minimum Number</label>
                    <input type="number" name="MinNumber" class="field type:number" />
               
               
                    <label class="field-label">Maximum Number</label>
                    <input type="number" name="MaxNumber" class="field type:number" />
               
           
       
    </details>
</template>

@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }


    const modal = new Modal();

    function getParent(element, selector) {
        for (; element && element !== document; element = element.parentNode) {
            if (element.classList.contains(selector)) {
                return element;
            }
        }
        return null;
    }

    this.Builder = function () {
        this.template = document.querySelector('#template');
        this.builder = document.querySelector('.builder');
        this.addStep = document.querySelector('.add-step');
        this.clone = null;
        this.counter = 0;
       
        this.addStep.addEventListener('click', this.add.bind(this));
    };

    Builder.prototype = {
        add: function () {
            this.counter++;
            this.clone = document.importNode(this.template.content, true);
            this.builder.appendChild(this.clone);

            // i'm doing things here to set the new field attribute values - removed for brevity
        }
    };

    let builder = new Builder();

    builder.add();


}

我是否需要在 javascript 中以特定方式设置新字段 name 值?它们应该看起来像 <input type="text" name="Name[index]"> 还是 <input type="text" name="@Model.Procedure[index].Name">

我都尝试了,但回发时计数仍然为 0。我几乎碰壁了,所以我很感激任何和所有的帮助。


.NET 对复杂类型的模型绑定需要更多的努力。元素的命名很重要。

1
2
3
4
<input name="model[index].Name" />
<input name="model[index].Type" />
<input name="model[index].MinNumber" />
<input name="model[index].MaxNumber" />

要绑定的命名约定是:控制器(模型)中的参数名称、索引、属性名称。这会将所有对象正确传递到您的列表。

如果您将对象传递给模型内的列表,情况也是如此。
例如

1
2
3
4
public class Example {
    public int Id { get; set; }
    public List<Procedure> Procedures { get; set; }
}

在这种情况下,我们只需要告诉 .NET 你也在映射什么属性。

1
<input name="model.Procedures[index].Name" />

在某些情况下,您可能还需要为索引添加隐藏字段:https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core /