Skip to content

Exceptions ​

The package defines no exceptions of its own, and no handler for yours. It defines IExceptionMapper, which decides which exceptions you answer and what each one becomes on the wire, and ErrorMapping, the answer itself. Writing the handler that pairs them is yours to do, as it is for every package in this repository.

IExceptionMapper ​

Turns one exception into the status code, error code, and message it should produce. The exception stays plain and carries only its own data, so a domain type never names an HTTP status or a message file key.

csharp
public sealed class AccountLockedException(
    DateTimeOffset lockoutEnd
) : Exception
{
    public DateTimeOffset LockoutEnd { get; } = lockoutEnd;
}
csharp
using Microsoft.AspNetCore.Http;
using AlmightyShogun.AspNet.Core;

public sealed class AppExceptionMapper : IExceptionMapper
{
    public ErrorMapping? Map(Exception exception) => exception switch
    {
        AccountLockedException lockedOut => new ErrorMapping
        {
            StatusCode = StatusCodes.Status423Locked,
            Code = "account_locked_out",
            MessageKey = "auth.locked-out",
            MessageParameters = [lockedOut.LockoutEnd]
        },

        _ => null
    };
}
csharp
using Microsoft.AspNetCore.Http;
using AlmightyShogun.AspNet.Core;
using Microsoft.AspNetCore.Diagnostics;
using AlmightyShogun.AspNet.Localization;

public sealed class AppExceptionHandler(
    AppExceptionMapper exceptionMapper,
    IMessageResolver messageResolver,
    IHttpErrorResponseWriter responseWriter
) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken
    )
    {
        if (httpContext.Response.HasStarted
            || exceptionMapper.Map(exception) is not { } mapping)
        {
            return false;
        }

        await responseWriter.WriteAsync(
            httpContext,
            mapping.StatusCode,
            mapping.Code,
            messageResolver.Resolve(
                mapping.MessageKey,
                mapping.MessageParameters
            ),
            cancellationToken
        );

        return true;
    }
}

WARNING

Register your handler before AddExceptionHandling. Handlers run in registration order and the fallback there answers every exception, so a handler registered after it never runs.

Map ​

Called on the exception path of a failing request with whatever was thrown. Return an ErrorMapping to answer, or null to decline, which is what lets the handler return false and pass the exception to the ones behind it.

A mapper is normally registered as a singleton and runs on a failing request, so it has to be thread-safe. Keep it a pattern match; it is the wrong place to read configuration or touch a database.

Type signature ​

csharp
public interface IExceptionMapper
{
    ErrorMapping? Map(Exception exception);
}

All packages are released under the MIT License.