DEV Community

Mirco Benthien
Mirco Benthien

Posted on

Sane Loading State Management in .NET Frontends (Blazor + WPF)

Scenario

In any kind of real-world application, you will need to eventually display a loading spinner. Usually pretty early on and usually pretty often. Any time a page is loading, any time you click a button or interact with any other type of UI control.

The Problem

Have you ever seen this kind of code and progression?
It begins very simple.

IsLoading = true;
var data = await CallExternalService();
IsLoading = false;
Enter fullscreen mode Exit fullscreen mode

Now let's add a standard programming pattern with early returns.
3/8 lines of actual code here are reserved for loading state management.

IsLoading = true;
var result = await CallExternalService();
if (result is "cool")
{
    var differentResult = await GetDataFromSomewhereElse();
    IsLoading = false;
    return differentResult;
}
IsLoading = false;
return result;
Enter fullscreen mode Exit fullscreen mode

Can't forget error handling.
3/7 lines of actual code here are reserved for loading state management.

try
{
    IsLoading = true;
    var data = await CallExternalService();
    IsLoading = false;
}
catch (Exception ex)
{
    // log..
    IsLoading = false;
}
Enter fullscreen mode Exit fullscreen mode

If you combine the two and add more real-life code than just a single call to an external service it gets even messier. I know I have seen this type of code hundreds of times. You probably did too.

The Solution

What we will end up with is this.

private async Task GetDataFromSomwhere()
{
    using var _ = DisplayLoading();
    var result = await CallExternalService();
}
Enter fullscreen mode Exit fullscreen mode

The first line here sets IsLoading = true at the beginning of the method and IsLoading = false at the end of the method. No matter what happens or where exactly you exit the method.

If you just want the code, here it is in a blazor page.

public partial class BasePage: ComponentBase, IBasePage
{
    public bool IsLoading { get; set; } = false;
    protected LoadingHelper DisplayLoading() => new LoadingHelper(this);
}

public class LoadingHelper : IDisposable
{
    private IBasePage basePage;

    public LoadingHelper(IBasePage basePage)
    {
        this.basePage = basePage;
        basePage.IsLoading = true;
    }

    public void Dispose()
    {
        basePage.IsLoading = false;
    }
}
Enter fullscreen mode Exit fullscreen mode

How it works

The LoadingHelper implements IDisposable. IDisposable is typically implemented by classes that need to do their own cleanup. It's usually heavier classes like SqlConnections or file access.
The Dispose() method runs when the class gets cleaned up by the GarbageCollector.

When the BasePage creates an instance of the LoadingHelper in a local method, that instance will get cleaned up at the end of the method. Always.
So this is where I ultimately set IsLoading = false.

I am using some c# syntax sugar to get the code as terse as possible.
This is actually the same code.

private async Task GetDataFromSomewhere()
{
    using (var loadingHelper = DisplayLoading())
    {
        var result = await CallExternalService();
    }
}
Enter fullscreen mode Exit fullscreen mode

The first one is the _ discard operator. If you don't need access to a local variable, you can simply assign it to _.
The second one is the using declaration. A using declaration is scoped to the surrounding scope. In this case the method.

How I got there

My first attempt was just writing a wrapping method. Something like this.

    private T WrapInLoading<T>(Func<T> func)
    {
        IsLoading = true;
        var res = func.Invoke();
        IsLoading = false;
        return res;
    }
Enter fullscreen mode Exit fullscreen mode

This leads to client code looking like this

private async Task GetDataV5()
{
    await WrapInLoading(async () =>
    {
        //...
        //...
        var data = await CallExternalService();
    });
}
Enter fullscreen mode Exit fullscreen mode

This is not terrible. However not that pretty either. It also can get pretty complicated if you want synchronous and asynchronous code with and without return values. Handling void, T, Task and Task is certainly possible, however not simple.

Ultimately this just "looked like" the standard using syntax to me and I dreamed up a syntax I would like to have.
Something like

using(Loading){

}
Enter fullscreen mode Exit fullscreen mode

I like what I ended up with even more and it actually compiles.
It even avoids the extra level of nesting.

What I would love to have now is using(Loading); however, I don't think it is possible with the current c# language features.

Top comments (0)