Click here to Skip to main content
15,885,782 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have a Model that includes two tables, linked in a one to many relationship: Recipe and Ingredients.

If my Blazor Page displays only elements from the Recipe class, it works fine.

But if I add a loop to display the Ingredients, I get "An unhandled error has occurred. Reload"

This uses
Visual Studio 2019
.Net Core EntityFramework 5.0
The Server project uses .Net Core 3.1
The Client project uses .Net Core 2.1
The Shared project uses .Net Core 2.1

What I have tried:

This is my Model
using System;
using System.Collections.Generic;
using System.Text;

namespace jimrolph.Shared
{
    public class Recipe
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Instructions { get; set; }

        public virtual ICollection<Ingredient> Ingredients { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace jimrolph.Shared
{
    public class Ingredient
    {
        public int Id { get; set; }
        public string Amount { get; set; }
        public string Name { get; set; }
    }
}


This is the controller code to fetch a recipe by Id. It is set to include the ICollection of Ingredients. When I inspect the recipe (with a Debug break at Return), It shows that recipe does include include the Ingredients. In my test, there were two Ingredients present.
// Read - Get by Id
        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {

            var recipe = await _context.Recipes
                .Include(a => a.Ingredients)
                .FirstOrDefaultAsync(a => a.Id == id);
            return Ok(recipe);
        }


When run using the Blazor page below, I get the message: An unhandled error has occurred. Reload

<EditForm Model="@recipe" OnValidSubmit="@OnValidSubmit">
    <DataAnnotationsValidator />
    <div class="form-group">
        <label>Name :</label>
        <div>
            <InputText @bind-Value="@recipe.Name" />
            <ValidationMessage For="@(() => recipe.Name)" />
        </div>
    </div>
    <div class="form-group ">
        <div>
            <label>Instructions :</label>
            <div>
                <InputText @bind-Value="@recipe.Instructions" />
                <ValidationMessage For="@(() => recipe.Instructions)" />
            </div>
        </div>
    </div>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>Ammount</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (Ingredient ingredient in recipe.Ingredients)
            {
                <tr>
                    <td>@ingredient.Id</td>
                    <td>@ingredient.Amount</td>
                    <td>@ingredient.Name</td>
                </tr>
            }
        </tbody>
    </table>

    <button type="submit" class="btn btn-success">
        @ButtonText
    </button>

</EditForm>


@code {
    [Parameter] public Recipe recipe { get; set; }
    [Parameter] public string ButtonText { get; set; } = "Save";
    [Parameter] public EventCallback OnValidSubmit { get; set; }
}

When I only change the Blazer Page by removing the Ingredient loop, there is no error.
The specified properties of Recipe display properly without the Ingredient elements.
This is the loop that was removed.
@foreach (Ingredient ingredient in recipe.Ingredients)
{
    <tr>
       <td>@ingredient.Id</td>
       <td>@ingredient.Amount</td>
       <td>@ingredient.Name</td>
    </tr>
}
Posted
Updated 25-Dec-20 7:43am
Comments
Richard Deeming 10-Dec-20 3:56am    
You'll need to debug your code and get the full details of the error before you can start trying to diagnose what's causing it.
[no name] 10-Dec-20 11:06am    
I think you choked the runtime awating an unrealized LINQ query that is maybe also doing lazy loading. Try synchronous before async.
Jim Rolph 12-Dec-20 16:53pm    
Thank you, Richard and Gerry.
I am kind of new to debugging WASMs, but I have found....
1. By placing a break at the return statement of the Get By Id routine in the controller, examination of the recipe class being returned show it populated with an Ingredient collection with two Ingredients, each populated.

2. There is an Edit.razor page, that requests the Recipe object from Get by Id routine in the controller.

Another break in the Edit page that retrieves the recipe object. Examination of the recipe object after it is received by the WASM shows it populated with Ingredients - containing two Ingredients with a count of two and containing two items populated properly.

3. The Edit page then invokes a shared Forms page (where the error occurs) using the Recipe object as a [Parameter].
The shared Forms page runs fine without the ingredients Foreach loop.

But add the Foreach loop for ingredients, with no other changes, and it generates the unhandled exception error in the WASM.

When I try to step through the activity in the Form page (in Visual Studio), it eventually pops up a tab that says Frame not in Module. That may be a red herring, but the search goes on.

I am wondering if there are issues of passing a eager loading object as a Parameter.

1 solution

Thank you again to Richard and Gerry.

After additional research, I found that this issue was addressed in stackoverflow.

Thanks to GAC for asking the question and to enet for answering.

I will try to summarize the answer, but you should check out the original at
.net core - Error in foreach loop when displaying an ICollection of a one-to-many data model - Stack Overflow[^]

The problem was in the Edit page (sorry, it was not included in the problem statement).
In the Edit page, the recipe class was
o defined AND instantiated,
o retrieved from the server via call to http.GetFromJsonAsync,
o and passed to the EditForm component.

Between retrieving the recipe class and passing the parameter, the ICollection was lost.

What is needed was to change the definition of the recipe class to OMIT instantiating it to null. It is a quirk of the rendering of the Blazor page. See the code below

@page "/recipe/edit/{recipeId:int}"
@inject HttpClient http
@inject NavigationManager uriHelper
@inject IJSRuntime js

<h3>Edit</h3>

<Form ButtonText="Update" recipe="recipe"
      OnValidSubmit="@EditRecipe" />

@code {
    [Parameter] public int recipeId { get; set; }
    // Recipe recipe = new Recipe();     // If this statement is used, recipe is
                                            // instantiated (TO NULL) and this 
                                            // causes the parameter to 
                                            // @EditRecipe to not pass the 
                                            // Ingredient ICollection
    
    Recipe recipe;                          // This declares, but does not 
                                            // instantiate recipe as null
                                            // This allows recipe to be 
                                            // instantiated in the
                                            // OnParametersSetAsync 
                                            // call.  IT WORKS

    protected async override Task OnParametersSetAsync()
    {
        recipe = await http.GetFromJsonAsync<Recipe>($"api/recipe/{recipeId}");
    }

    async Task EditRecipe()
    {
        await http.PutAsJsonAsync("api/recipe", recipe);
        await js.InvokeVoidAsync("alert", $"Updated Successfully!");
        uriHelper.NavigateTo("recipe");

    }
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900