Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
ASP.NET Core MVC frequently converts structured model-binding and validation errors into strings before application code can process them.
This makes it difficult to separate:
- diagnostic information that should be logged,
- machine-readable information that may safely be returned to clients, such as an error code or JSON path,
- potentially sensitive information that should not be exposed.
For example, System.Text.Json.JsonException contains structured information such as Path, LineNumber and BytePositionInLine.
SystemTextJsonInputFormatter catches the original JsonException, obtains its path, passes the exception through WrapExceptionForModelState, and adds the resulting exception to ModelState:
SystemTextJsonInputFormatter.cs — JsonException handling, ASP.NET Core v10.0.9, lines 88–99
The same class contains WrapExceptionForModelState(JsonException). When JsonOptions.AllowInputFormatterExceptionMessages is enabled it returns an InputFormatterException containing the original JsonException; otherwise it returns the original exception:
SystemTextJsonInputFormatter.cs — WrapExceptionForModelState, ASP.NET Core v10.0.9, lines 133–144
ModelStateDictionary.TryAddModelError(string, Exception, ModelMetadata) explicitly treats InputFormatterException as a signal that its message is safe to expose to clients and replaces the exception with only exception.Message:
ModelStateDictionary.cs — InputFormatterException handling, ASP.NET Core v10.0.9, lines 305-310
Setting AllowInputFormatterExceptionMessages = false preserves the original JsonException, but this is a JSON-specific and non-obvious mechanism. An application may instead want to log the complete exception, return a safe property such as the JSON path, but suppress sensitive information contained in the exception message, such as an internal CLR type name.
A similar loss of structured information occurs during DataAnnotations validation.
For example, an application may return a custom ValidationResult containing a machine-readable error code:
class ApiValidationResult : ValidationResult
{
public string ErrorCode { get; init; }
}
DataAnnotationsModelValidator.Validate receives the original ValidationResult from ValidationAttribute.GetValidationResult, but subsequently creates new ModelValidationResult instances containing only the member name and error message:
DataAnnotationsModelValidator.cs — ValidationResult to ModelValidationResult conversion, ASP.NET Core v10.0.9, lines 99–123
ValidationVisitor.ValidateNode then reduces this representation further by adding only result.Message to ModelState:
ModelState.TryAddModelError(key, result.Message);
ValidationVisitor.cs — validation results added to ModelState, ASP.NET Core v10.0.9, line 237
Consequently, application code in InvalidModelStateResponseFactory cannot access ErrorCode or other structured information originally provided by the validation result.
This is also a longstanding use case. For example, this Stack Overflow question asks specifically how to return an error code in a custom ValidationResult and access it after ASP.NET Core validation:
Stack Overflow — Return Custom ValidationResult and access it from controller in ASP.NET Core
Once structured information has been flattened into an error-message string, applications cannot reliably decide which portions should be exposed to clients and which should only be logged.
Describe the solution you'd like
Provide a general mechanism for model-binding and validation errors to preserve structured information through ModelState.
The exact API is open for discussion. Possible approaches include:
- allowing
ModelError / ModelValidationResult to carry structured metadata or the original error object, or
- providing another structured error abstraction that survives until
InvalidModelStateResponseFactory.
The important requirement is that ASP.NET Core should avoid irreversibly reducing an error to a string before application code has an opportunity to apply its own logging, security and API-response policy.
For example, an application should be able to produce:
Server log:
original exception + complete diagnostic information
Client:
field/path: $.customer.type
code: INVALID_VALUE
message: Invalid value
without parsing framework-generated error-message strings.
The goal is not to expose more information by default. The goal is the opposite: preserve structured information internally long enough for applications to make an explicit security decision about what to log and what to expose.
Additional context
The current ASP.NET Core 10 implementation demonstrates two related lossy paths.
DataAnnotations
ValidationResult
↓
DataAnnotationsModelValidator
↓
ModelValidationResult(MemberName, Message)
↓
ValidationVisitor
↓
ModelError(ErrorMessage)
JSON input with AllowInputFormatterExceptionMessages = true
JsonException
↓
InputFormatterException
↓
ModelStateDictionary
↓
ModelError(ErrorMessage)
Related issues include:
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
ASP.NET Core MVC frequently converts structured model-binding and validation errors into strings before application code can process them.
This makes it difficult to separate:
For example,
System.Text.Json.JsonExceptioncontains structured information such asPath,LineNumberandBytePositionInLine.SystemTextJsonInputFormattercatches the originalJsonException, obtains its path, passes the exception throughWrapExceptionForModelState, and adds the resulting exception toModelState:SystemTextJsonInputFormatter.cs —
JsonExceptionhandling, ASP.NET Core v10.0.9, lines 88–99The same class contains
WrapExceptionForModelState(JsonException). WhenJsonOptions.AllowInputFormatterExceptionMessagesis enabled it returns anInputFormatterExceptioncontaining the originalJsonException; otherwise it returns the original exception:SystemTextJsonInputFormatter.cs —
WrapExceptionForModelState, ASP.NET Core v10.0.9, lines 133–144ModelStateDictionary.TryAddModelError(string, Exception, ModelMetadata)explicitly treatsInputFormatterExceptionas a signal that its message is safe to expose to clients and replaces the exception with onlyexception.Message:ModelStateDictionary.cs —
InputFormatterExceptionhandling, ASP.NET Core v10.0.9, lines 305-310Setting
AllowInputFormatterExceptionMessages = falsepreserves the originalJsonException, but this is a JSON-specific and non-obvious mechanism. An application may instead want to log the complete exception, return a safe property such as the JSON path, but suppress sensitive information contained in the exception message, such as an internal CLR type name.A similar loss of structured information occurs during DataAnnotations validation.
For example, an application may return a custom
ValidationResultcontaining a machine-readable error code:DataAnnotationsModelValidator.Validatereceives the originalValidationResultfromValidationAttribute.GetValidationResult, but subsequently creates newModelValidationResultinstances containing only the member name and error message:DataAnnotationsModelValidator.cs —
ValidationResulttoModelValidationResultconversion, ASP.NET Core v10.0.9, lines 99–123ValidationVisitor.ValidateNodethen reduces this representation further by adding onlyresult.MessagetoModelState:ValidationVisitor.cs — validation results added to
ModelState, ASP.NET Core v10.0.9, line 237Consequently, application code in
InvalidModelStateResponseFactorycannot accessErrorCodeor other structured information originally provided by the validation result.This is also a longstanding use case. For example, this Stack Overflow question asks specifically how to return an error code in a custom
ValidationResultand access it after ASP.NET Core validation:Stack Overflow — Return Custom ValidationResult and access it from controller in ASP.NET Core
Once structured information has been flattened into an error-message string, applications cannot reliably decide which portions should be exposed to clients and which should only be logged.
Describe the solution you'd like
Provide a general mechanism for model-binding and validation errors to preserve structured information through
ModelState.The exact API is open for discussion. Possible approaches include:
ModelError/ModelValidationResultto carry structured metadata or the original error object, orInvalidModelStateResponseFactory.The important requirement is that ASP.NET Core should avoid irreversibly reducing an error to a string before application code has an opportunity to apply its own logging, security and API-response policy.
For example, an application should be able to produce:
without parsing framework-generated error-message strings.
The goal is not to expose more information by default. The goal is the opposite: preserve structured information internally long enough for applications to make an explicit security decision about what to log and what to expose.
Additional context
The current ASP.NET Core 10 implementation demonstrates two related lossy paths.
DataAnnotations
JSON input with AllowInputFormatterExceptionMessages = true
Related issues include: