Search code examples
c#asp.net-core-mvcasp.net-mvc-viewmodelasp.net-core-7.0

Why isn't my view model's property being sent back to the server?


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! :)


Solution

  • 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>
                        }