Mihail Gerginov
Mihail Gerginov

Reputation: 41

ASP.NET Core - Bind IEnumerable<T> to a ViewModel field on POST

I have a web application for registering teams to a competition, where each team can select a number of technologies that they will use for their project. The technologies are saved in a Label class.

I am using a view model to bind the information from the form to the action. However, when I try to submit the form, it takes all other fields, except the list of technologies.

Label.cs

public class Label
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string ColorPalette { get; set; }
}

CreateTeamViewModel.cs

public class CreateTeamViewModel
{
    [Required]
    public string TeamName { get; set; }

    public string ProjectName { get; set; }

    public string ProjectDescription { get; set; }

    [Required]
    public string RepositoryLink { get; set; }

    public List<Label> Labels = new List<Label>();
}

TeamsController.cs

public class TeamsController
{
    private readonly ApplicationDbContext context;

    public IActionResult Create()
    {
        ViewData["Labels"] = this.context.Labels.ToList();
        return View();
    }

    [HttpPost]
    public IActionResult Create(CreateTeamViewModel team)
    {
        List<Label> labels = team.Labels;
        int count = labels.Count; // count = 0
        return LocalRedirect("/");
    }
}

Create.cshtml (the list of checkboxes)

@model Competition.Data.ViewModels.CreateTeamViewModel

@{ 
    List<Label> labels = ViewData["Labels"] as List<Label>;
}

<form asp-action="Create">
    <div class="form-check">
        @for(int i = 0; i < labels.Count; i++)
        {
            <input asp-for="@Model.Labels[i].IsSelected" type="checkbox" />
            <label asp-for="@Model.Labels[i].Name">
                <span class="badge badge-@labels[i].ColorPalette">@labels[i].Name</span>
            </label>
            <input asp-for="@Model.Labels[i].Name" type="hidden" value="@labels[i].Name" />
            <input asp-for="@Model.Labels[i].ColorPalette" type="hidden" value="@labels[i].ColorPalette" />
        }
    </div>
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-default" />
    </div>
</form>

Upvotes: 2

Views: 2303

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239280

You need to bind to a list of int instead of a list of Label on your view model. Then, you'll need to use that list of selected ids to fill your list of labels on the Team entity you're persisting:

public class CreateTeamViewModel
{
    [Required]
    public string TeamName { get; set; }

    public string ProjectName { get; set; }

    public string ProjectDescription { get; set; }

    [Required]
    public string RepositoryLink { get; set; }

    public List<int> SelectedLabels { get; set; } = new List<int>();
}

Then, you'll need to modify your form to bind your checkboxes to this list:

@foreach (var label in labels)
{
    <input asp-for="SelectedLabels" id="Label@(label.Id)" value="@label.Id" type="checkbox" />
    <label id="Label@(label.Id)">
        <span class="badge [email protected]">@label.Name</span>
    </label>
}

Notice that I removed the hidden inputs. You should never post anything that the user should not be able to modify, as even hidden inputs can be tampered with.

After posting, server-side you'll end up with a list of label ids that were selected by the user. Simply query the associated labels out of your database and then assign that to the team you're creating:

team.Labels = await _context.Set<Label>().Where(x => model.SelectedLabels.Contains(x.Id)).ToListAsync();

Upvotes: 2

Related Questions