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..0259be1a74d9 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ProblemDetailsFactoryTest.cs @@ -1,6 +1,9 @@ // 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; using Microsoft.Extensions.DependencyInjection; @@ -169,6 +172,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 +245,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!; + } }