Changelog
All notable changes to ErrorLens.ErrorHandling will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[1.4.0] - 2026-02-27
Added
New Package: ErrorLens.ErrorHandling.FluentValidation (.NET 6-10)
FluentValidationExceptionHandler(Order 110) catchesFluentValidation.ValidationException- Automatic mapping of FluentValidation failures to structured
fieldErrors/globalErrors FluentValidationErrorCodeMappingmaps 14 built-in validators toDefaultErrorCodesconstants- Severity filtering via
FluentValidationOptions.IncludeSeverities(default: Error only) - Nested property name support with segment-by-segment camelCase conversion
- Custom error codes from
.WithErrorCode()preserved exactly - Setup:
services.AddErrorHandlingFluentValidation() - Dependency: FluentValidation >= 11.0.0
Configurable 5xx Fallback Message
ErrorHandlingOptions.FallbackMessageproperty (default:"An unexpected error occurred")- Customize the generic safe message for unhandled server errors
- 4xx exceptions are unaffected (preserve original messages)
- Configurable via code,
appsettings.json, or YAML
Customizable Built-in Handler Messages
ErrorHandlingOptions.BuiltInMessagesdictionary keyed by error code- Override default messages for
MESSAGE_NOT_READABLE,TYPE_MISMATCH,BAD_REQUEST,VALIDATION_FAILED - Partial configuration supported — unset keys use existing defaults
- Applied to
JsonExceptionHandler,TypeMismatchExceptionHandler,BadRequestExceptionHandler,ValidationExceptionHandler
Additional Error Code Strategies
ErrorCodeStrategy.KebabCase:UserNotFoundException→user-not-foundErrorCodeStrategy.PascalCase:UserNotFoundException→UserNotFoundErrorCodeStrategy.DotSeparated:UserNotFoundException→user.not.found- Correct acronym handling:
HTTPException→http-exception/HTTP/http.exception
RetryAfter Field Name Customization
JsonFieldNamesOptions.RetryAfterproperty (default:"retryAfter")- Customize the retry-after field name in rate limit JSON responses
- Included in startup duplicate field name validation
Enhanced Configuration Validation
ProblemDetailTypePrefixvalidated as valid absolute URI (or empty string) at startupRateLimiting.ErrorCodevalidated as non-null/non-empty at startupRateLimiting.DefaultMessagevalidated as non-null/non-empty at startup- Clear error messages for invalid configuration
Changed
JsonExceptionHandler,TypeMismatchExceptionHandler,BadRequestExceptionHandlernow acceptIOptions<ErrorHandlingOptions>for custom message supportErrorHandlingOptionsValidatorextended with 3 new validation rulesErrorCodeMapperextended with 3 new conversion methodsDefaultRateLimitResponseWriteruses configurableRetryAfterfield nameErrorHandlingFacadecatch-block uses configurableFallbackMessage
[1.3.1] - 2026-02-21
Added
IncludeRejectedValuesoption (default:true) — suppress rejected values in validation errors for sensitive data
Fixed
- Pipeline Resilience: Localization and telemetry errors no longer crash the error handling pipeline (moved inside try/catch)
- OperationCanceledException: Client-aborted requests now propagate naturally instead of returning 500 JSON
- Field-Specific Localization:
LocalizeFieldErrortries composite key (fieldName.errorCode) before falling back to error code - ProblemDetails Custom Field Names: Extension keys now use configured
JsonFieldNamesinstead of hardcoded values - ProblemDetails Null Code:
BuildTypeUrireturnsabout:blankfor null error codes instead of throwing - AggregateExceptionHandler: Cached handler resolution with type-based self-filtering (performance)
- Rate Limiting Headers:
RateLimitheader no longer includes invalidlimit=0;Retry-Afterrounds up to minimum 1 second - BadRequestExceptionHandler: Switched to allowlist-based message sanitization
- Field Name Validation: Case-insensitive duplicate detection for
JsonFieldNames - ErrorCodeMapper: Empty string guard returns
UNKNOWN_ERRORinstead of empty code - Null Guards: Added
ArgumentNullExceptionguards formessageparameter inApiFieldError,ApiGlobalError,ApiParameterError - Serialization: Cached
HashSet<string>for built-in field names (avoids per-write allocation)
Documentation
- Updated all configuration examples with
IncludeRejectedValuesoption - Updated sample YAML/JSON configs
- Improved documentation site content
[1.3.0] - 2026-02-16
Breaking Changes
AddExceptionHandler<T>()renamed toAddApiExceptionHandler<T>()- Method renamed to avoid collision with ASP.NET Core 8+ built-in
AddExceptionHandler<T>() - Migration: Update any custom handler registrations from
AddExceptionHandler<MyHandler>()toAddApiExceptionHandler<MyHandler>()
- Method renamed to avoid collision with ASP.NET Core 8+ built-in
Added
OpenTelemetry Distributed Tracing
ErrorHandlingActivitySourcewith sharedActivitySource("ErrorLens.ErrorHandling")- Automatic
Activityspans inErrorHandlingFacade.HandleException()with OTel semantic conventions - Tags:
error.code,error.type,http.response.status_code - Exception event with
exception.type,exception.message,exception.stacktrace - Zero new dependencies — uses
System.Diagnostics.Activityfrom runtime - Zero overhead when no
ActivityListeneris subscribed
Error Message Localization
IErrorMessageLocalizerabstraction withLocalize()andLocalizeFieldError()methodsNoOpErrorMessageLocalizerdefault (pass-through, registered viaTryAddSingleton)StringLocalizerErrorMessageLocalizer<TResource>bridge toIStringLocalizer<T>- Opt-in via
AddErrorHandlingLocalization<TResource>() - Localizes top-level message, field errors, global errors, and parameter errors
- New dependency:
Microsoft.Extensions.Localization.Abstractions 8.0.0
New Package: ErrorLens.ErrorHandling.OpenApi (.NET 9+)
ErrorResponseOperationTransformerimplementingIOpenApiOperationTransformer- Auto-adds error response schemas (400, 404, 500) to all OpenAPI operations
- Respects existing
[ProducesResponseType]attributes - Reflects
UseProblemDetailFormatand customJsonFieldNamesOptionsin generated schemas - Setup:
services.AddErrorHandlingOpenApi()
New Package: ErrorLens.ErrorHandling.Swashbuckle (.NET 6-8)
ErrorResponseOperationFilterimplementingIOperationFilter- Same auto-schema behavior as OpenApi package for Swashbuckle/Swagger
- Setup:
services.AddErrorHandlingSwashbuckle()
Rate Limiting Integration (.NET 7+)
IRateLimitResponseWriterinterface for structured 429 responsesDefaultRateLimitResponseWriterwithRetry-AfterandRateLimitheadersRateLimitingOptionsfor configuring error code, message, and header format- Optional
retryAfterproperty in response body - Supports localized rate limit messages
ErrorResponseWriter- Centralized response writing extracted into shared service
- Caches
JsonSerializerOptionsfor performance
ErrorHandlingOptionsValidator- Validates
JsonFieldNamesOptionsat startup (null, empty, duplicate checks)
- Validates
AggregateExceptionHandler (Order 50)
- Built-in handler for
AggregateExceptiontypes - Unwraps single-inner-exception aggregates and re-dispatches to appropriate specific handler
- Multi-exception aggregates delegate to fallback handler
- Automatically registered with default configuration
- Built-in handler for
5xx Safe Message Behavior
- All 5xx-class errors (500-599) now return generic safe message: "An unexpected error occurred"
- Prevents information disclosure of internal system details (database connection strings, file paths, stack traces)
- 4xx errors preserve original user-facing messages
TypeMismatchExceptionHandler Generic Message
- Handler now returns generic "A type conversion error occurred" message
- Prevents exposure of internal type conversion details
BadRequestStatusCode Usage
- Handler now uses actual
StatusCodeproperty fromBadHttpRequestException - Correctly returns 400, 408, 413, etc. based on framework's status code
- Handler now uses actual
Message Sanitization
BadRequestExceptionHandlersanitizes Kestrel-internal error messages- Replaces framework-specific details with safe "Bad request" message
OperationCanceledException → 499
OperationCanceledExceptionandTaskCanceledExceptionnow map to HTTP 499 (Client Closed Request)- Follows nginx convention for client-disconnected requests
reloadOnChange Default Changed
- YAML configuration
reloadOnChangenow defaults tofalse - Documented that application restart is required for config changes
- YAML configuration
ResponseErrorPropertyAttribute Cleanup
- Removed
AttributeTargets.MethodfromAttributeUsage - Now targets
AttributeTargets.Propertyonly (as documented)
- Removed
Changed
AddApiExceptionHandler<T>()now usesTryAddEnumerablefor idempotent registration (prevents duplicates on double-call)AddErrorResponseCustomizer<T>()now usesTryAddEnumerablefor idempotent registration- Integration packages use
IOptions<T>pattern instead ofBuildServiceProvider()anti-pattern - All package versions aligned to 1.3.0
Removed
IHttpStatusFromExceptionMapperinterface (orphaned, never implemented or registered)DefaultLoggingFilterclass (replaced byILoggingFilterenumerable pattern)
Fixed
Operator Precedence Bug
ModelStateValidationExceptionHandler.InferValidationTypefixed with explicit parentheses- Messages containing "json" no longer misclassified when combined with other keywords
Safe Pattern Matching
- All handler
Handle()methods now use safe pattern matching (is not) - Prevents
InvalidCastExceptionwhen handlers called without priorCanHandle()check
- All handler
Unified ToCamelCase
StringUtils.ToCamelCasenow shared acrossModelStateValidationExceptionHandler,ValidationExceptionHandler, andExceptionMetadataCache- Ensures consistent dotted-path handling (e.g.,
Address.ZipCode→address.zipCode)
ErrorCodeMapper Acronym Regex
- Fixed regex to correctly handle consecutive uppercase letters (acronyms)
HTTPConnection→HTTP_CONNECTION(notH_T_T_P_CONNECTION)IOError→IO_ERROR(notI_O_ERROR)
Null Safety
- Added
ArgumentNullException.ThrowIfNullguards to all model constructors - Added
ArgumentOutOfRangeExceptionforResponseStatusAttribute(valid HTTP range: 100-599)
- Added
Serialization Fixes
ApiErrorResponseConverterfilters duplicate JSON keys (extension properties matching built-in field names)RejectedValueserialization now passes parentJsonSerializerOptionsfor consistent naming policyApiErrorResponseConverter.ReadthrowsNotSupportedException(write-only contract)
ProblemDetailFactory Fixes
ProblemDetailConvertToKebabCaseoption now respected (false skips kebab-case conversion)- Extension keys can no longer overwrite library-set keys (
type,title,status, etc.) - List contents copied instead of sharing mutable references
Pipeline Resilience
ErrorResponseWriterchecksResponse.HasStartedbefore writing (graceful skip if response already started)ErrorHandlingMiddlewarepassescontext.RequestAbortedasCancellationTokento response writerErrorHandlingFacadeusesExceptionDispatchInfo.Capture(exception).Throw()to preserve stack trace when disabledErrorHandlingFacadematerializes_customizerswith.ToList()to prevent lazy enumerable issues- Safety-net in facade logs both handler exception AND original exception when handler throws
DI Improvements
- Built-in handler registrations use
TryAddEnumerablefor idempotency - Multiple
AddErrorHandling()calls no longer create duplicate handler registrations
- Built-in handler registrations use
Test Coverage
New Tests: 175 new tests added (total: 350 tests, up from 175)
- Null safety tests for all model constructors
- Range validation tests for
ResponseStatusAttribute - Startup validation tests for
JsonFieldNamesOptions - Handler tests:
AggregateExceptionHandler,TypeMismatchExceptionHandler,BadRequestExceptionHandler - Mapper tests: acronym regex,
OperationCanceledException→ 499 - Integration tests: middleware, exception handler, DI registration
- Security tests: 10+ information disclosure scenarios
- Serialization tests: duplicate key filtering,
RejectedValueoptions,Readthrows
Fixed Tests
ErrorResponseWriterTests: Replaced false-positive caching and cancellation tests with real assertionsProblemDetailFactoryTests: Fixed swappedApiFieldErrorconstructor argsLoggingServiceTests: Replaced weak.ReceivedCalls()assertions with specific log level verificationErrorHandlingFacadeTests: Added safety-net test (handler throws → both exceptions logged)
Coverage: 93.1% line coverage (exceeds 90% requirement)
CI/CD
- CI and Release workflows updated to pack and publish all 4 NuGet packages
- Release summary includes links for all 4 packages
[1.1.1] - 2026-02-12
Fixed
Critical Bug:
ILoggingServicewas not registered in DI container, causing exception logging to be silently disabled in v1.1.0- Added
services.TryAddSingleton<ILoggingService, LoggingService>()toRegisterCoreServices() - Logging now works correctly with all providers (console, Seq, Serilog, etc.)
- Added
Documentation: Handler ordering table in
custom-handlers.mdandindex.adocnow clearly marksJsonExceptionHandlerandTypeMismatchExceptionHandleras opt-in (not registered by default)
Added
- Sample Project READMEs: Comprehensive documentation for all three samples
- MinimalApiSample: Zero-config setup guide with curl examples
- FullApiSample: Custom handlers and customizers walkthrough
- ShowcaseSample: Feature matrix with YAML config explanation
[1.1.0] - 2026-02-11
Added
Custom JSON Field Names
JsonFieldNamesOptionsnow fully wired into serialization pipeline- Custom
ApiErrorResponseConverterfor runtime-configurable field names - Rename any JSON property:
code→type,message→detail, etc. - Applies to top-level response and all nested error objects
- Expanded from 5 to 10 configurable field names (added Status, Property, RejectedValue, Path, Parameter)
YAML Configuration Support
AddYamlErrorHandling()extension method forIConfigurationBuilder- Full
errorhandling.ymltemplate with all options documented - Same
ErrorHandlingsection name as JSON configuration - Dependency: NetEscapades.Configuration.Yaml 3.1.0
ShowcaseSample Project
- Comprehensive sample demonstrating all features in one project
- 5 controllers: BasicErrors, Validation, Attributes, ConfigDriven, ProblemDetails
- Custom exception handler (InfrastructureExceptionHandler)
- Response customizer (RequestMetadataCustomizer) with traceId, timestamp, path
- YAML configuration with custom field names, exception mappings, log levels
Professional Documentation
- AsciiDoc index page (
docs/index.adoc) with feature matrix - Guides: Getting Started, Configuration, Validation Errors, Logging
- Features: Custom Handlers, Attributes, Response Customization, Problem Details, JSON Field Names
- API Reference with all public types, interfaces, and extension methods
- Full YAML configuration template
- AsciiDoc index page (
OverrideModelStateValidation Option
- Opt-in interception of
[ApiController]automatic model validation - Returns ErrorLens structured
fieldErrorsformat instead of ASP.NET Core's defaultProblemDetails ModelStateValidationExceptionHandler(Order 90) convertsModelStateDictionaryinto structured response- Configurable via code, JSON, or YAML (
OverrideModelStateValidation: true)
- Opt-in interception of
Expanded Test Coverage
- 18 unit tests for ApiErrorResponseConverter
- 5 integration tests for JsonFieldNames end-to-end
- 10 integration tests for YAML configuration binding
- Total: 175 tests (up from 142)
Changed
- README.md rewritten with badges, YAML/JSON side-by-side config, custom field names section, samples table
[1.0.0] - 2026-02-10
Added
Zero-Config Exception Handling
- Automatic structured JSON error responses for all unhandled exceptions
- Default error code generation using ALL_CAPS strategy
- HTTP status code mapping based on exception types
Validation Error Support
- Field-level validation error details with
fieldErrorsarray - Property name, error code, message, and rejected value included
- Support for DataAnnotations validation exceptions
- Global and parameter-level error support
- Field-level validation error details with
Configuration via appsettings.json
- Custom error codes per exception type
- Custom error messages per exception type
- HTTP status code overrides
- Field-specific error code/message customization
- Superclass hierarchy search option
Custom Exception Attributes
[ResponseErrorCode]- Define custom error code on exception class[ResponseErrorProperty]- Include exception property in response[ResponseStatus]- Define HTTP status code on exception class- Reflection caching for performance
Custom Exception Handlers
IApiExceptionHandlerinterface for custom handlingAbstractApiExceptionHandlerbase class with default Order- Priority ordering (lower Order = higher priority)
- Built-in handlers: ModelStateValidation, Validation, BadRequest (Json and TypeMismatch available but not registered by default)
RFC 9457 Problem Details Format
- Optional RFC 9457 compliant response format
- Configurable type URI prefix
- Automatic kebab-case conversion for error codes
- Extension properties for additional data
Logging Configuration
- Configurable logging verbosity (None, MessageOnly, WithStacktrace)
- Per-HTTP-status log level configuration
- Full stacktrace options per status code or exception type
- Custom logging filters
Response Customization
IApiErrorResponseCustomizerinterface- Add global properties to all error responses
- Multiple customizers with order of execution
Multi-Target Framework Support
- .NET 6.0, 7.0, 8.0, 9.0, 10.0
IExceptionHandlerfor .NET 8+ (native)IMiddlewarefor .NET 6/7 (fallback)
Technical Details
- System.Text.Json for serialization
- Thread-safe reflection caching with ConcurrentDictionary
- Minimal dependencies (Microsoft.Extensions.Options, Microsoft.Extensions.Logging.Abstractions)
- Full XML documentation
- 142 unit and integration tests