asp.net-core-mvc

How to POST to a model that relies on joins / included data?


I am new to MVC and struggling a bit with understanding models and model binding, and having trouble finding this documented. When POSTing a change to this model I get a ModelState.IsValid = False on the Supplier and Category.

Model:

public class Products
{
    [Key]
    public int ProductID { get; set; }
    [Required]
    public string ProductName { get; set; } = string.Empty;
    [ForeignKey("Supplier")]
    public int SupplierID { get; set; }
    public Suppliers Supplier { get; set; }
    [ForeignKey("Category")]
    public int CategoryID { get; set; }
    public Categories Category { get; set; }
    public string? QuantityPerUnit { get; set; }
    public decimal UnitPrice { get; set; }
    public int UnitsInStock { get; set; }
    public int UnitsOnOrder { get; set; }
    public int ReorderLevel { get; set; }
    public string? Discontinued { get; set; }
}

My form is just auto-generated from the right click > Add > Controller > "with views, using Entity Framework", which looks like this:

<form asp-action="Edit">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <input type="hidden" asp-for="ProductID" />
    <div class="form-group">
        <label asp-for="ProductName" class="control-label"></label>
        <input asp-for="ProductName" class="form-control" />
        <span asp-validation-for="ProductName" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="SupplierID" class="control-label"></label>
        <select asp-for="SupplierID" class="form-control" asp-items="ViewBag.SupplierID"></select>
        <span asp-validation-for="SupplierID" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="CategoryID" class="control-label"></label>
        <select asp-for="CategoryID" class="form-control" asp-items="ViewBag.CategoryID"></select>
        <span asp-validation-for="CategoryID" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="QuantityPerUnit" class="control-label"></label>
        <input asp-for="QuantityPerUnit" class="form-control" />
        <span asp-validation-for="QuantityPerUnit" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="UnitPrice" class="control-label"></label>
        <input asp-for="UnitPrice" class="form-control" />
        <span asp-validation-for="UnitPrice" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="UnitsInStock" class="control-label"></label>
        <input asp-for="UnitsInStock" class="form-control" />
        <span asp-validation-for="UnitsInStock" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="UnitsOnOrder" class="control-label"></label>
        <input asp-for="UnitsOnOrder" class="form-control" />
        <span asp-validation-for="UnitsOnOrder" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ReorderLevel" class="control-label"></label>
        <input asp-for="ReorderLevel" class="form-control" />
        <span asp-validation-for="ReorderLevel" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Discontinued" class="control-label"></label>
        <input asp-for="Discontinued" class="form-control" />
        <span asp-validation-for="Discontinued" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input type="submit" value="Save" class="btn btn-primary" />
    </div>
</form>

But when POSTing the Supplier and Category both have a ValidationState = invalid in the debugger. My hunch is that I've built the model incorrectly, but I would appreciate any help here.

For reference the database I am using is the SQLite Northwind database available here: https://github.com/jpwhite3/northwind-SQLite3.


Solution

  • As This document said,

    Prior to .NET 6, new projects do not include the Nullable element. Beginning with .NET 6, new projects include the enable element in the project file.

    In .NET 6 the non-nullable property must be required, otherwise the ModelState will be invalid.

    So to resolve the issue easiest way is to set the property to the nullable:

    [ForeignKey("Supplier")]
           public int SupplierID { get; set; }
           public Suppliers? Supplier { get; set; }  // Nullable
    
           [ForeignKey("Category")]
           public int CategoryID { get; set; }
           public Categories? Category { get; set; }  // Nullable
    

    Or remove the <Nullable>enable</Nullable> from the csproj file.