Skip to content

Include exception in ProblemDetailsContext for controller error responses#65817

Open
BloodShop wants to merge 7 commits intodotnet:mainfrom
BloodShop:fix/problemdetails-exception-context
Open

Include exception in ProblemDetailsContext for controller error responses#65817
BloodShop wants to merge 7 commits intodotnet:mainfrom
BloodShop:fix/problemdetails-exception-context

Conversation

@BloodShop
Copy link
Copy Markdown

Summary

When using ExceptionHandlerMiddleware with controllers and CustomizeProblemDetails, the ProblemDetailsContext.Exception is always null. The same setup with Minimal APIs correctly populates the exception.

Reproduction

builder.Services.AddControllers();
builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = context =>
    {
        // context.Exception is always null with controllers!
        if (context.Exception is not null)
        {
            context.ProblemDetails.Detail = context.Exception.Message;
        }
    };
});

app.UseExceptionHandler();
app.MapControllers();
[ApiController]
[Route("[controller]")]
public class FailController : ControllerBase
{
    [HttpGet]
    public ActionResult Get() => throw new Exception("BOOM!");
}

context.Exception is always null even though the exception is available.

Root Cause

Two different code paths handle problem details writing:

Minimal APIs (DefaultProblemDetailsWriter): Calls CustomizeProblemDetails directly with the original ProblemDetailsContext from the middleware — which already has the Exception set. Works correctly.

Controllers (DefaultApiProblemDetailsWriterDefaultProblemDetailsFactory): The factory's ApplyProblemDetailsDefaults creates a new ProblemDetailsContext for the callback, but never sets the Exception property:

// Before: exception is lost
_configure?.Invoke(new() { HttpContext = httpContext!, ProblemDetails = problemDetails });

The Fix

In DefaultProblemDetailsFactory.ApplyProblemDetailsDefaults, resolve the exception from IExceptionHandlerFeature (which the ExceptionHandlerMiddleware sets on the HttpContext) and include it in the context:

// After: exception is propagated
var exception = httpContext?.Features.Get<IExceptionHandlerFeature>()?.Error;
_configure?.Invoke(new() { HttpContext = httpContext!, ProblemDetails = problemDetails, Exception = exception });

When no IExceptionHandlerFeature is present (validation errors, manual status codes, etc.), exception is null — same behavior as before.

Changes

DefaultProblemDetailsFactory.cs

  • Read exception from IExceptionHandlerFeature on HttpContext.Features
  • Pass it to the ProblemDetailsContext in the customize callback

Microsoft.AspNetCore.Mvc.Core.csproj

  • Add reference to Microsoft.AspNetCore.Diagnostics.Abstractions (for IExceptionHandlerFeature)

ProblemDetailsFactoryTest.cs (tests)

  • Verify exception is propagated when IExceptionHandlerFeature is present
  • Verify exception is null when no feature is present

Behavior After Fix

Scenario Before After
Controller + ExceptionHandler context.Exception == null context.Exception == <thrown>
Minimal API + ExceptionHandler context.Exception == <thrown> unchanged ✅
Controller, no exception context.Exception == null unchanged ✅
Validation errors context.Exception == null unchanged ✅

Breaking Changes

None. The only change is that ProblemDetailsContext.Exception is now populated where it was previously null. Code that checked for context.Exception is not null will now correctly enter that branch.

Fixes #65697

…nses

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 dotnet#65697
@BloodShop BloodShop requested a review from a team as a code owner March 17, 2026 21:53
@github-actions github-actions Bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Mar 17, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Thanks for your PR, @@BloodShop. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Mar 17, 2026
@gfoidl gfoidl added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-problem-details and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Mar 18, 2026
BloodShop and others added 2 commits March 22, 2026 15:48
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.
@BloodShop
Copy link
Copy Markdown
Author

🔧 Fixed CI Build Failures!

Issue identified: CS8632 compilation errors due to missing directive in the test file.

Fix applied: Added to to enable nullable reference type context, matching the pattern used in the source file.

What this resolves:

  • ✅ CS8632 errors on lines 178 and 208 where was used
  • ✅ Consistent nullable context between source and test files
  • ✅ Should allow CI tests to pass

The core implementation logic remains unchanged - this was purely a compilation issue. CI should now be green! 🚀

@BloodShop
Copy link
Copy Markdown
Author

CI Failure Analysis - Flaky Test Issue

The current CI failure is unrelated to this PR's code changes:

Failed Test: Microsoft.AspNetCore.SignalR.Client.Tests.TestServerTests.WebSocketsWorks
Error: ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'

Evidence this is a known flaky test:

  • PR [blazor][wasm] JSExport for events #65897 had the identical failure and was merged today (2026-03-23)
  • ✅ This PR only modifies DefaultProblemDetailsFactory.cs (MVC error handling) - zero relation to SignalR
  • ✅ The error is a race condition in SignalR test teardown, not related to ProblemDetails functionality

Files changed in this PR:

  • src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs (exception context fix)
  • src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj (added dependency)
  • src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs (test coverage)

Previous fix applied: Added #nullable enable directive to resolve CS8632 compilation errors.

Request: Please consider re-running CI or merging despite the flaky test, as this follows the established precedent of PR #65897 which had identical infrastructure failures.

The ProblemDetailsContext fix correctly addresses issue #65697 and provides exception access in controller scenarios.

This was referenced Apr 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates community-contribution Indicates that the PR has been added by a community member feature-problem-details pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProblemDetailsContext for CustomizeProblemDetails never contains an Exception for Controllers

3 participants