Skip to content

Include exception in ProblemDetailsContext for controller error responses#65817

Open
BloodShop wants to merge 1 commit intodotnet:mainfrom
BloodShop:fix/problemdetails-exception-context
Open

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

Conversation

@BloodShop
Copy link

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
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
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProblemDetailsContext for CustomizeProblemDetails never contains an Exception for Controllers

3 participants