Best Practices for Error Handling in .Net 6
There are many .NET exception handling and logging tools that help developers integrate global error handling and logging into their applications.
Join the DZone community and get the full member experience.
Join For FreeQuick Microsoft .NET 6 Overview
Microsoft .NET 6 is a cross-platform framework that merges the .NET Core, .NET Framework, and Xamarin/Mono technologies into a single framework. Continuing the unification that started with .NET 5 by unifying the runtime and SDK for desktop, web, mobile, and cloud applications.
Alongside C# 10 and Visual Studio 2022, Microsoft made .NET 6 generally available on November 9, 2021. There are many new enhancements to .NET 6, including:
- Improved performance
- Profile - guided optimizing
- Security improvements
- C# 10 and F# 6 improvements
Error Handling Using Try-Catch Blocks
Try-catch blocks are the most basic way of handling exceptions and are most widely used and generally used to gracefully handle code statements that might generate an exception.
Consider the following ProcessFile class as a code example:
using System;
using System.IO;
public class ProcessFile
{
public static void Main()
{
try
{
using (StreamReader sr = File.OpenText("data.txt"))
{
Console.WriteLine($"The first line of this file is {sr.ReadLine()}");
}
}
catch (FileNotFoundException e)
{
Console.WriteLine($"The file was not found: '{e}'");
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine($"The directory was not found: '{e}'");
}
catch (IOException e)
{
Console.WriteLine($"The file could not be opened: '{e}'");
}
catch (Exception e)
{
Console.WriteLine($"General exception: '{e}'");
}
}
}
The above code example is a typical case where we use try-catch to handle the exception. Any exception thrown by the code enclosed within the try block will be caught and handled by the catch block. Also illustrated is the use of catching multiple exception types, which is useful for handling different exceptions uniquely for the same block of code.
Try-catch blocks are best used within methods in which an error might occur, and you would like to handle it appropriately and uniquely for the end user or service. Every developer should be aware of try-catch blocks as it is a useful tools to develop robust applications. However, adding a try-catch block to every method is not desirable and can get messy quickly. It's best to use global exception handling and try-catch block where uniquely needed.
Global Exception Handling
Global exception handling enables an application with a much broader ability to handle all exceptions. This is usually done through custom middleware for ASP.NET core applications, AppDomain unhandled exception events for .NET core console and service applications, or .NET 3rd party exception handling libraries.
ASP.NET Core Global Exception Handling With Custom Middleware
Using custom middleware to handle global exceptions provides a better development and application experience.
Consider the following CustomExceptionHandlingMiddleware class as a Custom Middleware example:
using System.Net;
using System.Text.Json;
using ExceptionHandling.Models.Responses;
namespace Middleware;
public class CustomExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<CustomExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<CustomExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
var response = context.Response;
var errorResponse = new ErrorResponse
{
Success = false
};
switch (exception)
{
case ApplicationException ex:
if (ex.Message.Contains("Unauthorized"))
{
response.StatusCode = (int) HttpStatusCode.Forbidden;
errorResponse.Message = ex.Message;
break;
}
response.StatusCode = (int) HttpStatusCode.BadRequest;
errorResponse.Message = ex.Message;
break;
default:
response.StatusCode = (int) HttpStatusCode.InternalServerError;
errorResponse.Message = "Internal server error!";
break;
}
_logger.LogError(exception.Message);
var result = JsonSerializer.Serialize(errorResponse);
await context.Response.WriteAsync(result);
}
}
Next, you must register your middleware in your Program class.
app.UseMiddleware<ExceptionHandlingMiddleware>();
.NET Core Console Global Exception Handling
Global exception handling in console apps is done through the AppDomain unhandled exception event. Here you can add custom logic around what happens when an unhandled exception occurs.
Consider the following code from a sample Program class that catches and logs exceptions:
var logger = host.Services.GetRequiredService<ILogger<Program>>();
System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
logger.LogError(exception, exception.Message);
}
3rd Party Logging and Exception Handling
There are many .NET exception handling and logging tools that help developers integrate global error handling and logging into their applications. This removes a lot of the heavy lifting from the developer with easier implementation and management.
Consider this example of ClearInsights Logging for global exception handling:
ASP.NET Core
using ClearInsights.Logging;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
//Add to capture logs with ClearInsights Logging
builder.Logging.AddClearInsightsLogger(configuration =>
{
configuration.ApiKey = "{ApiKey}";
configuration.Secret = "{Environment Client Secret}";
configuration.ApplicationName = "{Application Name}";
});
var app = builder.Build();
//Add to use ClearInsights global error handling.
//This will automatically catch and log any unhandled exceptions
app.UseClearInsightsExceptionHandling(options =>
{
//Add to extend the error handler and add additional logic.
//Add logic like custom HTTP response, etc...
options.OnError += (sender, arg) =>
{
var response = "Oops something went wrong";
arg.HttpContext.Response.ContentType = "text/html";
arg.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
arg.HttpContext.Response.WriteAsync(response);
};
});
.NET Core
using ClearInsights.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(builder =>
builder.ClearProviders()
//Add to capture logs with ClearInsights Logging
.AddClearInsightsLogger(configuration =>
{
configuration.ApiKey = "{ApiKey}";
configuration.Secret = "{Environment Client Secret}";
configuration.ApplicationName = "{Application Name}";
}))
.Build();
//Add to use ClearInsights global error handling.
//This will automatically catch and log any unhandled exceptions
System.AppDomain.CurrentDomain.UseClearInsightsExceptionHandling(host.Services.GetRequiredService<ILogger<Program>>());
Opinions expressed by DZone contributors are their own.
Comments