diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs index 5364994a8561..22bd842bee18 100644 --- a/src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs +++ b/src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs @@ -151,19 +151,16 @@ private Task ExecuteAsyncCore(ActionContext context, ObjectResult result, Type? private static void InferContentTypes(ActionContext context, ObjectResult result) { Debug.Assert(result.ContentTypes != null); - if (result.ContentTypes.Count != 0) - { - return; - } // If the user sets the content type both on the ObjectResult (example: by Produces) and Response object, // then the one set on ObjectResult takes precedence over the Response object var responseContentType = context.HttpContext.Response.ContentType; - if (!string.IsNullOrEmpty(responseContentType)) + if (result.ContentTypes.Count == 0 && !string.IsNullOrEmpty(responseContentType)) { result.ContentTypes.Add(responseContentType); } - else if (result.Value is ProblemDetails) + + if (result.Value is ProblemDetails) { result.ContentTypes.Add("application/problem+json"); result.ContentTypes.Add("application/problem+xml"); diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/ObjectResultExecutorTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/ObjectResultExecutorTest.cs index 1fba1ac94a36..dd36fbe7dc92 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ObjectResultExecutorTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ObjectResultExecutorTest.cs @@ -114,50 +114,123 @@ public async Task ExecuteAsync_WithOneProvidedContentType_FromResponseContentTyp } [Fact] - public async Task ExecuteAsync_ForProblemDetailsValue_UsesSpecifiedContentType() + public async Task ExecuteAsync_WithResponseAndObjectResultContentType_ForProblemDetailsValue_UsesXMLContentType() { // Arrange var executor = CreateExecutor(); var httpContext = new DefaultHttpContext(); var actionContext = new ActionContext() { HttpContext = httpContext }; - httpContext.Response.ContentType = "application/json"; + httpContext.Response.ContentType = "application/xml"; // This will not be used var result = new ObjectResult(new ProblemDetails()) { - ContentTypes = { "text/plain" }, + ContentTypes = { "text/plain" }, // This will not be used }; - result.Formatters.Add(new TestXmlOutputFormatter()); + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the problem details content type result.Formatters.Add(new TestJsonOutputFormatter()); - result.Formatters.Add(new TestStringOutputFormatter()); // This will be chosen based on the content type + result.Formatters.Add(new TestStringOutputFormatter()); // Act await executor.ExecuteAsync(actionContext, result); // Assert - MediaTypeAssert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); + MediaTypeAssert.Equal("application/problem+xml; charset=utf-8", httpContext.Response.ContentType); } [Fact] - public async Task ExecuteAsync_ForProblemDetailsValue_UsesResponseContentType() + public async Task ExecuteAsync_WithResponseContentType_ForProblemDetailsValue_UsesProblemDetailXMLContentType() { // Arrange var executor = CreateExecutor(); var httpContext = new DefaultHttpContext(); var actionContext = new ActionContext() { HttpContext = httpContext }; - httpContext.Response.ContentType = "application/json"; + httpContext.Response.ContentType = "application/json"; // This will not be used var result = new ObjectResult(new ProblemDetails()); + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the problem details content type + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestStringOutputFormatter()); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("application/problem+xml; charset=utf-8", httpContext.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_ForProblemDetailsValue_UsesProblemDetailsContentType() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Response.ContentType = "application/json"; // This will not be used + + var result = new ObjectResult(new ProblemDetails()); + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the problem details content type + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestStringOutputFormatter()); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("application/problem+xml; charset=utf-8", httpContext.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_ForProblemDetailsValue_UsesProblemDetailsJsonContentType_BasedOnAcceptHeader() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Request.Headers[HeaderNames.Accept] = "application/json"; // This will not be used + httpContext.Response.ContentType = "application/xml"; // This will not be used + + var result = new ObjectResult(new ProblemDetails()) + { + ContentTypes = { "text/plain" }, // This will not be used + }; + result.Formatters.Add(new TestJsonOutputFormatter()); // This will be chosen based on the Accept Headers "application/json" result.Formatters.Add(new TestXmlOutputFormatter()); - result.Formatters.Add(new TestJsonOutputFormatter()); // This will be chosen based on the response content type result.Formatters.Add(new TestStringOutputFormatter()); // Act await executor.ExecuteAsync(actionContext, result); // Assert - MediaTypeAssert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType); + MediaTypeAssert.Equal("application/problem+json; charset=utf-8", httpContext.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_ForProblemDetailsValue_UsesProblemDetailsXMLContentType_BasedOnAcceptHeader() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Request.Headers[HeaderNames.Accept] = "application/xml"; // This will not be used + + var result = new ObjectResult(new ProblemDetails()) + { + ContentTypes = { "text/plain" }, // This will not be used + }; + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the Accept Headers "application/xml" + result.Formatters.Add(new TestStringOutputFormatter()); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("application/problem+xml; charset=utf-8", httpContext.Response.ContentType); } [Fact] @@ -170,7 +243,7 @@ public async Task ExecuteAsync_NoContentTypeProvidedForProblemDetails_UsesDefaul var actionContext = new ActionContext() { HttpContext = httpContext }; var result = new ObjectResult(new ProblemDetails()); - result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the implicitly added content type + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the problem details added content type result.Formatters.Add(new TestJsonOutputFormatter()); result.Formatters.Add(new TestStringOutputFormatter());