Home > Software engineering >  Model List returning as null from view to controller ASP.NET Core MVC
Model List returning as null from view to controller ASP.NET Core MVC

Time:09-19

I'm having trouble with a list within my model returning as null even tho in the view it clearly has some value.

I had trouble trying to add objects to my model's list, someone helped me and my problem was half solved. This is the code I came up with after the help

My view:

@model ActivityForm

@{
    ViewData["Title"] = "Activity Details";
}

<section >
    <div >
        <div >
            <h1 >Activity Details</h1>
        </div>
        <div >
            <div >
                <div >
                    <h1 >Form</h1>
                </div>
                <form id="form"  asp-action="Create">
                    <div >
                        <label asp-for="Name" >@Html.DisplayNameFor(model => model.Name)</label>
                        <input asp-for="Name" type="text"  id="inputEmail4"
                            placeholder="@Html.DisplayNameFor(model => model.Name)">
                        <span asp-validation-for="Name" ></span>
                    </div>
                    <div >
                        <label asp-for="Description" >@Html.DisplayNameFor(model =>
                            model.Description)</label>
                        <textarea asp-for="Description"  id="exampleFormControlTextarea1" rows="5"
                            placeholder="@Html.DisplayNameFor(model => model.Description)"></textarea>
                        <span asp-validation-for="Description" ></span>
                    </div>
                    <div >
                        <label asp-for="StartDate" >@Html.DisplayNameFor(model =>
                            model.StartDate)</label>
                        <input asp-for="StartDate" type="date" 
                            placeholder="@Html.DisplayNameFor(model => model.StartDate)">
                        <span asp-validation-for="StartDate" ></span>
                    </div>
                    <div >
                        <label asp-for="EndDate" >@Html.DisplayNameFor(model => model.EndDate)</label>
                        <input asp-for="EndDate" type="date" 
                            placeholder="@Html.DisplayNameFor(model => model.EndDate)">
                        <span asp-validation-for="EndDate" ></span>
                    </div>
                    <div >
                        <label asp-for="Points" >@Html.DisplayNameFor(model => model.Points)</label>
                        <input asp-for="Points" type="number" 
                            placeholder="@Html.DisplayNameFor(model => model.Points)">
                        <span asp-validation-for="Points" ></span>
                    </div>
                    <div >
                        <a  data-bs-toggle="modal" data-bs-target="#add-award">Add award</a>
                        <div >
                            <div >
                                <a  data-bs-toggle="modal"
                                    data-bs-target="#cancel-activity">Cancel</a>
                            </div>
                            <div >
                                <a  data-bs-toggle="modal"
                                    data-bs-target="#post-activity">Post Activity</a>
                            </div>
                        </div>
                    </div>

                    <div  id="add-award" tabindex="-1">
                        <div >
                            <div >
                                <div >
                                    <h5 >Award details</h5>
                                </div>
                                <div >
                                    <div >
                                        <div >
                                            <div >
                                                <label asp-for="Award.Name" >Name</label>
                                                <input asp-for="Award.Name" type="text" 
                                                    id="award-name">
                                            </div>
                                            <div >
                                                <label asp-for="Award.Description" for="inputAddress"
                                                    >Description</label>
                                                <textarea asp-for="Award.Description" 
                                                    id="award-description" rows="5"></textarea>
                                            </div>
                                        </div>
                                    </div>
                                    <div >
                                        <button type="button" 
                                            data-bs-dismiss="modal">Close</button>
                                        <input  type="submit" value="Confirm"></input>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </form>
            </div>
            <div >
                <div >
                    <h1 >Awards</h1>
                </div>
                <table >
                    <thead>
                        <tr>
                            <th scope="col">Award name</th>
                            <th scope="col">Description</th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                        @if (Model.Awards != null)
                        {
                            foreach (var item in Model.Awards)
                            {
                                <tr>
                                    <td>@item.Name</td>
                                    <td>@item.Description</td>
                                    <td>
                                        <a >Edit</a>
                                        <a >Remove</a>
                                    </td>
                                </tr>
                            }
                        }
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</section>

Method in controller:

        [HttpPost]
        public async Task<IActionResult> Create(ActivityForm data)
        {
            var award = data.Award;

            if (award.Name != null && award.Description != null)
            {
                if (data.Awards == null) data.Awards = new List<AwardForm>();

                data.Awards.Add(new AwardForm { Name = award.Name, Description = award.Description });

                data.Award.Name = "";
                data.Award.Description = "";

                return View(data);
            }


            if (!ModelState.IsValid)
            {
                return View(data);
            }

            string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);

            await service.NewActivityAsync(data, userId);
            return RedirectToAction(nameof(Index));
        }

Model

    public class ActivityForm
    {
        public string Name { get; set; }

        public string Description { get; set; }

        public DateTime StartDate { get; set; }

        public DateTime EndDate { get; set; }

        public int Points { get; set; }

        public AwardForm Award { get; set; }

        public List<AwardForm> Awards { get; set; }
    }

Everything was now working as intended but I had one more issue left. When I try to add another Award to the list, the list is returned to the controller method as null.

I'm not really sure if the issue is related to binding, I have noticed that every other value is bound and is returning the expected value except the list which is not bound.

CodePudding user response:

Change the foreach loop to:

<tbody>
    @if (Model.Awards != null)
    {
        @for (var i = 0; i < Model.Awards.Count; i  )
        {
            <input type="hidden" asp-for="@Model.Awards[i].Name" />
            <input type="hidden" asp-for="@Model.Awards[i].Description" />
            <tr>            
                <td>@Model.Awards[i].Name</td>
                <td>@Model.Awards[i].Description</td>
                <td>
                    <a >Edit</a>
                    <a >Remove</a>
                </td>
            </tr>
        }
    }
</tbody>

This has same effect to what @Victor suggested but using input tag helper instead of html helper.

Documentation for binding model to a collection: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#collections-1

Also your form should wrap around both Form and Awards sections:

<form id="form"  asp-action="Create">
    <div >
        <div >
            <h1 >Form</h1>
        </div>
        ...
    </div>
    <div >
        <div >
            <h1 >Awards</h1>
        </div>
        ...
    </div>
</form>

CodePudding user response:

The list binding context should include indexes to work properly.

Add to following code located below right after declaration of the <form> tag:

<form id="form"  asp-action="Create">

    @if (Model.Awards != null)
    {        
       for (int i=0; i < Model.Awards.Count; i  )
            {
                @Html.Hidden("Awards["   i   "].Name", Model.Awards[i].Name)
                @Html.Hidden("Awards["   i   "].Description", Model.Awards[i].Description)
            }
    }

    ... your markup and control that performs the submit are located here    

</form>

To be a part of the binding context these hidden fields should be declared inside the <form> from that the submit is performing.

  • Related