2

ASP.NET Core 7 MVC - view model's property is not being sent back to the server.

I have two entities that have a many-to-many relationship between them:

public class Trip
{
    [Key]
    public Guid TripId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    [ValidateNever]
    public IEnumerable<Tag> Tags { get; set; }
}

public class Tag
{
    [Key]
    public Guid TagId { get; set; }
    public string Name { get; set; }

    [ValidateNever]
    public IEnumerable<Trip> Trips { get; set; }
}

When I create a new Trip, I want to be able to select multiple tags using checkboxes. For this I created two view models:

public class SelectTagViewModel
{
    public string TagId { get; set; }
    public string TagName { get; set; }
    public bool IsSelected { get; set; } = false;
}

public class CreateTripViewModel
{
    public Trip Trip { get; set; }
    [ValidateNever]
    public List<SelectTagViewModel> Tags { get; set; }
}

This is the controller:

public class TripController : Controller
{
    private readonly ITripRepository _tripRepository;
    private readonly ITagRepository _tagRepository;

    public TripController(ITripRepository tripRepository, ITagRepository tagRepository)
    {
        _tripRepository = tripRepository;
        _tagRepository = tagRepository;
    }

    public async Task<IActionResult> Index()
    {
        var trips = await _tripRepository.GetAllTripsAsync(null, "Tags");
        return View(trips);
    }

    public async Task<IActionResult> Create()
    {
        List<SelectTagViewModel> tagsList = (await _tagRepository.GetAllTagsAsync(null)).Select(
            u => new SelectTagViewModel
            {
                TagId = u.TagId.ToString(),
                TagName = u.Name
            }).ToList();

        CreateTripViewModel viewModel = new()
        {
            Trip = new(),
            Tags = tagsList
        };

        return View(viewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(CreateTripViewModel createTripViewModel)
    {
        if (ModelState.IsValid)
        {
            await _tripRepository.AddTripAsync(createTripViewModel.Trip);

            return RedirectToAction(nameof(Index));
        }
        return View(createTripViewModel);
    }
}

And this is the Create action's view:

@using TestApplication.ViewModels;
@model CreateTripViewModel

<div class="card shadow border-0 mt-4" style="margin-left:20vw; margin-right:20vw;">
    <div class="card-header bg-primary m-lg-0 py-3">
        <div class="row">
            <div class="col-12 text-center">
                <h2 class="text-white py-2">Create Trip</h2>
            </div>
        </div>
    </div>
    <div class="card-body p-4">
        <form method="post" class="row px-5" asp-action="Create">
            <div class=" d-flex justify-content-center">
                <div class="px-5 col-md-8">
                    <label asp-for="Trip.Name" class="form-label mt-4"></label>
                    <span asp-validation-for="Trip.Name" class="text-danger"></span>
                    <input asp-for="@Model.Trip.Name" class="form-control" />
                </div>
            </div>
            <div class=" d-flex justify-content-center">
                <div class="px-5 col-md-8">
                    <label asp-for="Trip.Description" class="form-label mt-4"></label>
                    <span asp-validation-for="Trip.Description" class="text-danger"></span>
                    <textarea asp-for="@Model.Trip.Description" class="form-control"></textarea>
                </div>
            </div>
            <div class="d-flex justify-content-around  py-5">
                <div class="row row-cols-1 row-cols-md-4">
                                        <input asp-for="@Model.Tags" type="hidden" />
                    @foreach (var item in Model.Tags)
                    {
                        <div class="col py-2">
                            <input asp-for="@item.IsSelected" type="checkbox" />
                            <input asp-for="@item.TagName" type="hidden" />
                            <input asp-for="@item.TagId" type="hidden" />
                            <label class="form-label mt-4">@item.TagName</label>
                        </div>
                    }
                </div>
            </div>
            <hr class="my-5" />
            <div class="row pt-2">
                <div class="col-12 col-md-3">
                    <button type="submit" class="btn btn-primary form-control">Create</button>
                </div>
                <div class="col-12 col-md-3">
                    <a asp-controller="Trip" asp-action="Index" class="btn btn-outline-primary border  form-control">
                        Back to List
                    </a>
                </div>
            </div>
        </form>
    </div>
</div>

@section Scripts{
    @{
        <partial name="_ValidationScriptsPartial" />
    }
}

Everything works fine, the only problem is that I don't get the SelectTagViewModel list back to the server.

I know that it's probably a silly mistake, but I've been trying to figure out why it doesn't work for hours and I'm in a rush.

Thank you for any answers in advance! :)

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Andrew
  • 25
  • 3

2 Answers2

0

This is how i use.

View code:

<div class="form-group">
    <label asp-for="Input.JuryApplicationViewModel.Expertise" class="form-label"></label>
    <ul class="custom-control-group g-3 align-center">
        @foreach (var item in Model.Input.CategorySelectList)
        {
            <li>
                <div class="custom-control custom-control-sm custom-checkbox">
                    <input type="checkbox" class="custom-control-input" name="Input.JuryApplicationViewModel.Expertise" id="@item.Value" value="@item.Value" @(item.Selected ? "checked" : "")>
                    <label class="custom-control-label" for="@item.Value">@item.Text</label>
                </div>
            </li>
        }
    </ul>
</div>  

             

Controller code

public class InputModel
{
    public InputModel()
    {
        this.CategorySelectList = new List<SelectListItem>();
        this.JuryApplicationViewModel = new JuryApplicationViewModel();
        this.OperationType = OperationTypeEnum.Add;
        this.Result = null;
        this.ReferrerUri = String.Empty;
    }

    // property for displaying categories
    public List<SelectListItem> CategorySelectList { get; set; }

    public JuryApplicationViewModel JuryApplicationViewModel { get; set; }

    public OperationTypeEnum OperationType { get; set; }

    public Result? Result { get; set; }

    public string ReferrerUri { get; set; }
}
Ozan BAYRAM
  • 2,780
  • 1
  • 28
  • 35
0

You just need to change your For in the view section

This is Correct Code

 @* <input asp-for="@Model.Tags" type="hidden" />*@
                    @for (int i = 0; i < Model.Tags.Count;i++)
                    {
                        <div class="col py-2">

                            <input asp-for="@Model.Tags[i].IsSelected" type="checkbox" />
                            <input asp-for="@Model.Tags[i].TagName" type="hidden" />
                            <input asp-for="@Model.Tags[i].TagId" type="hidden" />
                            <label class="form-label mt-4">@Model.Tags[i].TagName</label>

                        </div>
                    }


Your code(This code is wrong)

       <input asp-for="@Model.Tags" type="hidden" />
                    @foreach (var item in Model.Tags)
                    {
                        <div class="col py-2">
                            <input asp-for="@item.IsSelected" type="checkbox" />
                            <input asp-for="@item.TagName" type="hidden" />
                            <input asp-for="@item.TagId" type="hidden" />
                            <label class="form-label mt-4">@item.TagName</label>
                        </div>
                    }

abolfazl sadeghi
  • 2,277
  • 2
  • 12
  • 20