Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/FloppyShelf/Problemize/llms.txt

Use this file to discover all available pages before exploring further.

By default, Problemize maps common .NET exceptions to appropriate HTTP status codes. You can customize this behavior by implementing your own status code mapper.

Understanding the default mapper

Problemize includes a StatusCodeMapper that handles common .NET exceptions:
// 400 Bad Request
ArgumentNullException
ArgumentOutOfRangeException
InvalidOperationException
ValidationException
FormatException
OverflowException
NullReferenceException

// 401 Unauthorized
UnauthorizedAccessException

// 404 Not Found
KeyNotFoundException
FileNotFoundException
DirectoryNotFoundException

// 405 Method Not Allowed
NotSupportedException

// 408 Request Timeout
TimeoutException

// 501 Not Implemented
NotImplementedException

// 500 Internal Server Error (default)
All other exceptions

Implementing a custom mapper

To create your own status code mapper, implement the IStatusCodeMapper interface:
1

Create your mapper class

Implement the IStatusCodeMapper interface with your custom logic:
MyCustomStatusCodeMapper.cs
using FloppyShelf.Problemize.Interfaces;

namespace MyApi.Exceptions;

public class MyCustomStatusCodeMapper : IStatusCodeMapper
{
    public int GetStatusCode(Exception exception)
    {
        return exception switch
        {
            // Your custom exceptions
            ResourceNotFoundException => StatusCodes.Status404NotFound,
            BusinessRuleViolationException => StatusCodes.Status422UnprocessableEntity,
            DuplicateResourceException => StatusCodes.Status409Conflict,
            RateLimitExceededException => StatusCodes.Status429TooManyRequests,
            
            // Fallback to common .NET exceptions
            ArgumentNullException => StatusCodes.Status400BadRequest,
            UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
            KeyNotFoundException => StatusCodes.Status404NotFound,
            TimeoutException => StatusCodes.Status408RequestTimeout,
            
            // Default for unknown exceptions
            _ => StatusCodes.Status500InternalServerError
        };
    }
}
You can chain exception types or add complex logic inside the switch expression based on exception properties.
2

Register your custom mapper

Pass your custom mapper to the UseExceptionHandling method in Program.cs:
Program.cs
using MyApi.Exceptions;
using FloppyShelf.Problemize;

var builder = WebApplication.CreateBuilder(args);

// Register services with custom status code mapper
builder.Services.UseExceptionHandling(
    statusCodeMapper: new MyCustomStatusCodeMapper()
);

var app = builder.Build();

// Enable exception handling middleware
app.UseExceptionHandling();

app.MapControllers();
app.Run();
3

Test your mapper

Throw your custom exceptions in your controllers and verify the correct status codes:
UserController.cs
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetUser(int id)
    {
        if (id <= 0)
            throw new ArgumentOutOfRangeException(nameof(id), "User ID must be positive");

        var user = _userService.FindById(id);
        
        if (user == null)
            throw new ResourceNotFoundException($"User with ID {id} not found");

        return Ok(user);
    }

    [HttpPost]
    public IActionResult CreateUser(CreateUserRequest request)
    {
        if (_userService.EmailExists(request.Email))
            throw new DuplicateResourceException($"User with email {request.Email} already exists");

        var user = _userService.Create(request);
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }
}

Advanced scenarios

Conditional status code mapping

You can implement complex logic based on exception properties:
public class AdvancedStatusCodeMapper : IStatusCodeMapper
{
    public int GetStatusCode(Exception exception)
    {
        return exception switch
        {
            // Map based on exception properties
            InvalidOperationException { Message: var msg } when msg.Contains("not found") 
                => StatusCodes.Status404NotFound,
            
            InvalidOperationException { Message: var msg } when msg.Contains("unauthorized") 
                => StatusCodes.Status401Unauthorized,
            
            // Check for specific error codes in custom exceptions
            BusinessException { ErrorCode: "PAYMENT_REQUIRED" } 
                => StatusCodes.Status402PaymentRequired,
            
            BusinessException { ErrorCode: "FORBIDDEN" } 
                => StatusCodes.Status403Forbidden,
            
            // Handle aggregate exceptions
            AggregateException agg when agg.InnerExceptions.Any(e => e is UnauthorizedAccessException) 
                => StatusCodes.Status401Unauthorized,
            
            _ => StatusCodes.Status500InternalServerError
        };
    }
}

Composing with the default mapper

You can delegate to the default mapper for common exceptions:
public class ComposedStatusCodeMapper : IStatusCodeMapper
{
    private readonly StatusCodeMapper _defaultMapper = new();

    public int GetStatusCode(Exception exception)
    {
        // Handle custom exceptions first
        var statusCode = exception switch
        {
            ResourceNotFoundException => StatusCodes.Status404NotFound,
            BusinessRuleViolationException => StatusCodes.Status422UnprocessableEntity,
            DuplicateResourceException => StatusCodes.Status409Conflict,
            _ => 0 // Indicate no match
        };

        // Fall back to default mapper if no custom match
        return statusCode != 0 ? statusCode : _defaultMapper.GetStatusCode(exception);
    }
}
Always include a default case in your mapper to handle unexpected exception types. Returning 500 Internal Server Error is recommended for safety.

Best practices

Be specific

Map specific exceptions before generic ones in your switch expression.

Use standard codes

Stick to standard HTTP status codes that clients understand.

Include defaults

Always provide a default case to handle unknown exceptions.

Test thoroughly

Verify each exception type returns the expected status code.

Next steps

Custom exceptions

Learn how to create custom exception types for your domain.

Validation errors

Handle validation errors with ValidationProblemDetails.

Build docs developers (and LLMs) love