Search code examples
c#.net-coreasp.net-core-mvcmodelstate

.NET Core, Invalid ModelState on AllowNull Navigational Properties on Simple Product & Categories Model


There is one to many relationship between categoreis and products. I am trying to create a new category or edit a category using default controller and views provided by Entity Framework (Scaffolding).

Whenever I try to create/edit a category, the model state is getting invalid. This is happening with productContoller as well.

Any suggestions or leads are welcome !!!

Thank you for your time...

My Model:

public class Product
{
    [Key]
    public int ProductId { get; set; }

    // Foreign Key
    [ForeignKey("CategoryId")]
    public int CategoryID { get; set; }

    public Categories Category { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }
    public string url { get; set; }
    public decimal Price { get; set; }
}

public class Categories
{
    [Key]
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public string url { get; set; }

    // Navigation Property for Products (optional but recommended)
    [AllowNull]
    public ICollection<Product> Products { get; set; }
}
// In your Models folder (or create a ViewModels folder)
public class ProductsCategoriesViewModel
{
    public List<Categories> Categories { get; set; }
    public List<Product> Products { get; set; }
}

Default CreateController (Using Scaffolding):

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("CategoryID,CategoryName,url")] Categories categories)
        {
            if (ModelState.IsValid)
            {
                _context.Add(categories);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(categories);
        }

Create.cshtml:

@model MyKartV2.Models.Categories

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Categories</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="CategoryName" class="control-label"></label>
                <input asp-for="CategoryName" class="form-control" />
                <span asp-validation-for="CategoryName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="url" class="control-label"></label>
                <input asp-for="url" class="form-control" />
                <span asp-validation-for="url" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Edit: Even though My form is submitting only 2 fields i.e. CategoryName and url, why is the ModelState even checking for Products navigation property.

The modelState is getting invalid because it is adding an entry for navigation property (Products) in the ModelState. The error I am getting is "The Products field is required."

ModeState Entries


Solution

  • From the current model definitions .NET has no idea which of the properties are Navigational properties. And also depending on the .NET version you are using nullability might be enforced. So you would need to specify that certain properties can be null.

    It is usually done by using the virtual keyword when defining the navigation properties. As for the nullability you will need to add a ? after the object definition to specify that the navigational properties can be null when creating an instance of that class.

    Your model definitions should look something like this:

    public class Product
    {
        [Key]
        public int ProductId { get; set; }
    
        // Foreign Key
        [ForeignKey("CategoryId")]
        public int CategoryID { get; set; }
    
        public virtual Categories? Category { get; set; }
    
        public string Name { get; set; }
        public string Description { get; set; }
        public string url { get; set; }
        public decimal Price { get; set; }
    }
    
    public class Categories
    {
        [Key]
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public string url { get; set; }
    
        // Navigation Property for Products (optional but recommended)
        public virtual ICollection<Product>? Products { get; set; }
    }