diff --git a/samples/FormatFilterSample.Web/CustomFormatter.cs b/samples/FormatFilterSample.Web/CustomFormatter.cs index c98c3c5228..1e6a477a25 100644 --- a/samples/FormatFilterSample.Web/CustomFormatter.cs +++ b/samples/FormatFilterSample.Web/CustomFormatter.cs @@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers; namespace FormatFilterSample.Web { - public class CustomFormatter : OutputFormatter + public class CustomFormatter : TextOutputFormatter { public CustomFormatter(string contentType) { @@ -38,7 +38,7 @@ namespace FormatFilterSample.Web return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var response = context.HttpContext.Response; await response.WriteAsync(context.Object.ToString()); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs index 3b1a14c7c6..314fadbfe9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs @@ -3,13 +3,9 @@ using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc.Formatters { @@ -18,52 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider { - private IDictionary _outputMediaTypeCache; - /// /// Initializes a new instance of the class. /// protected OutputFormatter() { - SupportedEncodings = new List(); SupportedMediaTypes = new MediaTypeCollection(); } - /// - /// Gets the mutable collection of character encodings supported by - /// this . The encodings are - /// used when writing the data. - /// - public IList SupportedEncodings { get; } - /// /// Gets the mutable collection of media type elements supported by /// this . /// public MediaTypeCollection SupportedMediaTypes { get; } - private IDictionary OutputMediaTypeCache - { - get - { - if (_outputMediaTypeCache == null) - { - var cache = new Dictionary(); - foreach (var mediaType in SupportedMediaTypes) - { - cache.Add(mediaType, MediaType.ReplaceEncoding(mediaType, Encoding.UTF8)); - } - - // Safe race condition, worst case scenario we initialize the field multiple times with dictionaries containing - // the same values. - _outputMediaTypeCache = cache; - } - - return _outputMediaTypeCache; - } - } - - /// /// Returns a value indicating whether or not the given type can be written by this serializer. /// @@ -115,49 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } } - /// - /// Determines the best amongst the supported encodings - /// for reading or writing an HTTP entity body based on the provided . - /// - /// The formatter context associated with the call. - /// - /// The to use when reading the request or writing the response. - public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var request = context.HttpContext.Request; - var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset); - if (encoding != null) - { - return encoding; - } - - if (context.ContentType.HasValue) - { - var parsedContentType = new MediaType(context.ContentType); - var contentTypeCharset = parsedContentType.Charset; - if (contentTypeCharset.HasValue) - { - for (var i = 0; i < SupportedEncodings.Count; i++) - { - var supportedEncoding = SupportedEncodings[i]; - if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase)) - { - // This is supported. - return SupportedEncodings[i]; - } - } - } - } - - // A formatter for a non-text media-type won't have any supported encodings. - return SupportedEncodings.Count > 0 ? SupportedEncodings[0] : null; - } - /// public virtual bool CanWriteResult(OutputFormatterCanWriteContext context) { @@ -206,46 +127,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } /// - public Task WriteAsync(OutputFormatterWriteContext context) + public virtual Task WriteAsync(OutputFormatterWriteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } - var selectedMediaType = context.ContentType; - if (!selectedMediaType.HasValue) - { - // If content type is not set then set it based on supported media types. - if (SupportedEncodings.Count > 0) - { - selectedMediaType = new StringSegment(SupportedMediaTypes[0]); - } - else - { - throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); - } - } - - // Note: Text-based media types will use an encoding/charset - binary formats just ignore it. We want to - // make this class work with media types that use encodings, and those that don't. - // - // The default implementation of SelectCharacterEncoding will read from the list of SupportedEncodings - // and will always choose a default encoding if any are supported. So, the only cases where the - // selectedEncoding can be null are: - // - // 1). No supported encodings - we assume this is a non-text format - // 2). Custom implementation of SelectCharacterEncoding - trust the user and give them what they want. - var selectedEncoding = SelectCharacterEncoding(context); - if (selectedEncoding != null) - { - // Override the content type value even if one already existed. - var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding); - selectedMediaType = new StringSegment(mediaTypeWithCharset); - } - - context.ContentType = selectedMediaType; - WriteResponseHeaders(context); return WriteResponseBodyAsync(context); } @@ -271,100 +159,5 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// The formatter context associated with the call. /// A task which can write the response body. public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); - - private string GetMediaTypeWithCharset(string mediaType, Encoding encoding) - { - if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) && - OutputMediaTypeCache.ContainsKey(mediaType)) - { - return OutputMediaTypeCache[mediaType]; - } - - return MediaType.ReplaceEncoding(mediaType, encoding); - } - - private Encoding MatchAcceptCharacterEncoding(IList acceptCharsetHeaders) - { - if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0) - { - var acceptValues = Sort(acceptCharsetHeaders); - for (var i = 0; i < acceptValues.Count; i++) - { - var charset = acceptValues[i].Value; - if (!string.IsNullOrEmpty(charset)) - { - for (var j = 0; j < SupportedEncodings.Count; j++) - { - var encoding = SupportedEncodings[j]; - if (charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || - charset.Equals("*", StringComparison.Ordinal)) - { - return encoding; - } - } - } - } - } - - return null; - } - - // There's no allocation-free way to sort an IList and we may have to filter anyway, - // so we're going to have to live with the copy + insertion sort. - private IList Sort(IList values) - { - var sortNeeded = false; - var count = 0; - - for (var i = 0; i < values.Count; i++) - { - var value = values[i]; - if (value.Quality == HeaderQuality.NoMatch) - { - // Exclude this one - } - else if (value.Quality != null) - { - count++; - sortNeeded = true; - } - else - { - count++; - } - } - - if (!sortNeeded) - { - return values; - } - - var sorted = new List(); - for (var i = 0; i < values.Count; i++) - { - var value = values[i]; - if (value.Quality == HeaderQuality.NoMatch) - { - // Exclude this one - } - else - { - // Doing an insertion sort. - var position = sorted.BinarySearch(value, StringWithQualityHeaderValueComparer.QualityComparer); - if (position >= 0) - { - sorted.Insert(position + 1, value); - } - else - { - sorted.Insert(~position, value); - } - } - } - - // We want a descending sort, but BinarySearch does ascending - sorted.Reverse(); - return sorted; - } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs index 704a97e3cb..5e417f420f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// Always writes a string value to the response, regardless of requested content type. /// - public class StringOutputFormatter : OutputFormatter + public class StringOutputFormatter : TextOutputFormatter { public StringOutputFormatter() { @@ -46,13 +46,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return false; } - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + var valueAsString = (string)context.Object; if (string.IsNullOrEmpty(valueAsString)) { @@ -60,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } var response = context.HttpContext.Response; - return response.WriteAsync(valueAsString, MediaType.GetEncoding(response.ContentType) ?? Encoding.UTF8); + return response.WriteAsync(valueAsString, encoding); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs new file mode 100644 index 0000000000..808c906e13 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs @@ -0,0 +1,264 @@ +// 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 System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Writes an object in a given text format to the output stream. + /// + public abstract class TextOutputFormatter : OutputFormatter + { + private IDictionary _outputMediaTypeCache; + + /// + /// Initializes a new instance of the class. + /// + protected TextOutputFormatter() + { + SupportedEncodings = new List(); + } + + /// + /// Gets the mutable collection of character encodings supported by + /// this . The encodings are + /// used when writing the data. + /// + public IList SupportedEncodings { get; } + + private IDictionary OutputMediaTypeCache + { + get + { + if (_outputMediaTypeCache == null) + { + var cache = new Dictionary(); + foreach (var mediaType in SupportedMediaTypes) + { + cache.Add(mediaType, MediaType.ReplaceEncoding(mediaType, Encoding.UTF8)); + } + + // Safe race condition, worst case scenario we initialize the field multiple times with dictionaries containing + // the same values. + _outputMediaTypeCache = cache; + } + + return _outputMediaTypeCache; + } + } + + /// + /// Determines the best amongst the supported encodings + /// for reading or writing an HTTP entity body based on the provided . + /// + /// The formatter context associated with the call. + /// + /// The to use when reading the request or writing the response. + public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (SupportedEncodings.Count == 0) + { + var message = Resources.FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty( + nameof(SupportedEncodings)); + throw new InvalidOperationException(message); + } + + var request = context.HttpContext.Request; + var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset); + if (encoding != null) + { + return encoding; + } + + if (context.ContentType.HasValue) + { + var parsedContentType = new MediaType(context.ContentType); + var contentTypeCharset = parsedContentType.Charset; + if (contentTypeCharset.HasValue) + { + for (var i = 0; i < SupportedEncodings.Count; i++) + { + var supportedEncoding = SupportedEncodings[i]; + if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase)) + { + // This is supported. + return SupportedEncodings[i]; + } + } + } + } + + return SupportedEncodings[0]; + } + + /// + public override Task WriteAsync(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var selectedMediaType = context.ContentType; + if (!selectedMediaType.HasValue) + { + // If content type is not set then set it based on supported media types. + if (SupportedEncodings.Count > 0) + { + selectedMediaType = new StringSegment(SupportedMediaTypes[0]); + } + else + { + throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); + } + } + + var selectedEncoding = SelectCharacterEncoding(context); + if (selectedEncoding != null) + { + // Override the content type value even if one already existed. + var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding); + selectedMediaType = new StringSegment(mediaTypeWithCharset); + } + else + { + var response = context.HttpContext.Response; + response.StatusCode = StatusCodes.Status406NotAcceptable; + return TaskCache.CompletedTask; + } + + context.ContentType = selectedMediaType; + + WriteResponseHeaders(context); + return WriteResponseBodyAsync(context, selectedEncoding); + } + + /// + public sealed override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + { + var message = Resources.FormatTextOutpurFormatter_WriteResponseBodyAsynNotSupported( + $"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)})", + nameof(TextOutputFormatter), + $"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)},{nameof(Encoding)})"); + + throw new InvalidOperationException(message); + } + + /// + /// Writes the response body. + /// + /// The formatter context associated with the call. + /// The that should be used to write the response. + /// A task which can write the response body. + public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding); + + private string GetMediaTypeWithCharset(string mediaType, Encoding encoding) + { + if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) && + OutputMediaTypeCache.ContainsKey(mediaType)) + { + return OutputMediaTypeCache[mediaType]; + } + + return MediaType.ReplaceEncoding(mediaType, encoding); + } + + private Encoding MatchAcceptCharacterEncoding(IList acceptCharsetHeaders) + { + if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0) + { + var acceptValues = Sort(acceptCharsetHeaders); + for (var i = 0; i < acceptValues.Count; i++) + { + var charset = acceptValues[i].Value; + if (!string.IsNullOrEmpty(charset)) + { + for (var j = 0; j < SupportedEncodings.Count; j++) + { + var encoding = SupportedEncodings[j]; + if (charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || + charset.Equals("*", StringComparison.Ordinal)) + { + return encoding; + } + } + } + } + } + + return null; + } + + // There's no allocation-free way to sort an IList and we may have to filter anyway, + // so we're going to have to live with the copy + insertion sort. + private IList Sort(IList values) + { + var sortNeeded = false; + var count = 0; + + for (var i = 0; i < values.Count; i++) + { + var value = values[i]; + if (value.Quality == HeaderQuality.NoMatch) + { + // Exclude this one + } + else if (value.Quality != null) + { + count++; + sortNeeded = true; + } + else + { + count++; + } + } + + if (!sortNeeded) + { + return values; + } + + var sorted = new List(); + for (var i = 0; i < values.Count; i++) + { + var value = values[i]; + if (value.Quality == HeaderQuality.NoMatch) + { + // Exclude this one + } + else + { + // Doing an insertion sort. + var position = sorted.BinarySearch(value, StringWithQualityHeaderValueComparer.QualityComparer); + if (position >= 0) + { + sorted.Insert(position + 1, value); + } + else + { + sorted.Insert(~position, value); + } + } + } + + // We want a descending sort, but BinarySearch does ascending + sorted.Reverse(); + return sorted; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 74eff64d28..db8b79a02b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1114,6 +1114,38 @@ namespace Microsoft.AspNetCore.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0); } + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string TextOutputFormatter_SupportedEncodingsMustNotBeEmpty + { + get { return GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"); } + } + + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"), p0); + } + + /// + /// '{0}' is not supported by '{1}'. Use '{2}' instead. + /// + internal static string TextOutpurFormatter_WriteResponseBodyAsynNotSupported + { + get { return GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"); } + } + + /// + /// '{0}' is not supported by '{1}'. Use '{2}' instead. + /// + internal static string FormatTextOutpurFormatter_WriteResponseBodyAsynNotSupported(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"), p0, p1, p2); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index da40c89bc1..84d1befc53 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -334,4 +334,10 @@ The list of '{0}' must not be empty. Add at least one supported encoding. + + The list of '{0}' must not be empty. Add at least one supported encoding. + + + '{0}' is not supported by '{1}'. Use '{2}' instead. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs index 82a9cce63d..b235a47120 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// An output formatter that specializes in writing JSON content. /// - public class JsonOutputFormatter : OutputFormatter + public class JsonOutputFormatter : TextOutputFormatter { private readonly IArrayPool _charPool; @@ -83,6 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } } + /// + /// Writes the given as JSON using the given + /// . + /// + /// The used to write the + /// The value to write as JSON. public void WriteObject(TextWriter writer, object value) { if (writer == null) @@ -132,16 +138,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return _serializer; } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + /// + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } - var response = context.HttpContext.Response; - var selectedEncoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8; + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + var response = context.HttpContext.Response; using (var writer = context.WriterFactory(response.Body, selectedEncoding)) { WriteObject(writer, context.Object); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs index ecd4db4c86..0862fa21c2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc.Formatters { @@ -20,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// This class handles serialization of objects /// to XML using /// - public class XmlDataContractSerializerOutputFormatter : OutputFormatter + public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter { private DataContractSerializerSettings _serializerSettings; private ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); @@ -180,15 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } /// - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + var writerSettings = WriterSettings.Clone(); - writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8; + writerSettings.Encoding = selectedEncoding; // Wrap the object only if there is a wrapping type. var value = context.Object; diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs index d90a62fd5a..5adfc6be44 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -11,8 +11,6 @@ using System.Xml; using System.Xml.Serialization; using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc.Formatters { @@ -20,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// This class handles serialization of objects /// to XML using /// - public class XmlSerializerOutputFormatter : OutputFormatter + public class XmlSerializerOutputFormatter : TextOutputFormatter { private ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); @@ -155,17 +153,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } /// - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + var response = context.HttpContext.Response; var writerSettings = WriterSettings.Clone(); - writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8; + writerSettings.Encoding = selectedEncoding; // Wrap the object only if there is a wrapping type. var value = context.Object; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs index b702342d46..741526a8ca 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Text; using System.Threading.Tasks; using Xunit; @@ -28,17 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.IsType(typeof(AnotherTestOutputFormatter), formatter); } - private class TestOutputFormatter : OutputFormatter + private class TestOutputFormatter : TextOutputFormatter { - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { throw new NotImplementedException(); } } - private class AnotherTestOutputFormatter : OutputFormatter + private class AnotherTestOutputFormatter : TextOutputFormatter { - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs index 6a2f626ad3..6476947fd5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs @@ -18,228 +18,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { public class OutputFormatterTests { - public static IEnumerable SelectResponseCharacterEncodingData - { - get - { - // string acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding - yield return new object[] { "", new string[] { "utf-8", "utf-16" }, "utf-8" }; - - yield return new object[] { "utf-8", new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" }; - yield return new object[] { "utf-16; q=0.5", new string[] { "utf-8", "utf-16" }, "utf-16" }; - - yield return new object[] { "utf-8; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "utf-8; q=0.0, utf-16; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; - - yield return new object[] { "*; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; - } - } - - [Theory] - [MemberData(nameof(SelectResponseCharacterEncodingData))] - public void SelectResponseCharacterEncoding_SelectsEncoding( - string acceptCharsetHeaders, - string[] supportedEncodings, - string expectedEncoding) - { - // Arrange - var httpContext = new Mock(); - var httpRequest = new DefaultHttpContext().Request; - httpRequest.Headers[HeaderNames.AcceptCharset] = acceptCharsetHeaders; - httpRequest.Headers[HeaderNames.Accept] = "application/acceptCharset"; - httpContext.SetupGet(o => o.Request).Returns(httpRequest); - - var formatter = new TestOutputFormatter(); - foreach (string supportedEncoding in supportedEncodings) - { - formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding)); - } - - var context = new OutputFormatterWriteContext( - httpContext.Object, - new TestHttpResponseStreamWriterFactory().CreateWriter, - typeof(string), - "someValue") - { - ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]), - }; - - // Act - var actualEncoding = formatter.SelectCharacterEncoding(context); - - // Assert - Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding); - } - - [Theory] - [InlineData("application/json; charset=utf-16", "application/json; charset=utf-32")] - [InlineData("application/json; charset=utf-16; format=indent", "application/json; charset=utf-32; format=indent")] - public void WriteResponse_OverridesCharset_IfDifferentFromContentTypeCharset( - string contentType, - string expectedContentType) - { - // Arrange - var formatter = new Mock(); - - formatter - .Setup(f => f.SelectCharacterEncoding(It.IsAny())) - .Returns(Encoding.UTF32); - - var formatterContext = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null) - { - ContentType = new StringSegment(contentType), - }; - - // Act - formatter.Object.WriteAsync(formatterContext); - - // Assert - Assert.Equal(new StringSegment(expectedContentType), formatterContext.ContentType); - } - - [Fact] - public void WriteResponse_GetMediaTypeWithCharsetReturnsMediaTypeFromCache_IfEncodingIsUtf8() - { - // Arrange - var formatter = new TestOutputFormatter(); - - var formatterContext = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null) - { - ContentType = new StringSegment("application/json"), - }; - - formatter.SupportedMediaTypes.Add("application/json"); - formatter.SupportedEncodings.Add(Encoding.UTF8); - - // Act - formatter.WriteAsync(formatterContext); - var firstContentType = formatterContext.ContentType; - - formatterContext.ContentType = new StringSegment("application/json"); - - formatter.WriteAsync(formatterContext); - var secondContentType = formatterContext.ContentType; - - // Assert - Assert.Same(firstContentType.Buffer, secondContentType.Buffer); - } - - [Fact] - public void WriteResponse_GetMediaTypeWithCharsetReplacesCharset_IfDifferentThanEncoding() - { - // Arrange - var formatter = new TestOutputFormatter(); - - var formatterContext = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null) - { - ContentType = new StringSegment("application/json; charset=utf-7"), - }; - - formatter.SupportedMediaTypes.Add("application/json"); - formatter.SupportedEncodings.Add(Encoding.UTF8); - - // Act - formatter.WriteAsync(formatterContext); - - // Assert - Assert.Equal(new StringSegment("application/json; charset=utf-8"), formatterContext.ContentType); - } - - [Fact] - public void WriteResponse_GetMediaTypeWithCharsetReturnsSameString_IfCharsetEqualToEncoding() - { - // Arrange - var formatter = new TestOutputFormatter(); - - var contentType = "application/json; charset=utf-16"; - var formatterContext = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null) - { - ContentType = new StringSegment(contentType), - }; - - formatter.SupportedMediaTypes.Add("application/json"); - formatter.SupportedEncodings.Add(Encoding.Unicode); - - // Act - formatter.WriteAsync(formatterContext); - - // Assert - Assert.Same(contentType, formatterContext.ContentType.Buffer); - } - - [Fact] - public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet() - { - // Arrange - var formatter = new TestOutputFormatter(); - - var testContentType = new StringSegment("text/json"); - - formatter.SupportedEncodings.Clear(); - formatter.SupportedMediaTypes.Clear(); - formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); - - var context = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null) - { - ContentType = testContentType, - }; - - // Act - formatter.WriteResponseHeaders(context); - - // Assert - Assert.Null(MediaTypeHeaderValue.Parse(context.ContentType.Value).Encoding); - Assert.Equal(testContentType, context.ContentType); - - // If we had set an encoding, it would be part of the content type header - Assert.Equal(MediaTypeHeaderValue.Parse(testContentType.Value), context.HttpContext.Response.GetTypedHeaders().ContentType); - } - - [Fact] - public async Task WriteResponseHeaders_ClonesMediaType() - { - // Arrange - var formatter = new PngImageFormatter(); - formatter.SupportedMediaTypes.Clear(); - var mediaType = new MediaTypeHeaderValue("image/png"); - formatter.SupportedMediaTypes.Add(mediaType); - - var context = new OutputFormatterWriteContext( - new DefaultHttpContext(), - new TestHttpResponseStreamWriterFactory().CreateWriter, - objectType: null, - @object: null); - - // Act - await formatter.WriteAsync(context); - - // Assert - Assert.NotSame(mediaType, context.ContentType); - Assert.Null(mediaType.Charset); - Assert.Equal("image/png; charset=utf-8", context.ContentType.ToString()); - } - [Fact] public void CanWriteResult_ForNullContentType_UsesFirstEntryInSupportedContentTypes() { @@ -397,42 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { public TestOutputFormatter() { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/acceptCharset")); - } - - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) - { - return Task.FromResult(true); - } - } - - private class DoesNotSetContext : OutputFormatter - { - public DoesNotSetContext() - { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/doesNotSetContext")); - SupportedEncodings.Add(Encoding.Unicode); - } - - public override bool CanWriteResult(OutputFormatterCanWriteContext context) - { - // Do not set the selected media Type. - // The WriteResponseHeaders should do it for you. - return true; - } - - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) - { - return Task.FromResult(true); - } - } - - private class PngImageFormatter : OutputFormatter - { - public PngImageFormatter() - { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("image/png")); - SupportedEncodings.Add(Encoding.UTF8); + SupportedMediaTypes.Add("application/acceptCharset"); } public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs index 5797d1577f..a1e9682332 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Encoding encoding = Encoding.UTF8; var memoryStream = new MemoryStream(); var response = new Mock(); - response.SetupProperty(o => o.ContentLength); + response.SetupProperty(o => o.ContentLength); response.SetupGet(r => r.Body).Returns(memoryStream); var httpContext = new Mock(); httpContext.Setup(o => o.Response).Returns(response.Object); @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters @object: null); // Act - await formatter.WriteResponseBodyAsync(context); + await formatter.WriteResponseBodyAsync(context, encoding); // Assert Assert.Equal(0, memoryStream.Length); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs new file mode 100644 index 0000000000..d77ab7c788 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs @@ -0,0 +1,271 @@ +// 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 System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + public class TextOutputFormatterTests + { + public static IEnumerable SelectResponseCharacterEncodingData + { + get + { + // string acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding + yield return new object[] { "", new string[] { "utf-8", "utf-16" }, "utf-8" }; + + yield return new object[] { "utf-8", new string[] { "utf-8", "utf-16" }, "utf-8" }; + yield return new object[] { "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" }; + yield return new object[] { "utf-16; q=0.5", new string[] { "utf-8", "utf-16" }, "utf-16" }; + + yield return new object[] { "utf-8; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; + yield return new object[] { "utf-8; q=0.0, utf-16; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; + + yield return new object[] { "*; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; + } + } + + [Theory] + [MemberData(nameof(SelectResponseCharacterEncodingData))] + public void SelectResponseCharacterEncoding_SelectsEncoding( + string acceptCharsetHeaders, + string[] supportedEncodings, + string expectedEncoding) + { + // Arrange + var httpContext = new Mock(); + var httpRequest = new DefaultHttpContext().Request; + httpRequest.Headers[HeaderNames.AcceptCharset] = acceptCharsetHeaders; + httpRequest.Headers[HeaderNames.Accept] = "application/acceptCharset"; + httpContext.SetupGet(o => o.Request).Returns(httpRequest); + + var formatter = new TestOutputFormatter(); + foreach (string supportedEncoding in supportedEncodings) + { + formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding)); + } + + var context = new OutputFormatterWriteContext( + httpContext.Object, + new TestHttpResponseStreamWriterFactory().CreateWriter, + typeof(string), + "someValue") + { + ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]), + }; + + // Act + var actualEncoding = formatter.SelectCharacterEncoding(context); + + // Assert + Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding); + } + + [Theory] + [InlineData("application/json; charset=utf-16", "application/json; charset=utf-32")] + [InlineData("application/json; charset=utf-16; format=indent", "application/json; charset=utf-32; format=indent")] + public void WriteResponse_OverridesCharset_IfDifferentFromContentTypeCharset( + string contentType, + string expectedContentType) + { + // Arrange + var formatter = new OverrideEncodingFormatter(Encoding.UTF32); + + var formatterContext = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = new StringSegment(contentType), + }; + + // Act + formatter.WriteAsync(formatterContext); + + // Assert + Assert.Equal(new StringSegment(expectedContentType), formatterContext.ContentType); + } + + [Fact] + public void WriteResponse_GetMediaTypeWithCharsetReturnsMediaTypeFromCache_IfEncodingIsUtf8() + { + // Arrange + var formatter = new TestOutputFormatter(); + + var formatterContext = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = new StringSegment("application/json"), + }; + + formatter.SupportedMediaTypes.Add("application/json"); + formatter.SupportedEncodings.Add(Encoding.UTF8); + + // Act + formatter.WriteAsync(formatterContext); + var firstContentType = formatterContext.ContentType; + + formatterContext.ContentType = new StringSegment("application/json"); + + formatter.WriteAsync(formatterContext); + var secondContentType = formatterContext.ContentType; + + // Assert + Assert.Same(firstContentType.Buffer, secondContentType.Buffer); + } + + [Fact] + public void WriteResponse_GetMediaTypeWithCharsetReplacesCharset_IfDifferentThanEncoding() + { + // Arrange + var formatter = new TestOutputFormatter(); + + var formatterContext = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = new StringSegment("application/json; charset=utf-7"), + }; + + formatter.SupportedMediaTypes.Add("application/json"); + formatter.SupportedEncodings.Add(Encoding.UTF8); + + // Act + formatter.WriteAsync(formatterContext); + + // Assert + Assert.Equal(new StringSegment("application/json; charset=utf-8"), formatterContext.ContentType); + } + + [Fact] + public void WriteResponse_GetMediaTypeWithCharsetReturnsSameString_IfCharsetEqualToEncoding() + { + // Arrange + var formatter = new TestOutputFormatter(); + + var contentType = "application/json; charset=utf-16"; + var formatterContext = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = new StringSegment(contentType), + }; + + formatter.SupportedMediaTypes.Add("application/json"); + formatter.SupportedEncodings.Add(Encoding.Unicode); + + // Act + formatter.WriteAsync(formatterContext); + + // Assert + Assert.Same(contentType, formatterContext.ContentType.Buffer); + } + + [Fact] + public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet() + { + // Arrange + var formatter = new TestOutputFormatter(); + + var testContentType = new StringSegment("text/json"); + + formatter.SupportedEncodings.Clear(); + formatter.SupportedMediaTypes.Clear(); + formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); + + var context = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = testContentType, + }; + + // Act + formatter.WriteResponseHeaders(context); + + // Assert + Assert.Null(MediaTypeHeaderValue.Parse(context.ContentType.Value).Encoding); + Assert.Equal(testContentType, context.ContentType); + + // If we had set an encoding, it would be part of the content type header + Assert.Equal(MediaTypeHeaderValue.Parse(testContentType.Value), context.HttpContext.Response.GetTypedHeaders().ContentType); + } + + [Fact] + public async Task WriteAsync_ReturnsNotAcceptable_IfSelectCharacterEncodingReturnsNull() + { + // Arrange + var formatter = new OverrideEncodingFormatter(encoding: null); + + var testContentType = new StringSegment("text/json"); + formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); + + var context = new OutputFormatterWriteContext( + new DefaultHttpContext(), + new TestHttpResponseStreamWriterFactory().CreateWriter, + objectType: null, + @object: null) + { + ContentType = testContentType, + }; + + // Act + await formatter.WriteAsync(context); + + // Assert + Assert.Equal(StatusCodes.Status406NotAcceptable, context.HttpContext.Response.StatusCode); + } + + private class TestOutputFormatter : TextOutputFormatter + { + public TestOutputFormatter() + { + SupportedMediaTypes.Add("application/acceptCharset"); + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + return Task.FromResult(true); + } + } + + private class OverrideEncodingFormatter : TextOutputFormatter + { + private readonly Encoding _encoding; + + public OverrideEncodingFormatter(Encoding encoding) + { + _encoding = encoding; + } + + public override Encoding SelectCharacterEncoding(OutputFormatterWriteContext context) + { + return _encoding; + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + return Task.FromResult(true); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs index 61f700fec8..31b03e356e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs @@ -520,7 +520,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private class TestJsonOutputFormatter : OutputFormatter + private class TestJsonOutputFormatter : TextOutputFormatter { public TestJsonOutputFormatter() { @@ -530,13 +530,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { return Task.FromResult(0); } } - private class TestXmlOutputFormatter : OutputFormatter + private class TestXmlOutputFormatter : TextOutputFormatter { public TestXmlOutputFormatter() { @@ -546,13 +546,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { return Task.FromResult(0); } } - private class TestStringOutputFormatter : OutputFormatter + private class TestStringOutputFormatter : TextOutputFormatter { public TestStringOutputFormatter() { @@ -561,7 +561,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { return Task.FromResult(0); } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs index 30b03f6816..6b2741a111 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var outputFormatterContext = GetOutputFormatterContext(person, typeof(User)); // Act - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8); // Assert var body = outputFormatterContext.HttpContext.Response.Body; @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // This will create a serializer - which gets cached. var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User)); - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1, Encoding.UTF8); // These changes should have no effect. jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User)); // Act - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2, Encoding.UTF8); // Assert var body = outputFormatterContext2.HttpContext.Response.Body; @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // This will create a serializer - which gets cached. var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User)); - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1, Encoding.UTF8); // This results in a new serializer being created. jsonFormatter.SerializerSettings = new JsonSerializerSettings() @@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User)); // Act - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2, Encoding.UTF8); // Assert var body = outputFormatterContext2.HttpContext.Response.Body; @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var outputFormatterContext = GetOutputFormatterContext(person, typeof(User)); // Act - await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8); // Assert var body = outputFormatterContext.HttpContext.Response.Body; @@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters memStream); // Act - await formatter.WriteResponseBodyAsync(outputFormatterContext); + await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8); // Assert memStream.Position = 0; @@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters }; // Act - await formatter.WriteResponseBodyAsync(outputFormatterContext); + await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString)); // Assert var actualData = body.ToArray(); diff --git a/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs b/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs index 5d46cc63a4..61274b8716 100644 --- a/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs +++ b/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs @@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers; namespace BasicWebSite.Formatters { - public class CustomFormatter : OutputFormatter + public class CustomFormatter : TextOutputFormatter { public string ContentType { get; private set; } @@ -33,7 +33,7 @@ namespace BasicWebSite.Formatters return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var response = context.HttpContext.Response; response.ContentType = ContentType + ";charset=utf-8"; diff --git a/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs b/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs index 19ada47c7b..5978642dee 100644 --- a/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs +++ b/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs @@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers; namespace BasicWebSite.Formatters { - public class PlainTextFormatter : OutputFormatter + public class PlainTextFormatter : TextOutputFormatter { public PlainTextFormatter() { @@ -31,7 +31,7 @@ namespace BasicWebSite.Formatters return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var response = context.HttpContext.Response; response.ContentType = "text/plain;charset=utf-8"; diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs index f54bb574fe..731e4dd0b2 100644 --- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs +++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs @@ -15,7 +15,7 @@ namespace BasicWebSite.Formatters /// /// Provides contact information of a person through VCard format. /// - public class VCardFormatter_V3 : OutputFormatter + public class VCardFormatter_V3 : TextOutputFormatter { public VCardFormatter_V3() { @@ -28,7 +28,7 @@ namespace BasicWebSite.Formatters return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var contact = (Contact)context.Object; @@ -38,8 +38,6 @@ namespace BasicWebSite.Formatters builder.AppendLine(); builder.AppendLine("END:VCARD"); - var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8; - await context.HttpContext.Response.WriteAsync( builder.ToString(), selectedEncoding); diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs index 31caee654b..be5d28cfac 100644 --- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs +++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs @@ -16,7 +16,7 @@ namespace BasicWebSite.Formatters /// Provides contact information of a person through VCard format. /// In version 4.0 of VCard format, Gender is a supported property. /// - public class VCardFormatter_V4 : OutputFormatter + public class VCardFormatter_V4 : TextOutputFormatter { public VCardFormatter_V4() { @@ -29,7 +29,7 @@ namespace BasicWebSite.Formatters return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); } - public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var contact = (Contact)context.Object; @@ -41,8 +41,6 @@ namespace BasicWebSite.Formatters builder.AppendLine(); builder.AppendLine("END:VCARD"); - var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8; - await context.HttpContext.Response.WriteAsync( builder.ToString(), selectedEncoding);