0

I want to register an unknown number of users, and would like to avoid having to do a separate form submit per user.

I think I have figured out the view, where I can add and remove rows of users (Thanks to this article).

But I can't seem to get it to bind to the controller POST method. Users are returning null.

Update

Adding [Bind("Users")] to the controller method fixed the null problem! Now I'm struggeling with the indecies of the rows.

Update 2

Solved! Adding <input type="hidden" name="Users.Index" value="{some unique value}" /> sorts out the indexing problem. I can now consider the index to be non-sequential, and not worry about maintaining the sequence. I have replaced the lastIndex introduced in the answer below, with a timestamp. It could be any random number, as long as it is unique to each row in the form.

This is what I have now:

Models

public class ApplicationUserViewModel
{
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class BatchUserRegistrationViewModel
{
    public List<ApplicationUserViewModel> Users { get; set; }
}

Create view

@model NameSpace.Models.BatchUserRegistrationViewModel

<form asp-action="RegisterManyUsers">
    @if (Model.Users != null)
    {
        // This part is for when the ModelState is not valid, and POST redirects back to the view.
        // I want the submitted form data to persist.
        for (int i = 0; i < Model.Users.Count; i++)
        {
            <div id="inputFormRow">
                <div class="input-group mb-3">
                    <input type="hidden" name="Users.Index" value="@i" />
                    <input type="text" name="users[@i].Email">
                    <input type="text" name="users[@i].FirstName>
                    <input type="text" name="users[@i].LastName">
                    <div class="input-group-append">
                        <button id="removeRow" type="button">
                            (-) Remove row
                        </button>
                    </div>
                </div>
            </div>
        }
    }
    else
    {
        // This part is for a new, blank form:
        <div id="inputFormRow">
            <div class="input-group mb-3">
                <input type="hidden" name="Users.Index" value="0" />
                <input type="text" name="users[0].Email">
                <input type="text" name="users[0].FirstName">
                <input type="text" name="users[0].LastName">
                <div class="input-group-append">
                    <button id="removeRow" type="button">
                        (-) Remove row
                    </button>
                </div>
            </div>
        </div>
    }
    <div id="newRow"></div>
    <p>
        <button id="addRow" type="button">
            (+) Add row
        </button>
    </p>
    <p>
        <button type="submit">(v) Register users</button>
    </p>
</form>

Client script

<script>
    // add row
    $("#addRow").click(function () {
        let ts = $.now();
        var html = '';
        html += '<div id="inputFormRow">';
        html += '    <div class="input-group mb-3">';
        html += '        <input type="hidden" name="Users.Index" value="' + ts + '" />';
        html += '        <input type="text" name="users[' + ts + '].Email">';
        html += '        <input type="text" name="users[' + ts + '].FirstName">';
        html += '        <input type="text" name="users[' + ts + '].LastName">';
        html += '        <div class="input-group-append">';
        html += '            <button id="removeRow" type="button">';
        html += '                (-) Remove row';
        html += '            </button>';
        html += '        </div>';
        html += '    </div>';
        html += '</div>';
        $('#newRow').append(html);
    });

    // remove row
    $(document).on('click', '#removeRow', function () {
        $(this).closest('#inputFormRow').remove();
    });
</script>

Controller POST-method

public async Task<IActionResult> RegisterManyUsers([Bind("Users")] BatchUserRegistrationViewModel BatchReg)
{
    if (ModelState.IsValid)
    {
        if (BatchReg.Users != null)
        {
            List<ApplicationUser> dbUsers = new();
            foreach (ApplicationUserViewModel user in BatchReg.Users)
            {
                dbUsers.Add(new ApplicationUser {
                    Email = user.Email,
                    FirstName = user.FirstName,
                    LastName = user.LastName
                });
            }
            db.AddRange(dbUsers);
            await db.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    return View(BatchReg);
}
2
  • the name of the row you are adding is incorrect, it should be users[0].Email Commented Oct 20, 2021 at 7:30
  • you can achieve this by defining a global variable in scripts Commented Oct 20, 2021 at 7:31

1 Answer 1

1

try this :

<script>
let lastIndex= 0 ;
// add row
$("#addRow").click(function () {
    var html = '';
    html += '<div id="inputFormRow">';
    html += '    <div class="input-group mb-3">';
    html += '        <input type="text" name="users['+lastIndex+'].Email">';
    html += '        <input type="text" name="users['+lastIndex+'].FirstName">';
    html += '        <input type="text" name="users['+lastIndex+'].LastName">';
    html += '        <div class="input-group-append">';
    html += '            <button id="removeRow" type="button">';
    html += '                (-) Remove row';
    html += '            </button>';
    html += '        </div>';
    html += '    </div>';
    html += '</div>';
    lastIndex=lastIndex+1;
    $('#newRow').append(html);
});

// remove row
$(document).on('click', '#removeRow', function () {
    lastIndex=lastIndex-1;
    $(this).closest('#inputFormRow').remove();
});
Sign up to request clarification or add additional context in comments.

6 Comments

Well, you could be right, but it seems that this is not about what the OP asked.
@steve well i think the logic is ok, it's just it sends null because of wrong indexing
The OP complains about POST the whole model as null not a single row. IE the block before the one you are fixing here.
@fatemehyadollahzadeh This is almost working. Users are stored in the database now. But there is a conflict or mis-match between the index[0] which is in the DOM and the indecies created by the script. If I remove the first row and then submits a list of some rows, the first row is not received by the controller. If I don't remove the first row and just submits that one row, it is received by the controller.
@Steve I fixed the null problem. See updated question.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.