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.