diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
index 56f14f8185..33f2b51eac 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
@@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
options,
_jsonOptions));
- options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
+ options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValues.ApplicationJson);
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IJsonPatchDocument)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(JToken)));
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs
index dbe7a8ca82..c1cd77f685 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
@@ -42,6 +43,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options));
+ // Do not override any user mapping
+ var key = "xml";
+ var mapping = options.FormatterMappings.GetMediaTypeMappingForFormat(key);
+ if (string.IsNullOrEmpty(mapping))
+ {
+ options.FormatterMappings.SetMediaTypeMappingForFormat(
+ key,
+ MediaTypeHeaderValues.ApplicationXml);
+ }
+
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.Linq.XObject"));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.XmlNode"));
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs
index e2b75d7f89..6c7546e332 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs
@@ -4,6 +4,7 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
@@ -35,6 +36,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
/// The .
public void Configure(MvcOptions options)
{
+ // Do not override any user mapping
+ var key = "xml";
+ var mapping = options.FormatterMappings.GetMediaTypeMappingForFormat(key);
+ if (string.IsNullOrEmpty(mapping))
+ {
+ options.FormatterMappings.SetMediaTypeMappingForFormat(
+ key,
+ MediaTypeHeaderValues.ApplicationXml);
+ }
+
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlSerializerInputFormatter(options));
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs
new file mode 100644
index 0000000000..5219f0b97a
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
+{
+ public class MvcXmlDataContractSerializerMvcOptionsSetupTest
+ {
+ [Fact]
+ public void AddsFormatterMapping()
+ {
+ // Arrange
+ var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
+ var options = new MvcOptions();
+
+ // Act
+ optionsSetup.Configure(options);
+
+ // Assert
+ var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
+ Assert.Equal("application/xml", mappedContentType);
+ }
+
+ [Fact]
+ public void DoesNotOverrideExistingMapping()
+ {
+ // Arrange
+ var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
+ var options = new MvcOptions();
+ options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
+
+ // Act
+ optionsSetup.Configure(options);
+
+ // Assert
+ var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
+ Assert.Equal("text/xml", mappedContentType);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs
new file mode 100644
index 0000000000..0ff2072a4c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
+{
+ public class MvcXmlSerializerMvcOptionsSetupTest
+ {
+ [Fact]
+ public void AddsFormatterMapping()
+ {
+ // Arrange
+ var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
+ var options = new MvcOptions();
+
+ // Act
+ optionsSetup.Configure(options);
+
+ // Assert
+ var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
+ Assert.Equal("application/xml", mappedContentType);
+ }
+
+ [Fact]
+ public void DoesNotOverrideExistingMapping()
+ {
+ // Arrange
+ var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
+ var options = new MvcOptions();
+ options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
+
+ // Act
+ optionsSetup.Configure(options);
+
+ // Assert
+ var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
+ Assert.Equal("text/xml", mappedContentType);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs
index 9116b47831..75a684c4a4 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs
@@ -450,13 +450,12 @@ END:VCARD
[Fact]
public async Task ProducesAttribute_And_FormatFilterAttribute_Conflicting()
{
- // Arrange
- var expectedContentType = MediaTypeHeaderValue.Parse("application/json");
-
- // Act
- var response = await Client.GetAsync("http://localhost/FormatFilter/MethodWithFormatFilter.json");
+ // Arrange & Act
+ var response = await Client.GetAsync(
+ "http://localhost/FormatFilter/ProducesTakesPrecedenceOverUserSuppliedFormatMethod?format=json");
// Assert
+ // Explicit content type set by the developer takes precedence over the format requested by the end user
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -464,7 +463,8 @@ END:VCARD
public async Task ProducesAttribute_And_FormatFilterAttribute_Collaborating()
{
// Arrange & Act
- var response = await Client.GetAsync("http://localhost/FormatFilter/MethodWithFormatFilter");
+ var response = await Client.GetAsync(
+ "http://localhost/FormatFilter/ProducesTakesPrecedenceOverUserSuppliedFormatMethod");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -508,5 +508,24 @@ END:VCARD
var contact = xmlDeserializer.ReadObject(bodyStream) as Contact;
Assert.Equal("Jason Ecsemelle", contact.Name);
}
+
+ [Fact]
+ public async Task FormatFilter_XmlAsFormat_ReturnsXml()
+ {
+ // Arrange
+ var expectedBody = ""
+ + "John";
+
+ // Act
+ var response = await Client.GetAsync(
+ "http://localhost/FormatFilter/CustomerInfo?format=xml");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("application/xml; charset=utf-8", response.Content.Headers.ContentType.ToString());
+ var body = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedBody, body);
+ }
}
}
\ No newline at end of file
diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs b/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs
index 34a1b22136..ec617bdf3e 100644
--- a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs
+++ b/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs
@@ -7,24 +7,39 @@ using Microsoft.AspNetCore.Mvc.Filters;
namespace BasicWebSite.Controllers.ContentNegotiation
{
- [Produces("application/FormatFilterController")]
public class FormatFilterController : Controller
{
- public override void OnActionExecuted(ActionExecutedContext context)
- {
- var result = context.Result as ObjectResult;
- if (result != null)
- {
- result.Formatters.Add(new CustomFormatter("application/FormatFilterController"));
- }
-
- base.OnActionExecuted(context);
- }
-
+ [Produces("application/FormatFilterController")]
[FormatFilter]
- public string MethodWithFormatFilter()
+ [CustomFormatterActionFilter]
+ public string ProducesTakesPrecedenceOverUserSuppliedFormatMethod()
{
return "MethodWithFormatFilter";
}
+
+ [HttpGet]
+ [FormatFilter]
+ public Customer CustomerInfo()
+ {
+ return new Customer() { Name = "John" };
+ }
+
+ private class CustomFormatterActionFilter : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(ActionExecutedContext context)
+ {
+ var result = context.Result as ObjectResult;
+ if (result != null)
+ {
+ result.Formatters.Add(new CustomFormatter("application/FormatFilterController"));
+ }
+ }
+ }
+
+ public class Customer
+ {
+ public string Name { get; set; }
+ }
}
+
}
\ No newline at end of file