0

I have created a form that edits a list of notes. The form displays the Id of each note along with the message it contains. This works.

The problem is that after changing the messages in these notes and submitting the changes, the form is sent but I receive an empty list of models from the HttpPost action-method parameter.

I looked through many similar questions but a common problem was that the view model did not contain public properties. Mine does. I can't see where the problem is. I am a beginner to programming so I apologise if the problem is too obvious.

// My View Model

public class NoteViewModel
{
    public int Id { get; set; }
    public string Message { get; set; }
}

// My Post Action method

[HttpPost]
public IActionResult EditNotes(List<NoteViewModel> model)
{   
    foreach (var item in model)
    {
        // Create a note, and copy values from model
        Note note = new Note
        {
            Id = item.Id,
            Message = item.Message
        };

        // Update note in database.
        noteRepository.Update(note);
    }
    return RedirectToAction("NotePage", "Home");
}

// My View EditNote.cshtml

@model List<MyWebsite.ViewModels.NoteViewModel>

<form asp-action="EditNotes" method="post">
    @foreach (var note in Model)
    {
        <label asp-for="@note.Id">@note.Id</label>
        <label asp-for="@note.Message">Message</label>
        <input asp-for="@note.Message" value="@note.Message" />
    }
    <button type="submit" class="btn btn-success">Submit</button>
</form>

I expect to receive a list of models that contain the notes, but I receive an empty list here

public IActionResult EditNotes(List<NoteViewModel> model)
{
    // model is empty
    // model.Count() gives 0.
}
Sea_Ocean
  • 309
  • 3
  • 8

2 Answers2

2

This:

@model List<MyWebsite.ViewModels.NoteViewModel>

<form asp-action="EditNotes" method="post">
    @foreach (var note in Model)
    {
        <label asp-for="@day.Id">@note.Id</label>
        <label asp-for="@note.Message">Message</label>
        <input asp-for="@day.Message" value="@day.Message" />
    }
    <button type="submit" class="btn btn-success">Submit</button>
</form>

Needs to be this:

@model List<MyWebsite.ViewModels.NoteViewModel>

<form asp-action="EditNotes" method="post">
    @for( Int32 i = 0; i < this.Model.Count; i++ ) {
        <label>@Model[i].Id</label>
        <input asp-for="@Model[i].Id" type="hidden" />
        <label asp-for="@Model[i].Message">Message</label>
        <input asp-for="@Model[i].Message" />
    }
    <button type="submit" class="btn btn-success">Submit</button>
</form>

This is because ASP.NET Core MVC's model-binding uses Expression<Func<TModel,TProperty>> for generating the name="" attributes, which means it needs a full expression to get from TModel to the property it's bound to.

The tag-helper will also generate the value="" attribute for you, you don't need to specify it manually. Ditto the <label>'s text too (especially if you use the [Display] or [DisplayName] attributes on the model properties.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • 1
    I'd suggest to add a hidden input for the `Id` or it will always be zero when posted – haldo Oct 20 '19 at 23:44
  • OMG this worked :D Thank you so much for your effort and the explanation you gave. I was stuck on this problem for hours. I could not have done it without your help. I only changed "Model.Length" to "Model.Count" because it gave me compilation error. Seriously I cannot thank you enough – Sea_Ocean Oct 21 '19 at 03:21
  • @Sea_Ocean if this worked for you, and solved the problem, then please mark this as the accepted answer. – Brendan Green Oct 21 '19 at 06:15
  • @BrendanGreen I didn't know I could that. I ticked it now. Thanks for letting me know. – Sea_Ocean Oct 22 '19 at 23:33
0

You should iterate with a for block at the view side. For example see this example:

<form asp-action="EditBulk" , method="post">
    <table class="table">
        <thead>
            <tr>
                <th>
                    <label id="noteTextLbl">Note Text</label>
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.Count; i++)
            {
                <tr>
                    <td>
                        <input asp-for="@Model[i].NoteId" type="hidden" />
                        <input asp-for="@Model[i].NoteText" />
                    </td>
                    <td>
                        <a asp-action="Edit" asp-route-id="@Model[i].NoteId">Edit</a> |
                        <a asp-action="Details" asp-route-id="@Model[i].NoteId">Details</a> |
                        <a asp-action="Delete" asp-route-id="@Model[i].NoteId">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <button type="submit">Send changes</button>
</form>

I don't know why, but this just works.

Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
Fran
  • 181
  • 8
  • Thank you Fran. I opted for the other answer because it worked without having to refactor my code. I will investigate this further because I still don't understand why yours and his work but mine doesn't. Thank you again – Sea_Ocean Oct 21 '19 at 03:27
  • @Sea_Ocean I explained that you need to provide the "full path" from the root `Model` object to the value - hence why you need to supply the indexer `[i]` and cannot use a `foreach` because otherwise the model-binder doesn't know *which* element of the `List` each item refers to. – Dai Oct 21 '19 at 03:45
  • Thank you Dai. It's still a little cryptic for me but I am learning as I go along. There are some concepts I haven't grasped yet so I need time to go over them. Take care – Sea_Ocean Oct 21 '19 at 04:17
  • @Sea_Ocean If you look at the **rendered HTML** you'll see what I'm referring to: look at the `name=""` attributes that ASP.NET generates for you. – Dai Oct 21 '19 at 06:18
  • @Dai Yeah I did that this morning after watching a video tutorial by kudvenkat in which he explains the foreach problem. He explained it by showing the HTML rendering. – Sea_Ocean Oct 22 '19 at 23:36