From 4be294e80091e525f4677b130f909fba90af3a8a Mon Sep 17 00:00:00 2001 From: Alon Kolyakov Date: Tue, 17 Mar 2026 23:53:23 +0200 Subject: [PATCH 1/2] Include exception in ProblemDetailsContext for controller error responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using ExceptionHandlerMiddleware with controllers, the CustomizeProblemDetails callback receives a ProblemDetailsContext with a null Exception. This happens because DefaultProblemDetailsFactory creates a new context internally without pulling the exception from the HttpContext features. The Minimal API path (DefaultProblemDetailsWriter) works correctly because it passes the original context — which already has the exception set by the middleware — straight through to the callback. The fix: in DefaultProblemDetailsFactory.ApplyProblemDetailsDefaults, resolve the exception from IExceptionHandlerFeature on the HttpContext before invoking the customize callback. When no exception handler feature is present (e.g., validation errors, manual status codes), the exception remains null as before. Changes: - DefaultProblemDetailsFactory: read exception from IExceptionHandlerFeature and set it on the ProblemDetailsContext - Add Diagnostics.Abstractions reference to Mvc.Core - Add tests verifying exception propagation in both scenarios Fixes #65697 --- .../DefaultProblemDetailsFactory.cs | 4 +- .../src/Microsoft.AspNetCore.Mvc.Core.csproj | 1 + .../ProblemDetailsFactoryTest.cs | 65 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs b/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs index 749aa0d81de5..f2f3031b6957 100644 --- a/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs +++ b/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs @@ -4,6 +4,7 @@ #nullable enable using System.Diagnostics; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; @@ -108,6 +109,7 @@ private void ApplyProblemDetailsDefaults(HttpContext httpContext, ProblemDetails problemDetails.Extensions["traceId"] = traceId; } - _configure?.Invoke(new() { HttpContext = httpContext!, ProblemDetails = problemDetails }); + var exception = httpContext?.Features.Get()?.Error; + _configure?.Invoke(new() { HttpContext = httpContext!, ProblemDetails = problemDetails, Exception = exception }); } } diff --git a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj index 467923182b89..97723e5584bc 100644 --- a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj @@ -53,6 +53,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute + diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs index 2695b167ff25..142d16af6538 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; @@ -169,6 +170,65 @@ public void CreateValidationProblemDetails_WithTitleAndInstance() }); } + [Fact] + public void CreateProblemDetails_IncludesExceptionFromExceptionHandlerFeature() + { + // Regression test for https://github.com/dotnet/aspnetcore/issues/65697 + // CustomizeProblemDetails should receive the exception when one is available. + Exception? receivedException = null; + var expectedException = new InvalidOperationException("BOOM!"); + + var options = new ApiBehaviorOptions(); + new ApiBehaviorOptionsSetup().Configure(options); + + var problemDetailsOptions = new ProblemDetailsOptions + { + CustomizeProblemDetails = context => + { + receivedException = context.Exception; + } + }; + + var factory = new DefaultProblemDetailsFactory( + Options.Create(options), + Options.Create(problemDetailsOptions)); + + var httpContext = GetHttpContext(); + httpContext.Features.Set( + new ExceptionHandlerFeature { Error = expectedException }); + + factory.CreateProblemDetails(httpContext, statusCode: 500); + + Assert.Same(expectedException, receivedException); + } + + [Fact] + public void CreateProblemDetails_ExceptionIsNullWhenNoExceptionHandlerFeature() + { + Exception? receivedException = new Exception("should be overwritten"); + + var options = new ApiBehaviorOptions(); + new ApiBehaviorOptionsSetup().Configure(options); + + var problemDetailsOptions = new ProblemDetailsOptions + { + CustomizeProblemDetails = context => + { + receivedException = context.Exception; + } + }; + + var factory = new DefaultProblemDetailsFactory( + Options.Create(options), + Options.Create(problemDetailsOptions)); + + var httpContext = GetHttpContext(); + + factory.CreateProblemDetails(httpContext, statusCode: 400); + + Assert.Null(receivedException); + } + private static DefaultHttpContext GetHttpContext() { return new DefaultHttpContext @@ -183,4 +243,9 @@ private static ProblemDetailsFactory GetProblemDetails() new ApiBehaviorOptionsSetup().Configure(options); return new DefaultProblemDetailsFactory(Options.Create(options)); } + + private sealed class ExceptionHandlerFeature : IExceptionHandlerFeature + { + public Exception Error { get; init; } = default!; + } } From 6c361e7882154b34585b7f7a7b93380abbffd34d Mon Sep 17 00:00:00 2001 From: BloodShop Date: Sun, 22 Mar 2026 15:55:19 +0200 Subject: [PATCH 2/2] fix: add nullable enable directive to fix CS8632 compilation errors The test file uses Exception? nullable annotations but was missing the #nullable enable directive, causing CS8632 compilation errors in CI. This fix enables nullable context for the test file to match the source file pattern and resolve the build failures. --- .../Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs index 142d16af6538..0259be1a74d9 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding;