Search code examples
c#jqueryasp.net-coreasp.net-core-mvc

ASP.NET Core MVC validations not being displayed on view for child entities


I'm experiencing the issue while displaying errors in my view for child element. I've got 2 entities Restaurant and Menu with their respective relationships described as below:

public class Restaurant
{
    public int ID { get; set; }

    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Foundation Date")]
    public DateTime FoundationDate { get; set; }

    [Required]
    public string Country { get; set; }

    [Required]
    [StringLength(50)]
    [Display(Name = "Restaurant Name")]
    public string RestaurantName { get; set; }

    [Required]
    [StringLength(100)]
    public string Address { get; set; }
 
    public ICollection<Menu> Menus { get; set; }
}


public class Menu
{
    public int MenuId { get; set; }

    [Required]
    [StringLength(20, MinimumLength=2)]
    public string Name { get; set; }

    [StringLength(50)]
    public string Description { get; set; }

    public int RestaurantId { get; set; }

    public Restaurant Restaurant { get; set; }

    public ICollection<Product> Products { get; set; }   
}

The issue is happening when I press the create button validations for menu (child entity) are not being displayed at all. I want to be able to capture validations next to my input fields after I submit the form.

Here is my div element which is nested inside a form for triggering create action on my controller.

<div id="menuFields" class="toggable-div" style="display: @ViewData["Visibility"]" data-count="0">
@if (Model?.Menus != null)
{
    @foreach (var (menu,i) in Model.Menus.Select((menu, index) => (menu,index)).ToArray())
    {
        <div class="form-group">
            <label asp-for="Menus[@i].Name">Menu Name</label>
            <input asp-for="Menus[@i].Name" type="text" class="form-control" id="menuName1" name="Menus[@i].Name" value="@menu.Name" />
            <span asp-validation-for="Menus[@i].Name" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Menus[@i].Description">Menu Description</label>
            <input asp-for="Menus[@i].Description" type="text" class="form-control" id="menuDescription@(i)" name="Menus[@i].Description" value="@menu.Description"/>
            <span asp-validation-for="Menus[@i].Description" class="text-danger"></span>

        </div>
    }
}
</div>

Solution

  • I think the issue is validation framework is unable to understand where the Menus is coming from. You should explicitly specify the @Model while accessing Menus and that should resolve the issue.

    So, in the CSHTML file, you can use @model.Menus[i] syntax. This will ensure that the validation framework understands that the Menus is nested and it should apply the nested validations.

    And my personal preference is to use List<Menu> instead of ICollection<Menu> in Restaurant model. This is to ensure you get some more ways to access the collection.

    <div id="menuFields" class="toggable-div" style="display: @ViewData["Visibility"]" data-count="0">
    @if (Model?.Menus != null)
    {
        @foreach (var (menu,i) in Model.Menus.Select((menu, index) => (menu,index)).ToArray())
        {
            <div class="form-group">
                <label asp-for="@Model.Menus[i].Name">Menu Name</label>
                <input asp-for="@Model.Menus[i].Name" type="text" class="form-control" id="menuName1" name="@Model.Menus[i].Name" value="@menu.Name" />
                <span asp-validation-for="@Model.Menus[i].Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="@Model.Menus[i].Description">Menu Description</label>
                <input asp-for="@Model.Menus[i].Description" type="text" class="form-control" id="menuDescription@(i)" name="@Model.Menus[i].Description" value="@menu.Description"/>
                <span asp-validation-for="@Model.Menus[i].Description" class="text-danger"></span>
    
            </div>
        }
    }
    
    </div>
    

    Hope this helps !