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.