55using System . Diagnostics . CodeAnalysis ;
66using System . Linq ;
77using System . Text ;
8+ using System . Text . Json ;
9+ using System . Text . Json . Serialization ;
810using Microsoft . AspNetCore . Builder ;
911using Microsoft . AspNetCore . Diagnostics . RazorViews ;
1012using Microsoft . AspNetCore . Hosting ;
1113using Microsoft . AspNetCore . Http ;
1214using Microsoft . AspNetCore . Http . Features ;
15+ using Microsoft . AspNetCore . Http . Json ;
1316using Microsoft . AspNetCore . Mvc ;
1417using Microsoft . AspNetCore . Routing ;
1518using Microsoft . Extensions . FileProviders ;
@@ -34,6 +37,7 @@ internal class DeveloperExceptionPageMiddlewareImpl
3437 private readonly ExceptionDetailsProvider _exceptionDetailsProvider ;
3538 private readonly Func < ErrorContext , Task > _exceptionHandler ;
3639 private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue ( "text/html" ) ;
40+ private readonly ExtensionsExceptionJsonContext _serializationContext ;
3741 private readonly IProblemDetailsService ? _problemDetailsService ;
3842
3943 /// <summary>
@@ -45,6 +49,7 @@ internal class DeveloperExceptionPageMiddlewareImpl
4549 /// <param name="hostingEnvironment"></param>
4650 /// <param name="diagnosticSource">The <see cref="DiagnosticSource"/> used for writing diagnostic messages.</param>
4751 /// <param name="filters">The list of registered <see cref="IDeveloperPageExceptionFilter"/>.</param>
52+ /// <param name="jsonOptions">The <see cref="JsonOptions"/> used for serialization.</param>
4853 /// <param name="problemDetailsService">The <see cref="IProblemDetailsService"/> used for writing <see cref="ProblemDetails"/> messages.</param>
4954 public DeveloperExceptionPageMiddlewareImpl (
5055 RequestDelegate next ,
@@ -53,6 +58,7 @@ public DeveloperExceptionPageMiddlewareImpl(
5358 IWebHostEnvironment hostingEnvironment ,
5459 DiagnosticSource diagnosticSource ,
5560 IEnumerable < IDeveloperPageExceptionFilter > filters ,
61+ IOptions < JsonOptions > ? jsonOptions = null ,
5662 IProblemDetailsService ? problemDetailsService = null )
5763 {
5864 if ( next == null )
@@ -77,15 +83,22 @@ public DeveloperExceptionPageMiddlewareImpl(
7783 _diagnosticSource = diagnosticSource ;
7884 _exceptionDetailsProvider = new ExceptionDetailsProvider ( _fileProvider , _logger , _options . SourceCodeLineCount ) ;
7985 _exceptionHandler = DisplayException ;
86+ _serializationContext = CreateSerializationContext ( jsonOptions ? . Value ) ;
8087 _problemDetailsService = problemDetailsService ;
81-
8288 foreach ( var filter in filters . Reverse ( ) )
8389 {
8490 var nextFilter = _exceptionHandler ;
8591 _exceptionHandler = errorContext => filter . HandleExceptionAsync ( errorContext , nextFilter ) ;
8692 }
8793 }
8894
95+ private static ExtensionsExceptionJsonContext CreateSerializationContext ( JsonOptions ? jsonOptions )
96+ {
97+ // Create context from configured options to get settings such as PropertyNamePolicy and DictionaryKeyPolicy.
98+ jsonOptions ??= new JsonOptions ( ) ;
99+ return new ExtensionsExceptionJsonContext ( new JsonSerializerOptions ( jsonOptions . SerializerOptions ) ) ;
100+ }
101+
89102 /// <summary>
90103 /// Process an individual request.
91104 /// </summary>
@@ -172,21 +185,7 @@ private async Task DisplayExceptionContent(ErrorContext errorContext)
172185
173186 if ( _problemDetailsService != null )
174187 {
175- var problemDetails = new ProblemDetails
176- {
177- Title = TypeNameHelper . GetTypeDisplayName ( errorContext . Exception . GetType ( ) ) ,
178- Detail = errorContext . Exception . Message ,
179- Status = httpContext . Response . StatusCode
180- } ;
181-
182- problemDetails . Extensions [ "exception" ] = new
183- {
184- Details = errorContext . Exception . ToString ( ) ,
185- Headers = httpContext . Request . Headers ,
186- Path = httpContext . Request . Path . ToString ( ) ,
187- Endpoint = httpContext . GetEndpoint ( ) ? . ToString ( ) ,
188- RouteValues = httpContext . Features . Get < IRouteValuesFeature > ( ) ? . RouteValues ,
189- } ;
188+ var problemDetails = CreateProblemDetails ( errorContext , httpContext ) ;
190189
191190 await _problemDetailsService . WriteAsync ( new ( )
192191 {
@@ -214,6 +213,31 @@ await _problemDetailsService.WriteAsync(new()
214213 }
215214 }
216215
216+ [ UnconditionalSuppressMessage ( "Trimming" , "IL2026" , Justification = "Values set on ProblemDetails.Extensions are supported by the default writer." ) ]
217+ [ UnconditionalSuppressMessage ( "AOT" , "IL3050" , Justification = "Values set on ProblemDetails.Extensions are supported by the default writer." ) ]
218+ private ProblemDetails CreateProblemDetails ( ErrorContext errorContext , HttpContext httpContext )
219+ {
220+ var problemDetails = new ProblemDetails
221+ {
222+ Title = TypeNameHelper . GetTypeDisplayName ( errorContext . Exception . GetType ( ) ) ,
223+ Detail = errorContext . Exception . Message ,
224+ Status = httpContext . Response . StatusCode
225+ } ;
226+
227+ // Problem details source gen serialization doesn't know about IHeaderDictionary or RouteValueDictionary.
228+ // Serialize payload to a JsonElement here. Problem details serialization can write JsonElement in extensions dictionary.
229+ problemDetails . Extensions [ "exception" ] = JsonSerializer . SerializeToElement ( new ExceptionExtensionData
230+ (
231+ Details : errorContext . Exception . ToString ( ) ,
232+ Headers : httpContext . Request . Headers ,
233+ Path : httpContext . Request . Path . ToString ( ) ,
234+ Endpoint : httpContext . GetEndpoint ( ) ? . ToString ( ) ,
235+ RouteValues : httpContext . Features . Get < IRouteValuesFeature > ( ) ? . RouteValues
236+ ) , _serializationContext . ExceptionExtensionData ) ;
237+
238+ return problemDetails ;
239+ }
240+
217241 private Task DisplayCompilationException (
218242 HttpContext context ,
219243 ICompilationException compilationException )
@@ -328,3 +352,10 @@ private Task DisplayRuntimeException(HttpContext context, Exception ex)
328352 return errorPage . ExecuteAsync ( context ) ;
329353 }
330354}
355+
356+ internal record ExceptionExtensionData ( string Details , IHeaderDictionary Headers , string Path , string ? Endpoint , RouteValueDictionary ? RouteValues ) ;
357+
358+ [ JsonSerializable ( typeof ( ExceptionExtensionData ) ) ]
359+ internal sealed partial class ExtensionsExceptionJsonContext : JsonSerializerContext
360+ {
361+ }
0 commit comments