Skip to content

Instantly share code, notes, and snippets.

@Zodt
Last active July 6, 2023 22:34
Show Gist options
  • Save Zodt/12e5fd474fa01c7026a29e01f081c143 to your computer and use it in GitHub Desktop.
Save Zodt/12e5fd474fa01c7026a29e01f081c143 to your computer and use it in GitHub Desktop.
MediatR exceptions handling
public static class ExceptionExtensions
{
public static List<Exception> FlattenInnerExceptions(this Exception exception)
{
return exception
.DepthFirstSearchInnerExceptions()
.Distinct()
.Reverse()
.ToList();
}
private static IEnumerable<Exception> DepthFirstSearchInnerExceptions(this Exception exception)
{
if (exception is not AggregateException) yield return exception;
var bypassInnerException = exception;
do
{
switch (bypassInnerException)
{
case AggregateException { InnerExceptions: { } innerExceptions } aggregateException when innerExceptions.Any():
foreach (var innerException in aggregateException.FlattenAggregateExceptions())
{
yield return innerException;
}
break;
case { InnerException: AggregateException { InnerExceptions: { } innerExceptions } aggregateException } when innerExceptions.Any():
foreach (var innerException in aggregateException.FlattenAggregateExceptions())
{
yield return innerException;
}
break;
case { InnerException: { } innerException }:
yield return innerException;
break;
default:
yield return bypassInnerException;
break;
}
bypassInnerException = bypassInnerException.InnerException;
} while (bypassInnerException is not null);
}
private static IEnumerable<Exception> FlattenAggregateExceptions(this AggregateException aggregateException)
{
return aggregateException
.Flatten().InnerExceptions
.SelectMany(DepthFirstSearchInnerExceptions);
}
}
public class ExceptionsHandlerBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
try
{
return await next().ConfigureAwait(false);
}
catch (InvalidOperationException exception)
{
var exceptions = exception.FlattenInnerExceptions();
foreach (var ex in exceptions)
{
Log.Write(LogEventLevel.Warning, "{Exception}: {Message}", ex.GetType().FullName, ex.Message);
}
throw new AggregateException("One or more errors occurred", exceptions);
}
}
}
public static void AddMediatR(this IServiceCollection services)
{
//...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ExceptionsHandlerBehavior<,>));
//...
}
public class ExceptionExtensionsTests
{
private readonly ITestOutputHelper _output;
public ExceptionExtensionsTests(ITestOutputHelper output)
{
_output = output;
}
public static readonly object[][] AggregateException_InnerNineExceptions =
{
new object[]
{
ExceptionsExtensionsFixture.CreateAggregateException_InnerNineExceptions(),
}
};
[Theory, MemberData(nameof(AggregateException_InnerNineExceptions))]
public void FlattenInnerExceptions_ShouldReturnDistinctOrderedListExceptions((Exception exception, List<Exception> expected) valueTuple)
{
var (exception, expected) = valueTuple;
var actual = exception.FlattenInnerExceptions();
Assert.Equal(expected.Count, actual.Count);
for (var i = 0; i < expected.Count; i++)
{
_output.WriteLine($"{actual[i].GetType().FullName}: {actual[i].Message}");
Assert.Equal(expected[i], actual[i]);
}
}
}
public static class ExceptionsExtensionsFixture
{
/// <summary>
/// Created nine exceptions in "Exception.InnerException" each other
/// </summary>
/// <returns>
/// System.NullReferenceException: NullReferenceException <br/>
/// System.ArgumentException: ArgumentException <br/>
/// System.Exceptions.RecordNotFoundException: RecordNotFoundException <br/>
/// MongoDB.Bson.BsonSerializationException: BsonSerializationException <br/>
/// MongoDB.Bson.BsonException: BsonException <br/>
/// Microsoft.EntityFrameworkCore.Storage.RetryLimitExceededException: RetryLimitExceededException <br/>
/// Castle.DynamicProxy.Generators.GeneratorException: GeneratorException <br/>
/// System.TypeLoadException: TypeLoadException <br/>
/// System.InvalidOperationException: InvalidOperationException <br/>
/// </returns>
public static (Exception, List<Exception>) CreateAggregateException_InnerNineExceptions()
{
var nullReference = new NullReferenceException(nameof(NullReferenceException));
var argument = new ArgumentException(nameof(ArgumentException), nullReference);
var recordNotFound = new RecordNotFoundException(nameof(RecordNotFoundException), argument);
var bsonSerialization = new BsonSerializationException(nameof(BsonSerializationException));
var bson = new BsonException(nameof(BsonException), bsonSerialization);
var aggregate = new AggregateException(bson, recordNotFound);
var retryLimitExceeded = new RetryLimitExceededException(nameof(RetryLimitExceededException), aggregate);
var generator = new GeneratorException(nameof(GeneratorException), retryLimitExceeded);
var typeLoad = new TypeLoadException(nameof(TypeLoadException), generator);
var invalidOperation = new InvalidOperationException(nameof(InvalidOperationException), typeLoad);
var exception = new AggregateException(nameof(AggregateException), aggregate, invalidOperation);
List<Exception> exceptions = new()
{
nullReference,
argument,
recordNotFound,
bsonSerialization,
bson,
retryLimitExceeded,
generator,
typeLoad,
invalidOperation
};
return (exception, exceptions);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment