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

Posted

技术标签:

【中文标题】ASP.Net Core MVC 如何针对强类型模型发布未知数量的字段【英文标题】:ASP.Net Core MVC how to post unknown number of fields against strongly-typed model 【发布时间】:2021-01-11 23:07:55 【问题描述】:

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

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

Get post data with unknown number of variables in ASP MVC

Passing data from Form with variable number of inputs - MVC 5

模型(Procedure.cs)

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)

[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&lt;Procedure&gt; model

还有视图(Create.cshtml

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

@model List<Procedure>
/* also tried @model Procedure */

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


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

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

@section Scripts 
@ await Html.RenderPartialAsync("_ValidationScriptsPartial"); 
<script>

    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();

</script>

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

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

【问题讨论】:

您是否尝试将[FromForm]List&lt;Procedure&gt; 更改为[FromForm]Procedure?因为您实际上向控制器提交了一个Proceduce,而不是列表。 我这样做了,但是在提交时我只收到一个过程的值,而不是多个。通过 javascript 添加的附加字段的语法是什么? 你能展示一下 POST 请求中的负载是什么样的吗?例如。在 Chrome 中,按 F12 并在“网络”选项卡下查看请求的负载。 看here:“当你加载它时它会为网页生成代码......当我通过JavaScript添加一行时,不会生成asp属性,所以输入不会具有正确的 ID 和属性..."。所以:1)[FromForm]List&lt;Procedure&gt; 是一个好的开始。 2) 在 Chrome 开发工具中研究实际的 HTML 有效负载。 3) 确保在动态 JS 中添加 same 字段/属性。 感谢@paulsm4 等等附加字段,假设名为Name 的字段,它看起来像&lt;input name="Name"&gt; 还是&lt;input name="Name[index]"&gt;?我想问题的症结在于实际的名称值。 【参考方案1】:

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

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

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

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

public class Example 
    public int Id  get; set; 
    public List<Procedure> Procedures  get; set; 

在这种情况下,我们只需要告诉 .NET 您还映射了哪些属性。

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

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

【讨论】:

好建议:+1。但是有很多我们都没有预料到的潜在“陷阱”。 OP肯定需要在 Chrome 开发者工具中自己检查细节。 这一行:“要绑定的命名约定是:控制器(模型)中的参数名称、索引、属性名称。这将正确地将所有对象传递到您的列表。”是答案。一旦我有了正确的name 属性语法,它就起作用了。非常感谢!

以上是关于ASP.Net Core MVC 如何针对强类型模型发布未知数量的字段的主要内容,如果未能解决你的问题,请参考以下文章

[MVC&Core]ASP.NET Core MVC 视图传值入门

ASP.NET MVC 强类型视图与否?

ASP.NET MVC 强类型部分视图,给出无法加载类型错误

asp.net mvc 视图和强类型视图数据

是否可以创建自定义 ASP.NET MVC 强类型 HTML Helper?

带有 DropDownList 的 ASP.NET MVC 强类型视图