ForEvolve.ExceptionMapper
3.0.41
dotnet add package ForEvolve.ExceptionMapper --version 3.0.41
NuGet\Install-Package ForEvolve.ExceptionMapper -Version 3.0.41
<PackageReference Include="ForEvolve.ExceptionMapper" Version="3.0.41" />
paket add ForEvolve.ExceptionMapper --version 3.0.41
#r "nuget: ForEvolve.ExceptionMapper, 3.0.41"
// Install ForEvolve.ExceptionMapper as a Cake Addin #addin nuget:?package=ForEvolve.ExceptionMapper&version=3.0.41 // Install ForEvolve.ExceptionMapper as a Cake Tool #tool nuget:?package=ForEvolve.ExceptionMapper&version=3.0.41
ExceptionMapper
A simple ASP.NET Core middleware that intercepts and reacts to Exception
.
You can map specific exception types to HTTP Status Codes, use predefined handlers, or create your own.
You can throw an exception from anywhere in your codebase and ExceptionMapper will handle it according to your specifications. This makes it a breeze to uniformize exception handling in a REST API.
All the handlers are iterated through, in order, so you can build a pipeline to handle exceptions where multiple handlers have a single responsibility. For example, you could have handlers that respond to certain exception types, then one or more fallback handlers that react only if no previous handler handled the exception.
Finally, there is a serializer that converts handled exceptions to JSON, in the format of your choice, making your API linear between endpoints and exception types without much effort. The default serializer converts the errors to Problem Details for HTTP APIs.
How to install
Add a reference to the ForEvolve.ExceptionMapper
NuGet package:
dotnet add package ForEvolve.ExceptionMapper
You can take a look at the samples/WebApiSample
project for a working example.
Getting started
You must register the services, optionally configure/register handlers, and use the middleware that catches exceptions (and that handles the logic).
Program.cs
// Add the dependencies to the container
builder.AddExceptionMapper();
// Register the middleware
app.UseExceptionMapper();
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddExceptionMapper(Configuration);
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseExceptionMapper(); // Register the middleware
//...
}
}
Extending the existing exception
An easy way to manage your custom exceptions is to inherit from the ones that are already mapped.
For example, you could create and throw the following DroidNotFoundException
and ExceptionMapper will associate it with a 404 Not Found status code because it inherits from NotFoundException
:
public class DroidNotFoundException : NotFoundException
{
public DroidNotFoundException()
: base("These aren't the droids we're looking for.")
{
}
}
Mapping Exception types to status code
If you do not want or can't inherit the provided exceptions, you can map any Exception types to a specific status code, like this:
builder.AddExceptionMapper(builder =>
{
builder.Map<ImATeapotException>().ToStatusCode(StatusCodes.Status418ImATeapot);
});
public class ImATeapotException : Exception
{
}
Creating a custom exception handler
If the previous options are not enough to handle your use case, you can implement a custom exception handler.
To do so, you can inherit from the ExceptionHandler<TException>
or implement the IExceptionHandler
interface.
Then you must register it using the AddExceptionHandler
extension method.
Let's start by creating an exception:
public class MyForbiddenException : Exception { /* Omitted implementation */ }
Then create the handler:
public class MyForbiddenExceptionHandler : ExceptionHandler<MyForbiddenException>
{
public override int StatusCode => StatusCodes.Status403Forbidden;
}
Finally, you must register it:
builder.AddExceptionMapper(builder =>
{
builder.AddExceptionHandler<MyForbiddenExceptionHandler>();
});
Updating the type URI
You can customize the type
property of the problem details object by configuring the ApiBehaviorOptions
object, like this:
builder.Services
.Configure<ApiBehaviorOptions>(options =>
{
options.ClientErrorMapping.Add(StatusCodes.Status409Conflict, new ClientErrorData
{
Link = "https://localhost:8828/Status409Conflict", // This is taken into account because the middleware do not set any link by default.
Title = "This will not be displayed." // Not taken into account because the middleware sets the title to the Exception.Message value.
});
})
;
If using MVC the mappings should be registered so you may want to modify the data instead:
builder.Services
.Configure<ApiBehaviorOptions>(options =>
{
options.ClientErrorMapping[StatusCodes.Status409Conflict].Link = "https://localhost:8828/Status409Conflict";
})
;
Common Exceptions
ExceptionMapper implements different common exceptions and their handlers, like the following maps (namespace ForEvolve.ExceptionMapper
):
// Common client exceptions
.Map<BadRequestException>().ToStatusCode(StatusCodes.Status400BadRequest)
.Map<ConflictException>().ToStatusCode(StatusCodes.Status409Conflict)
.Map<ForbiddenException>().ToStatusCode(StatusCodes.Status403Forbidden)
.Map<GoneException>().ToStatusCode(StatusCodes.Status410Gone)
.Map<NotFoundException>().ToStatusCode(StatusCodes.Status404NotFound)
.Map<ResourceNotFoundException>().ToStatusCode(StatusCodes.Status404NotFound)
.Map<UnauthorizedException>().ToStatusCode(StatusCodes.Status401Unauthorized)
// .NET exceptions
.Map<BadHttpRequestException>().ToStatusCode(StatusCodes.Status400BadRequest)
.Map<NotImplementedException>().ToStatusCode(StatusCodes.Status501NotImplemented)
// Common server exceptions
.Map<GatewayTimeoutException>().ToStatusCode(StatusCodes.Status504GatewayTimeout)
.Map<InternalServerErrorException>().ToStatusCode(StatusCodes.Status500InternalServerError)
.Map<ServiceUnavailableException>().ToStatusCode(StatusCodes.Status503ServiceUnavailable)
Fallback handler
ExceptionMapper also comes with a fallback handler that converts unhandled exceptions to 500 InternalServerError
. This is an opt-out feature, configured by the FallbackExceptionHandlerOptions
.
You can also configure the FallbackExceptionHandlerOptions
like the following or under the ExceptionMapper:FallbackExceptionHandler
key in your settings:
services.Configure<FallbackExceptionHandlerOptions>(options =>
{
options.Strategy = FallbackStrategy.Handle;
});
Serialization (Json)
ExceptionMapper provides a default implementation of the IExceptionSerializer
interface that serializes exceptions as ProblemDetails
.
When targetting .NET 7+, ExceptionMapper uses the IProblemDetailsService
interface from ASP.NET Core.
If you want to customize the default serializer, you can configure the ProblemDetailsSerializationOptions
class, like this:
builder.Services.Configure<ProblemDetailsSerializationOptions>(options =>
{
options.SerializeExceptions = false;
options.DisplayDebugInformation = (ExceptionHandlingContext ctx) =>
{
#if DEBUG
return true;
#else
return false;
#endif
};
});
You can also customize the options from the appsettings.json
file:
{
"ExceptionMapper": {
"ProblemDetailsSerialization": {
"SerializeExceptions": false
}
}
}
Note that the serializer displays the debug information when in development.
Use the DisplayDebugInformation
function to display the debug info in other environments, like staging or production.
Property names
To change the way the property names are serialized, you can configure the JsonOptions
and change the PropertyNamingPolicy
property.
.NET 8+
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseUpper;
});
.NET 6 and .NET 7
builder.Services.Configure<JsonOptions>(options => {
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
Dictionary keys
For .NET 7+ projects, ExceptionMapper sets the DictionaryKeyPolicy
property to the PropertyNamingPolicy
property value, so dictionaries are serialized the same way as the normal properties.
Ensuring a property is not serialized
If your custom exception has properties that should not be serialized, you can mark them with the [JsonIgnore]
attribute, like the following and the serializer will ignore them:
public class MyForbiddenException : Exception
{
public MyForbiddenException()
: base("Accessing this resource is forbidden.")
{
}
public string CustomProperty1 => "Lorem Ipsum 1";
[JsonIgnore]
public string CustomProperty2 => "Lorem Ipsum 2";
}
Creating your own serializer
If you want to replace the default serializer, implement the IExceptionSerializer
and register it with the container, before calling AddExceptionMapper
:
services.AddSingleton<IExceptionSerializer, MySerializationHandler>();
builder.AddExceptionMapper();
Versioning
The package follows semantic versioning and uses Nerdbank.GitVersioning
to automatically version packages based on git commits.
Pre-released
Prerelease packages are packaged code not yet merged to the main
branch.
The prerelease CI builds are packaged and hosted at feedz.io, thanks to their "Open Source" subscription.
Release notes
3.0
Version 3 of ExceptionMapper is a major rewrite that simplifies the codebase and usage of the library. Here are a few important changes:
- Add support to .NET 7 and .NET 8.
- Remove transitive dependency on JSON.NET (
Newtonsoft.Json
). - Drop support for .NET Standard 2.0 because
ExceptionMapper
depends on theHttpContext
class which requires a<FrameworkReference Include="Microsoft.AspNetCore.App" />
which is not compatible withnetstandard2.0
. - Merge all assemblies in
ForEvolve.ExceptionMapper
butForEvolve.ExceptionMapper.Scrutor
and removedForEvolve.ExceptionMapper.Scrutor
altogether. - Replace the
AddMvcCore
call by registering a copy of theDefaultProblemDetailsFactory
using aTryAddSingleton
call, so you must register your customProblemDetailsFactory
implementation beforeAddExceptionMapper
. The good news is, if you are using a custom factory, theProblemDetailsSerializationHandler
will use it!Removing the copy of the
DefaultProblemDetailsFactory
class could be resolved by https://github.com/dotnet/aspnetcore/issues/49982 - Calling
AddExceptionMapper()
now registers the common exceptions and the serializer automatically. - The
Order
property was removed from theIExceptionHandler
interface. The system uses the registration order instead. - The interface now leverages a serializer implementing the
IExceptionSerializer
interface. The serializer no longer implements theIExceptionHandler
interface. - By default,
ProblemDetailsSerializationOptions
is bound to the sectionExceptionMapper:ProblemDetailsSerialization
andFallbackExceptionHandlerOptions
is bound to the sectionExceptionMapper:FallbackExceptionHandler
.
Breaking changes .NET 7+
- Remove the
ContentType
andJsonSerializerOptions
properties from theProblemDetailsSerializationOptions
class (ForEvolve.ExceptionMapper.Serialization.Json
). - The
ProblemDetailsSerializationHandler
class now leverages theIProblemDetailsService
interface to write theProblemDetails
object to the response stream instead of serializing it with theJsonSerializer
, relinquishing the control of the process to .NET. - The
ProblemDetailsSerializationHandler
leverages theJsonOptions
class to ensure the names are formatted according to thePropertyNamingPolicy
object. The default iscamelCase
. - ExceptionMapper sets the
DictionaryKeyPolicy
property to thePropertyNamingPolicy
property value so dictionaries are serialized the same way as the normal properties.
2.0 (deprecated)
- Drop .NET Core 3.1 support
- Add support for .NET 6.0
1.1 (deprecated)
- Add a handler that serializes exceptions to
ProblemDetails
(JSON) - Add the
ForEvolve.ExceptionMapper.Serialization.Json
project
1.0 (deprecated)
- Initial release (not yet released).
Found a bug or have a feature request?
Please open an issue and be as clear as possible; see How to contribute? for more information.
How to contribute?
If you would like to contribute to the project, first, thank you for your interest, and please read Contributing to ForEvolve open source projects for more information.
Contributor Covenant Code of Conduct
Also, please read the Contributor Covenant Code of Conduct that applies to all ForEvolve repositories.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- ForEvolve.Core (>= 2.3.5)
-
net7.0
- ForEvolve.Core (>= 2.3.5)
-
net8.0
- ForEvolve.Core (>= 2.3.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
3.0.41 | 184 | 2/13/2024 |
3.0.38 | 123 | 2/13/2024 |
3.0.30 | 124 | 2/10/2024 |
3.0.29-gfb178e8663 | 408 | 8/13/2023 |
2.0.15 | 469 | 11/22/2021 |
2.0.13 | 404 | 11/22/2021 |
1.1.9-g780e75793e | 275 | 1/8/2021 |
1.0.25-g3a7a25efb3 | 296 | 8/26/2020 |
1.0.24-gff560b4837 | 352 | 7/3/2020 |