简体   繁体   中英

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

Totally stumped on this. I have a page where users can click an "Add Step" button, and some javascript appends rows with additional inputs from a <template> tag to a form. When debugging from the controller, the values are empty.

I looked at the following posts, but neither answer seem to help here (not sure if it's because I'm using .Net core - probably not):

Get post data with unknown number of variables in ASP MVC

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

Model (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; }
}

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

Currently the Create action signature includes the [FromForm] attribute, but I have no idea if that's required here. I also tried List<Procedure> model .

And the View (Create.cshtml)

I've stripped out a lot of the UI and javascript to make things easier to read

@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>
}

In the javascript do I need to set the new field name values a specific way? Should they look like <input type="text" name="Name[index]"> or <input type="text" name="@Model.Procedure[index].Name"> ?

I tried both but the Count is still 0 when posting back. I've pretty much hit a wall, so I appreciate any and all help.

.NET requires a little more effort for model binding on complex types. Naming of elements is important.

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

The naming convention to bind is: Name of the parameter in your controller (model), index, property name. This will properly pass all objects to your list.

The same is true if you are passing objects to a list inside your model. For example

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

in this case we just need to tell .NET what property you are mapping too.

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

In some cases you may need to also add a hidden field for the index: https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM