Use this file to discover all available pages before exploring further.
Custom exceptions help you express domain-specific errors in your application. Problemize handles all exceptions consistently, whether they’re built-in .NET exceptions or your own custom types.
Create an exception class that inherits from Exception or a more specific base class:
ResourceNotFoundException.cs
namespace MyApi.Exceptions;public class ResourceNotFoundException : Exception{ public string ResourceType { get; } public string ResourceId { get; } public ResourceNotFoundException(string resourceType, string resourceId) : base($"{resourceType} with ID '{resourceId}' was not found") { ResourceType = resourceType; ResourceId = resourceId; } public ResourceNotFoundException(string resourceType, string resourceId, Exception innerException) : base($"{resourceType} with ID '{resourceId}' was not found", innerException) { ResourceType = resourceType; ResourceId = resourceId; }}
Always provide constructors that accept an inner exception for proper exception chaining.
2
Map to HTTP status codes
Configure your custom status code mapper to handle your custom exceptions:
MyStatusCodeMapper.cs
using FloppyShelf.Problemize.Interfaces;using MyApi.Exceptions;public class MyStatusCodeMapper : IStatusCodeMapper{ public int GetStatusCode(Exception exception) { return exception switch { ResourceNotFoundException => StatusCodes.Status404NotFound, BusinessRuleViolationException => StatusCodes.Status422UnprocessableEntity, DuplicateResourceException => StatusCodes.Status409Conflict, InsufficientPermissionsException => StatusCodes.Status403Forbidden, // Fallback to common exceptions ArgumentNullException => StatusCodes.Status400BadRequest, UnauthorizedAccessException => StatusCodes.Status401Unauthorized, _ => StatusCodes.Status500InternalServerError }; }}
3
Throw exceptions in your code
Use your custom exceptions throughout your application:
ProductService.cs
public class ProductService{ private readonly IProductRepository _repository; public Product GetProduct(int id) { var product = _repository.FindById(id); if (product == null) throw new ResourceNotFoundException("Product", id.ToString()); return product; } public void UpdateProduct(int id, UpdateProductRequest request) { var product = GetProduct(id); // Throws ResourceNotFoundException if not found if (request.Price < 0) throw new BusinessRuleViolationException("Product price cannot be negative"); product.Update(request); _repository.Save(product); }}
When a custom exception is thrown, Problemize automatically:
Maps the exception to an HTTP status code using your IStatusCodeMapper
Creates a ProblemDetails response with:
type: The exception class name (e.g., "ResourceNotFoundException")
title: "An error occured while processing your request"
detail: The exception message
instance: The HTTP method and path
requestId: The trace identifier
{ "type": "ResourceNotFoundException", "title": "An error occured while processing your request", "status": 404, "detail": "Product with ID '123' was not found", "instance": "GET /api/products/123", "requestId": "0HN4G7H8J9K0L1M2N3P4Q5R6", "activityId": "00-1234567890abcdef-1234567890abcdef-00"}
public class BusinessRuleViolationException : Exception{ public string RuleName { get; } public BusinessRuleViolationException(string ruleName, string message) : base(message) { RuleName = ruleName; }}
Usage example
public void TransferFunds(Account from, Account to, decimal amount){ if (from.Balance < amount) throw new BusinessRuleViolationException( "InsufficientFunds", $"Account {from.Number} has insufficient funds for transfer of {amount:C}" ); if (amount > from.DailyTransferLimit) throw new BusinessRuleViolationException( "DailyLimitExceeded", $"Transfer amount exceeds daily limit of {from.DailyTransferLimit:C}" ); // Perform transfer}
public class DuplicateResourceException : Exception{ public string ResourceType { get; } public string ConflictingProperty { get; } public string ConflictingValue { get; } public DuplicateResourceException( string resourceType, string conflictingProperty, string conflictingValue) : base($"{resourceType} with {conflictingProperty} '{conflictingValue}' already exists") { ResourceType = resourceType; ConflictingProperty = conflictingProperty; ConflictingValue = conflictingValue; }}
Usage example
public User CreateUser(CreateUserRequest request){ if (_repository.ExistsByEmail(request.Email)) throw new DuplicateResourceException("User", "email", request.Email); if (_repository.ExistsByUsername(request.Username)) throw new DuplicateResourceException("User", "username", request.Username); return _repository.Create(request);}
public class ResourceNotFoundException : MyApiException{ public ResourceNotFoundException(string resourceType, string resourceId) : base("RESOURCE_NOT_FOUND", $"{resourceType} with ID '{resourceId}' was not found") { }}public class BusinessRuleViolationException : MyApiException{ public BusinessRuleViolationException(string ruleName, string message) : base($"BUSINESS_RULE_{ruleName.ToUpperInvariant()}", message) { }}
Exception hierarchies make it easier to catch related exceptions and implement cross-cutting concerns like logging.
Exception names should clearly describe the error condition:
✅ ResourceNotFoundException
✅ DuplicateEmailException
❌ Error1Exception
❌ CustomException
Include helpful messages
Exception messages should help developers understand what went wrong:
// Good: Specific and actionablethrow new ResourceNotFoundException("Product", productId.ToString());// Message: "Product with ID '123' was not found"// Bad: Vague and unhelpfulthrow new Exception("Error");
Add relevant properties
Include properties that provide context about the error:
public class ValidationException : Exception{ public Dictionary<string, string[]> Errors { get; } public ValidationException(Dictionary<string, string[]> errors) : base("One or more validation errors occurred") { Errors = errors; }}
Don't expose sensitive data
Never include passwords, tokens, or other sensitive information in exception messages:
// Bad: Exposes passwordthrow new Exception($"Login failed for {username} with password {password}");// Good: No sensitive datathrow new UnauthorizedAccessException("Invalid username or password");