diff --git a/samples/FormatFilterSample.Web/Startup.cs b/samples/FormatFilterSample.Web/Startup.cs index 7a5dee35d9..08948b2b1c 100644 --- a/samples/FormatFilterSample.Web/Startup.cs +++ b/samples/FormatFilterSample.Web/Startup.cs @@ -15,7 +15,7 @@ namespace FormatFilterSample.Web // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc(options => + var mvcBuilder = services.AddMvc(options => { var formatFilter = new FormatFilterAttribute(); options.Filters.Add(formatFilter); @@ -28,6 +28,8 @@ namespace FormatFilterSample.Web "custom", MediaTypeHeaderValue.Parse("application/custom")); }); + + mvcBuilder.AddXmlDataContractSerializerFormatters(); } public void Configure(IApplicationBuilder app) diff --git a/samples/FormatFilterSample.Web/project.json b/samples/FormatFilterSample.Web/project.json index cfa331229d..c5937d7924 100644 --- a/samples/FormatFilterSample.Web/project.json +++ b/samples/FormatFilterSample.Web/project.json @@ -7,6 +7,7 @@ }, "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*", "Microsoft.AspNet.Server.Kestrel": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs index c067ff6179..d014a93764 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs @@ -122,12 +122,22 @@ namespace Microsoft.AspNet.Mvc.Formatters } var objectResult = context.Result as ObjectResult; - if (objectResult != null) + if (objectResult == null) { - var contentType = _options.FormatterMappings.GetMediaTypeMappingForFormat(format); - objectResult.ContentTypes.Clear(); - objectResult.ContentTypes.Add(contentType); + return; } + + // If the action sets a single content type, then its takes precedence over the user + // supplied content type based on format mapping. + if ((objectResult.ContentTypes != null && objectResult.ContentTypes.Count == 1) || + !string.IsNullOrEmpty(context.HttpContext.Response.ContentType)) + { + return; + } + + var contentType = _options.FormatterMappings.GetMediaTypeMappingForFormat(format); + objectResult.ContentTypes.Clear(); + objectResult.ContentTypes.Add(contentType); } /// diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs index f86ce95b37..ae39bbb995 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs @@ -60,12 +60,13 @@ namespace Microsoft.AspNet.Mvc.Formatters { // If the format is present in both route and query data, the one in route data wins - // Arrange + // Arrange var mediaType = MediaTypeHeaderValue.Parse("application/json"); var mockObjects = new MockObjects("json", FormatSource.RouteData); var httpContext = new Mock(); + httpContext.Setup(c => c.Response).Returns(new Mock().Object); - // Query contains xml + // Query contains xml httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true); httpContext.Setup(c => c.Request.Query["format"]).Returns("xml"); @@ -106,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.Formatters FormatSource place, string contentType) { - // Arrange + // Arrange var mediaType = MediaTypeHeaderValue.Parse(contentType); var mockObjects = new MockObjects(format, place); @@ -226,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Act filter.OnResourceExecuting(resourceExecutingContext); - // Assert + // Assert var actionResult = resourceExecutingContext.Result; Assert.IsType(actionResult); } @@ -298,6 +299,73 @@ namespace Microsoft.AspNet.Mvc.Formatters Assert.Equal(expected, filter.GetFormat(context)); } + [Fact] + public void FormatFilter_ExplicitContentType_SetOnObjectResult_TakesPrecedence() + { + // Arrange + var mediaType = MediaTypeHeaderValue.Parse("application/foo"); + var mockObjects = new MockObjects("json", FormatSource.QueryData); + var httpContext = new Mock(); + httpContext.Setup(c => c.Response).Returns(new Mock().Object); + httpContext.Setup(c => c.Request.Query["format"]).Returns("json"); + var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + var objectResult = new ObjectResult("Hello!"); + objectResult.ContentTypes.Add(new MediaTypeHeaderValue("application/foo")); + var resultExecutingContext = new ResultExecutingContext( + actionContext, + new IFilterMetadata[] { }, + objectResult, + controller: new object()); + + var resourceExecutingContext = new ResourceExecutingContext( + actionContext, + new IFilterMetadata[] { }); + + var filter = new FormatFilter(mockObjects.OptionsManager); + + // Act + filter.OnResourceExecuting(resourceExecutingContext); + filter.OnResultExecuting(resultExecutingContext); + + // Assert + var result = Assert.IsType(resultExecutingContext.Result); + Assert.Equal(1, result.ContentTypes.Count); + AssertMediaTypesEqual(mediaType, result.ContentTypes[0]); + } + + [Fact] + public void FormatFilter_ExplicitContentType_SetOnResponse_TakesPrecedence() + { + // Arrange + var mediaType = MediaTypeHeaderValue.Parse("application/foo"); + var mockObjects = new MockObjects("json", FormatSource.QueryData); + var response = new Mock(); + response.Setup(r => r.ContentType).Returns("application/foo"); + var httpContext = new Mock(); + httpContext.Setup(c => c.Response).Returns(response.Object); + httpContext.Setup(c => c.Request.Query["format"]).Returns("json"); + var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + var resultExecutingContext = new ResultExecutingContext( + actionContext, + new IFilterMetadata[] { }, + new ObjectResult("Hello!"), + controller: new object()); + + var resourceExecutingContext = new ResourceExecutingContext( + actionContext, + new IFilterMetadata[] { }); + + var filter = new FormatFilter(mockObjects.OptionsManager); + + // Act + filter.OnResourceExecuting(resourceExecutingContext); + filter.OnResultExecuting(resultExecutingContext); + + // Assert + var result = Assert.IsType(resultExecutingContext.Result); + Assert.Equal(0, result.ContentTypes.Count); + } + private static void AssertMediaTypesEqual( MediaTypeHeaderValue expectedMediaType, MediaTypeHeaderValue actualMediaType) @@ -326,6 +394,7 @@ namespace Microsoft.AspNet.Mvc.Formatters { var httpContext = new Mock(); httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(false); + httpContext.Setup(c => c.Response).Returns(new Mock().Object); MockHttpContext = httpContext.Object; Initialize(httpContext, format, place);