Throw exceptions from backend to frontend with blazor


Shared code of blazor .net hosted projects

if you create new blazor asp.net core hosted project, you can see that it has Shared class library with WeatherForecast model, which is shared between frontend and backend projects. It is convinient way to shared models and source code and increase reusability. Moreover, you can reuse not only view models but also exceptions.

Exceptions

In every project we deal with exceptions, for example external service can be unavailable at the moment or item with such arguments is not found. All is understood by developer, but not for client. So we try to unify response models with understandable information what happened with request for client. For example, we can create generic response model with result code and message, however our frontend should understand all codes which also could be changed.

Let’s take a look how shared exceptions can give us more convinient way working with exceptions.

We start from creating new blazor asp.net core hosted project.

First of all, let’s create our custom exception and throw exception in WeatherForecastController. In Shared project we add WeatherUnavailableException.cs

public class WeatherUnavailableException : Exception
{
    public WeatherUnavailableException(string message) : base(message)
    {
    }
}

And in WeatherForecastController.cs

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{

    throw new WeatherUnavailableException("Wether service is not available right now");
}

Now our controller returns 500 http response code. We need exceptions handler in Server proejct.So let’s add to Server project ExceptionFilterAttribute.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ExceptionFilterAttribute : Microsoft.AspNetCore.Mvc.Filters.ExceptionFilterAttribute
{
    
    private JsonSerializerSettings _settings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Objects,
        TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full
    };
    
    public override Task OnExceptionAsync(ExceptionContext context)
    {

        switch (context.Exception)
        {
            case WeatherUnavailableException :
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.FailedDependency;                    
                break;
            
            default:
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;                    
                break;
        }

        context.HttpContext.Response.ContentType = "application/json";
        return context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(context.Exception, _settings));
    }
}

Here we ask jsonSerializer also include information about Type, in order to know how deserialize in frontend.

Final step in Server project is setting up exceptions filter in Startup.cs

public void ConfigureServices(IServiceCollection services)
{

    services.AddControllersWithViews(options=>options.Filters.Add(typeof(ExceptionFilterAttribute)));
    services.AddRazorPages();
}

Now, if you send request to WheatherForecastConttoller you get such respone

{
   "$type":"BlazorExceptions.Shared.WeatherUnavailableException, BlazorExceptions.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
   "Message":"Wether service is not available right now",
   "Data":{
      "$type":"System.Collections.ListDictionaryInternal, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
   },
   "InnerException":null,
   "HelpLink":null,
   "Source": "BlazorExceptions.Server",
   "HResult": -2146233088,
   "StackTrace": "  ..."
}

Also http response code is FailedDependency = 424.

Frontend

In Client project we need custom http client, let’s add MyHttpClient.cs

public class MyHttpClient
{
    private HttpClient _httpClient;

    public MyHttpClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<T> Get<T>(string url)
    {
        var response = await _httpClient.GetAsync(url);

        if (!response.IsSuccessStatusCode)
        {
            var responseEx = await response.Content.ReadAsStringAsync();
            throw JsonConvert.DeserializeObject<Exception>(responseEx, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.All,
                TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full
            });
        }

        var responseContent = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    }
}

If response status code is not 200 we try to deserizalize exception. In Program.cs add MyHttpClient to DI container.

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");

    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddScoped<MyHttpClient>();

    await builder.Build().RunAsync();
}

And last point, FetchData.razor


@page "/fetchdata"
@using BlazorExceptions.Shared
@inject MyHttpClient Http
@inject IJSRuntime JsRuntime

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.Get<WeatherForecast[]>("WeatherForecast");        
        }
        catch(WeatherUnavailableException e)
        {
            //expected exception
            await JsRuntime.InvokeVoidAsync("alert", e.Message);
        }
        catch(Exception e)
        {
            //unexpected exception
            Console.WriteLine(e.Message); 
        }        
    }
}

Conclusion

One language code base allows to reuse exceptions in very convinient way. After some time, you can forget about splited client and server applications and work with exceptions like you are in one application context.

Buy Me A Coffee

Related Posts

Avoid reflections of mappers and let Mapster generate mappers for you

Mapster generating tool for onion application

Predict Bitcoin price with ML.net

Live time series coin price predictor with machine learning

How to avoid violating of SOLID principles while extending service behaviours

Step by step extending service behaviour with decorator pattern respecting SOLID

Blazor render optimization

Best practices

.Net 6 brings application state to blazor webassembly server prerender

It kills strange blinking on screen

Must have libraries for blazor

List of best nuget packages

Blazor virtualize component

Lazy loading of big lists. Rendering optimization.

Blazor grpc - comunication optimization

Smaller and faster requests to your backend from blazor wasm

Free database for your blazor app

Don't pay for the cloud

Blazor common error component

Single component for showing errors on any page and component