0

I am trying to figure out a sort of master/detail view of some simple data. I have a list of Recipes and each Recipe has a list of Ingredients.

I have a Recipe page which lists all recipes, and this is working well. I have a RecipeDetail razor component which is supposed to fetch the Ingredient List once a recipe is chosen from the Recipe page.

I've tried all kinds of things. I've tried data binding by passing the Recipe and its ingredients fully populated down to the RecipeDetail component, but due to the data structure, the GetJsonAsync method detects a circular reference and it throws an exception. I've tried passing the ID of the recipe chosen from the Recipe page down to the RecipeDetail page through a Parameter bound to a variable on the Recipe page, but the data fetch is never triggered.

@page "/recipes"
@using BlazorApp.Shared.Models
@inject HttpClient Http

<h1>Recipes</h1>

@if(RecipesArray != null)
{
    @foreach (var r in RecipesArray)
    {
        <h3 @onclick="@(() => SelectRecipe(r))">@r.Title</h3>
    }
}

<RecipeDetail RecipeId="@SelectedRecipeId"></RecipeDetail>

@code {
    Recipe[] RecipesArray;
    int SelectedRecipeId;

    protected override async Task OnInitializedAsync()
    {
        RecipesArray = await Http.GetJsonAsync<Recipe[]>("Recipes");
    }

    protected void SelectRecipe(Recipe recipe)
    {
        SelectedRecipeId = recipe.Id;
    }
}

The below technically works, but when I un-comment the fetch, it no longer works. The application freezes.

@using BlazorApp.Shared.Models
@inject HttpClient Http

<h1>RecipeId = @selectedRecipeId</h1>
@if (Ingredients != null && Ingredients.Length > 0)
{
    <h1>@RecipeId</h1>
    <ul>
        @foreach (var item in Ingredients)
        {
            <li>@item.Ingredient.ToString()</li>
        }
    </ul>
}


@code {
    private int selectedRecipeId;
    RecipeIngredient[] Ingredients;

    [Parameter]
    public int RecipeId
    {
        get { return selectedRecipeId; }
        set
        {
            selectedRecipeId = value;
            //if (value > 0)
            //    Ingredients = Http.GetJsonAsync<RecipeIngredient[]>($"Recipes/{RecipeId}").Result;
        }
    }
}

I'd be happy fixing the JSON error and just binding to an object passed down, but I couldn't find a solution to that either. I tried the attribute [JsonIgnore] but that was no help.

Even if the Json fetch was fixed, I think I still have a use case to re-fetch data after a parameter changed, so I'd like to understand what I'm doing wrong in both cases.

Kevon
  • 159
  • 1
  • 8
  • So, I tried the solution from Issac, and while it worked, it created a bit more coding overhead than I needed for the specific problem I was trying to solve. My actual solution was to modify the Json Serializer to ignore the circular references. On the server / api side... in startup.cs, method ConfigureServices... ``` services.AddMvc().AddNewtonsoftJson(z=> z.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); ``` – Kevon Aug 27 '19 at 18:58

2 Answers2

0

Try (I haven't tested this)...

@page "/recipes"
@using BlazorApp.Shared.Models
@inject HttpClient Http

<h1>Recipes</h1>

@if(RecipesArray != null)
{
    @foreach (var r in RecipesArray)
    {
        <h3 @onclick="@(() => SelectRecipe(r))">@r.Title</h3>
    }
}

<RecipeDetail RecipeId="@SelectedRecipeId" Ingredients="@Ingredients"></RecipeDetail>

@code {
    Recipe[] RecipesArray;
    int SelectedRecipeId;
    RecipeIngredient[] Ingredients = new RecipeIngredient[]();

    protected override async Task OnInitializedAsync()
    {
        RecipesArray = await Http.GetJsonAsync<Recipe[]>("Recipes");
    }

    protected void SelectRecipe(Recipe recipe)
    {
        SelectedRecipeId = recipe.Id;
        if (value > 0)
        {
          Ingredients = Http.GetJsonAsync<RecipeIngredient[]>($"Recipes/{SelectedRecipeId}").Result;
        }
    }
}

and:

<h1>RecipeId = @selectedRecipeId</h1>
@if (Ingredients != null && Ingredients.Length > 0)
{
    <h1>@RecipeId</h1>
    <ul>
        @foreach (var item in Ingredients)
        {
            <li>@item.Ingredient.ToString()</li>
        }
    </ul>
}


@code {
    [Parameter]
    public int RecipeId;

    [Parameter]
    public RecipeIngredient[] Ingredients;
}
Michael Washington
  • 2,097
  • 13
  • 22
  • That might work, but it seems to break the separation of concerns. I'd really like the component to be responsible for fetching its own data. – Kevon Aug 25 '19 at 22:19
-1

This is the solution I came up with for the RecipeDetail

@using BlazorApp.Shared.Models
@inject HttpClient Http

@if (Ingredients != null && Ingredients.Length > 0)
{
    <ul>
        @foreach (var item in Ingredients)
        {
            <li>@item.ToString()</li>
        }
    </ul>
}


@code {
    RecipeIngredient[] Ingredients;

    [Parameter]
    public int RecipeId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        if (RecipeId != 0)
        {
            await LoadRecipeDetails(RecipeId);
        }
    }

    private async Task LoadRecipeDetails(int RecipeId)
    {
        Ingredients = await Http.GetJsonAsync<RecipeIngredient[]>($"Recipes/{RecipeId}");
    }
}

Kevon
  • 159
  • 1
  • 8