Three NuGet packages to improve exceptions in .NET/C#
We love exceptions. Not in the oh-no-my-website-crashed kind of way, but all of the possibilities provided by exceptions and a good exception handling strategy. In this post, I'll introduce you to three different NuGet packages that will help you when dealing with exceptions in C#.
.NET comes with a set of exceptions as part of the C# language. You've probably tried creating your own exceptions too by extending System.ApplicationException or similar. When dealing with exceptions you will challenge the same problems again and again like having to retry a method call or properly understand a stack trace. Let's dig into three NuGet packages that can help you.
Xeption
Too few developers actively use the Data
dictionary available on all exceptions. Or at least that's my opinion. For the uninvited, all exceptions inherit a public property named Data
of type IDictionary
. The idea behind this property is for you to enrich an exception with additional information to help debug what went wrong. Entries are usually added like this:
string someString = "hello world";
try
{
// ...
}
catch (Exception e)
{
e.Data.Add("String was", someString);
throw;
}
When logging this exception somewhere, you should be able to see the value of the String was
item. Being a dictionary, the Data
property has some disadvantages, though. The primary one is that a new exception is thrown if trying to add the same key twice. Meet Xeption
. Xeption
is created for home-made exceptions only and makes it possible for you to extend the Xeption
class:
public class MyException : Xeption
{
}
By doing so, you have access to a new way of adding data:
string someString = "hello world";
try
{
// ...
}
catch (MyException e)
{
e.UpsertDataList("String was", someString);
throw;
}
Xeption
also provides a convenient way of throwing an exception if data has been added to it. This works great with validation errors and similar:
var e = new MyException();
if (string.IsNullOrWhitespace(firstname))
{
e.UpserDataList("validationError", "Missing firstname");
}
if (string.IsNullOrWhitespace(lastname))
{
e.UpserDataList("validationError", "Missing lastname");
}
e.ThrowIfContainsErrors();
Ben.Demystifier
Ever looked at a stack trace and wondered how a method translates to your code? Stack traces can be hard to read sometimes, mainly because they represent compiled code and not the way it was originally written. The following image from the Ben.Demystifier
repository on GitHub explains it better than me, why I'm hoping it's ok to borrow and repost here:
As shown in the image, there are multiple improvements done to the original stack trace by the Ben.Demystifier
package. Exception stack traces are transformed by calling the Demystify
method:
try
{
// ...
}
catch (Exception e)
{
e.Demystify();
logger.LogError(e, "An error happened");
}
There's a great list of improvements made to stack traces with this package on the GitHub repository, why I won't repeat them here. Make sure to check out all the goodness of Ben.Demystifier
and consider calling Demystify
on all exceptions before logging them.
Polly
I'm not sure if this package needs an introduction but here goes. When handling exceptions it sometimes makes sense to do something else than simply log, ignore, or show an error message to the user. Consider a situation where you need to call an external API resulting in an HTTP exception. An approach could be to wait a bit and retry the request. This is where Polly can help.
The main entry point for setting up Polly is through the Policy
class:
Policy
.Handle<HttpRequestException>()
.Retry(3)
.Execute(() =>
{
// Make a HTTP Request
});
The declared policy has three parts. First, we tell Polly which exception to look for. In this example the HttpRequestException
. In the next line, we tell Polly what to do when an exception of this type is thrown. In the example, we retry three times. And finally, the Execute
method provides an action that should be called. Inside the lambda, you can use a HttpClient
to make an HTTP request. In case all four attempts (one initial and three retries) fail, Polly will re-throw the HttpRequestException
.
But Polly doesn't stop there. Multiple retry strategies are available and even complex scenarios like a circuit breaker. Check out the Polly documentation for more examples.
Polly also nicely integrates with ASP.NET Core and all code in general injecting HttpClient
s. You have probably seen setup code like this in a Startup.cs
file somewhere:
services.AddHttpClient("myclient");
Rather than wrapping all requests when using this HTTP client with retry policies, you can set up a global policy using the Microsoft.Extensions.Http.Polly
NuGet package:
services
.AddHttpClient("myclient")
.AddPolicyHandler(Policy
.Handle<HttpRequestException>()
.Retry(3)
.AsAsyncPolicy<HttpResponseMessage>());;
I just want to quickly mention an alternative to Polly when configuring retries with HttpClient
and HttpClientFactory
: Finity. Finity provides some of the same features as Polly using an alternative but similar syntax when configuring an HttpClient
:
services
.AddHttpClient("myclient")
.WithRetry(options =>
{
options.SleepDurationRetry = TimeSpan.FromMilliseconds(100);
options.RetryCount = 3;
});
I'm using Polly currently, but will definitely keep an eye on Finity too.
You made it! With so much passion for exceptions, I'm betting you will find C# exception handling best practices interesting as well 😁