Search code examples
c#asp.net-mvcasp.net-coremodel-view-controller

HttpPost and Model Binding, what am I missing?


I have 2 tables/classes: Films and Actors, and intermediate table relating them. I'm trying to generate a new record into the into the intermediate table to generate a new relationship and add a new film to an actor's filmography.

Here's the ViewModel class:

    public class ActorsUpdateFilmsViewModel
    {
        public Actor actor { get; set; }

        public IQueryable<Film> bindedFilms { get; set; }

        [DisplayName("Title")]
        public int FilmId { get; set; }

        public int ActorId;
    }

The view where you can choose an unrelated film from a combo box:

<div class="right">
    <div class="right">
        <h3>Add a film:</h3>
        <form asp-action="Bind">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="FilmId" class="control-label"></label>
                <select asp-for="FilmId" class="form-control" asp-items="ViewBag.Unbinded"></select>
                <span asp-validation-for="FilmId" class="text-danger"></span>
            </div>
            <input type="hidden" asp-for="actor.ActorId" />
            <input type="hidden" asp-for="@Model.actor" />
            <input type="hidden" asp-for="@Model.bindedFilms" />
            <div class="form-group">
                <input type="submit" value="Bind" class="btn btn-success" />
            </div>
        </form>
    </div>
</div>

Then the controller which retrieves data:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Bind(int id, [Bind("actor,FilmId,bindedFilms, ActorId")]ActorsUpdateFilmsViewModel model) 
        {
            if (ModelState.IsValid)
            {
                if (model.FilmId != null)
                {
                    FilmsAndActor newBinding = new FilmsAndActor()
                    {
                        FilmIdFk = model.FilmId,
                        ActorIdFk = model.actor.ActorId
                    };

The newBinding item works perfectly for adding it to the intermediate table and bind the actor and the film. But the ModelState.IsValid returns a false. Why? What am I missing?

Thanks

NOTE: I have made a little test and all data is being retrieved and binded, except for the bindedFilms property. Tried to make it an IEnumerable/List, but none of these work. The .Any() function always returns a false


Solution

  • The problem is that you are not binding a corresponding value to @Model.actor.ActorName which is causing this property to receive null in the controller.

    You can add a breakpoint and look at the Values property under ModelState to find which model property is Invalid:

    enter image description here

    There are two solutions.

    The first one, add binding value to @Model.actor.ActorName in UpdateFilms.cshtml:

    //...
    <input type="hidden" asp-for="@Model.actor.ActorName" />
    //...
    

    The second one, remove the [Required] attribute and change the ActorName property in the Actor class to be nullable:

    [DisplayName("Actor name")]
    //[Required]
    public string? ActorName { get; set; } = null!;