diff --git a/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs b/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs index 4cbd1a2dba..f51b5ade2e 100644 --- a/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs +++ b/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs @@ -42,7 +42,10 @@ namespace Microsoft.Extensions.DependencyInjection } else { - result = new ObjectResult(problemDetails); + result = new ObjectResult(problemDetails) + { + StatusCode = problemDetails.Status, + }; } result.ContentTypes.Add("application/problem+json"); result.ContentTypes.Add("application/problem+xml"); diff --git a/src/Mvc/Mvc.Core/src/ObjectResult.cs b/src/Mvc/Mvc.Core/src/ObjectResult.cs index a5a0323089..c85dfcc4ab 100644 --- a/src/Mvc/Mvc.Core/src/ObjectResult.cs +++ b/src/Mvc/Mvc.Core/src/ObjectResult.cs @@ -54,14 +54,21 @@ namespace Microsoft.AspNetCore.Mvc throw new ArgumentNullException(nameof(context)); } + if (Value is ProblemDetails details) + { + if (details.Status != null && StatusCode == null) + { + StatusCode = details.Status; + } + else if (details.Status == null && StatusCode != null) + { + details.Status = StatusCode; + } + } + if (StatusCode.HasValue) { context.HttpContext.Response.StatusCode = StatusCode.Value; - - if (Value is ProblemDetails details && !details.Status.HasValue) - { - details.Status = StatusCode.Value; - } } } } diff --git a/src/Mvc/Mvc.Core/test/DependencyInjection/ApiBehaviorOptionsSetupTest.cs b/src/Mvc/Mvc.Core/test/DependencyInjection/ApiBehaviorOptionsSetupTest.cs index ebca639ee9..c6700283eb 100644 --- a/src/Mvc/Mvc.Core/test/DependencyInjection/ApiBehaviorOptionsSetupTest.cs +++ b/src/Mvc/Mvc.Core/test/DependencyInjection/ApiBehaviorOptionsSetupTest.cs @@ -7,6 +7,8 @@ using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Moq; using Xunit; namespace Microsoft.Extensions.DependencyInjection @@ -70,6 +72,29 @@ namespace Microsoft.Extensions.DependencyInjection Assert.Equal(link, problemDetails.Type); } + [Fact] + public void ProblemDetailsInvalidModelStateResponse_UsesProblemDetailsFactory() + { + // Arrange + var actionContext = GetActionContext(); + var factory = Mock.Of(m => m.CreateValidationProblemDetails(It.IsAny(), It.IsAny(), null, null, null, null, null) == new ValidationProblemDetails + { + Status = 422, + }); + + // Act + var result = ApiBehaviorOptionsSetup.ProblemDetailsInvalidModelStateResponse(factory, actionContext); + + // Assert + var objectResult = Assert.IsType(result); + Assert.Equal(422, objectResult.StatusCode); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes.OrderBy(c => c)); + + var problemDetails = Assert.IsType(objectResult.Value); + Assert.Equal(422, problemDetails.Status); + Assert.Equal("One or more validation errors occurred.", problemDetails.Title); + } + [Fact] public void ProblemDetailsInvalidModelStateResponse_SetsTraceId() { diff --git a/src/Mvc/Mvc.Core/test/ObjectResultTests.cs b/src/Mvc/Mvc.Core/test/ObjectResultTests.cs index 5f210f2921..187e1ede7c 100644 --- a/src/Mvc/Mvc.Core/test/ObjectResultTests.cs +++ b/src/Mvc/Mvc.Core/test/ObjectResultTests.cs @@ -94,6 +94,68 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value); } + [Fact] + public async Task ObjectResult_ExecuteResultAsync_GetsStatusCodeFromProblemDetails() + { + // Arrange + var details = new ProblemDetails { Status = StatusCodes.Status413RequestEntityTooLarge, }; + + var result = new ObjectResult(details) + { + Formatters = new FormatterCollection() + { + new NoOpOutputFormatter(), + }, + }; + + var actionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext() + { + RequestServices = CreateServices(), + } + }; + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, details.Status.Value); + Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, result.StatusCode.Value); + Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, actionContext.HttpContext.Response.StatusCode); + } + + [Fact] + public async Task ObjectResult_ExecuteResultAsync_ResultAndProblemDetailsHaveStatusCodes() + { + // Arrange + var details = new ProblemDetails { Status = StatusCodes.Status422UnprocessableEntity, }; + + var result = new BadRequestObjectResult(details) + { + Formatters = new FormatterCollection() + { + new NoOpOutputFormatter(), + }, + }; + + var actionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext() + { + RequestServices = CreateServices(), + } + }; + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode.Value); + Assert.Equal(StatusCodes.Status400BadRequest, actionContext.HttpContext.Response.StatusCode); + } + private static IServiceProvider CreateServices() { var services = new ServiceCollection();