1

I'm using the Language-Ext library to create a generic union type that represents one of three states...

  [Union]
  public interface LoadingOption<T> {
    LoadingOption<T> Loading();
    LoadingOption<T> Loaded(T value);
    LoadingOption<T> NotLoaded();
  }

I then have a Blazor component that will show one of three render fragments, depending on which of the three states is represented by the instance of the union...

@typeparam T

@(Data switch {
  Loading<T> => Loading,
  Loaded<T> t => Loaded(t.Value),
  NotLoaded<T> => NotLoaded,
  _ => Loading
  })

@code {

  [Parameter]
  public LoadingOption<T> Data { get; set; }

  [Parameter]
  public RenderFragment<Loading<T>> Loading { get; set; }

  [Parameter]
  public RenderFragment<Loaded<T>> Loaded { get; set; }

  [Parameter]
  public RenderFragment<NotLoaded<T>> NotLoaded { get; set; }

}

Note that the default case in the switch statement is needed to prevent an exception Non-exhaustive switch expression failed to match its input when the page firsts loads.

However, when I try to use this component, the HTML is never rendered. Instead, I see Microsoft.AspNetCore.Components.RenderFragment on the page, which sounds like it's treating the RenderFragment as a plain object, rather than as a RenderFragment

Sample usage of the Loader.razor component is as follows...

@page "/LoadingOptionSample"
<h3>Loader sample</h3>

<p><button class="btn btn-primary" @onclick="LoadCustomerFound">Existing customer</button>
   <button class="btn btn-secondary" @onclick="LoadCustomerNotFound">No such customer</button></p>

<Loader Data="_customer" Context="customer">
  <Loading>
    <p>Please wait while we look for the customer...</p>
  </Loading>
  <Loaded>
    <p>Found @customer.Value.Name (id: @customer.Value.Id), who is @customer.Value.Age years old</p>
  </Loaded>
  <NotLoaded>
    <p>Big flop, we can't find him</p>
  </NotLoaded>
</Loader>

@code {

  private LoadingOption<Customer> _customer;

  private async Task LoadCustomerFound() {
    _customer = new Loading<Customer>();
    // Simulate database access so we can see the Loading HTML...
    await Task.Delay(2000);
    _customer = new Loaded<Customer>(new Customer(1, "Jim Spriggs", 42));
  }

  private async Task LoadCustomerNotFound() {
    _customer = new Loading<Customer>();
    await Task.Delay(2000);
    _customer = new NotLoaded<Customer>();
  }

}

After clicking one of the buttons, the page looks like this...

Sample output

Doesn't matter which of the buttons I click, the output is the same, and doesn't change. This supports my suspicion that Blazor isn't treating the RenderFragment as such, but treats it as a plain .NET object and renders it by calling ToString(), which is why I see the type, not the content.

If I do a non-generic version of this, it works fine.

Note that I have repeatedly restarted VS, as well as trying this in a separate project, but the result is always the same, so it seems the problem is encapsulated in the three files shown above.

Anyone any idea what I'm doing wrong? Thanks

Update I tried using a regular switch statement, instead of the switch expression, but it didn't make any difference. However, while testing this, I discovered that if I didn't have the switch at all, and simply did this...

  @NotLoaded

...then I still got the type shown on the page.

8
  • There is mismatch between that [union] thing and how switch expressions work. Your return values are not all of the same type. Try a traditional switch statement. Commented Jun 27, 2021 at 21:15
  • @henk Thanks for the suggestion, but it didn't make any difference. Please see my updated question, as your suggestion led to a discovery that may be significant. Thanks again. Commented Jun 28, 2021 at 13:29
  • You are missing a parameter. It should be something like @NotLoaded(Data) , although that might not compile. Commented Jun 28, 2021 at 20:32
  • @HenkHolterman NotLoaded and Loading don't take a parameter, or at least they shouldn't. Only Loaded takes a parameter, namely the entity that has been loaded. If you look at the [Union] code, you can see this. Not sure if I got the RenderFragment declarations right in the Blazor component though. Maybe that's where it's going wrong. However, if that's the problem, I would have expected either NotLoaded and Loading to work, or Loaded to work. As it is, all three just show the RenderFragment's type, as opposed to rendering it as I want. Thanks again, any more ideas? Commented Jun 28, 2021 at 21:06
  • Is there a reason why you can't use a generic "Loading" component with three states and three RenderFragments? Then set the state from the parent? Commented Jun 28, 2021 at 21:15

1 Answer 1

1

This answer demonstrates a somewhat different approach to managing component loading and state.

Enum for the State

    public enum DataLoaderState { NotSet, Loading, Loaded, Error }

A basic Loader Component.

@namespace Blazor.Starter.Components

@inherits ComponentBase
@if (this.State == DataLoaderState.Loading)
{
    if (this.LoadingContent != null)
    {
        @this.LoadingContent
    }
    else
    {
        <div class="m-2 p-2">Loading......</div>
    }
}
else if (this.State == DataLoaderState.Error)
{
    if (this.ErrorContent != null)
    {
        @this.ErrorContent
    }
    else
    {
        <div class="m-2 p-2">Error Loading Data</div>
    }
}
else
{
    @this.ChildContent
}
@code{

    [Parameter] public RenderFragment ChildContent { get; set; }
    [Parameter] public RenderFragment LoadingContent { get; set; }
    [Parameter] public RenderFragment ErrorContent { get; set; }
    [Parameter] public DataLoaderState State { get; set; } = DataLoaderState.NotSet;
}

A sample page similar to the one in the question to demo the Loader.

@page "/"

<h2>Home Page</h2>

<DataLoader State="loadState">
    <LoadingContent>
        <div class="m-2 p-2 bg-warning">
            Please wait while we look for the customer...
        </div>
    </LoadingContent>
    <ChildContent>
        <div class="m-2 p-2 bg-success">
            Name: @Model.Name - Age: @Model.Age
        </div>
    </ChildContent>
    <ErrorContent>
        <div class="m-2 p-2 bg-danger">
            Big flop, we can't find him
        </div>
    </ErrorContent>
</DataLoader>

<DataLoader State="loadState">
    <div class="m-2 p-2">
        Loaded
    </div>
</DataLoader>

<div class="m-2 p-2">
    <button class="btn btn-primary" @onclick="LoadAsync">Reload</button>
</div>

<div class="m-2 p-2">
    <button class="btn btn-danger" @onclick="LoadErrorAsync">Reload with Error</button>
</div>

@code {

    private DataLoaderState loadState = DataLoaderState.Loaded;

    private Customer Model;

    protected async override Task OnInitializedAsync()
    {
        await LoadAsync();
    }

    private async Task LoadAsync()
    {
        Model = null;
        loadState = DataLoaderState.Loading;
        // emulate a slow async data load
        await Task.Delay(3000);
        Model = new Customer { Name = "Billy Bloggs", Age = 24 };
        loadState = DataLoaderState.Loaded;
    }

    private async Task LoadErrorAsync()
    {
        Model = null;
        loadState = DataLoaderState.Loading;
        // emulate a slow async data load
        await Task.Delay(3000);
        loadState = DataLoaderState.Error;
    }

    public class Customer
    {
        public Guid ID { get; set; } = Guid.NewGuid();
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the reply. This is similar in a way to what I'm currently doing (see github.com/MrYossu/Pixata.Utilities/blob/master/… for the component and github.com/MrYossu/Pixata.Utilities/blob/master/… for a sample of how to use it). It works, but I can't help but feel that I ought to be able to do this with just the one parameter to the component, not two. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.