[Fixes #4051] Split OutputFormatter into OutputFormatter and TextOutputFormatter

This commit is contained in:
jacalvar 2016-02-03 16:36:21 -08:00
parent 63354e25a8
commit 65858b8d8b
19 changed files with 645 additions and 518 deletions

View File

@ -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());

View File

@ -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
/// </summary>
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
{
private IDictionary<string, string> _outputMediaTypeCache;
/// <summary>
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
/// </summary>
protected OutputFormatter()
{
SupportedEncodings = new List<Encoding>();
SupportedMediaTypes = new MediaTypeCollection();
}
/// <summary>
/// Gets the mutable collection of character encodings supported by
/// this <see cref="OutputFormatter"/>. The encodings are
/// used when writing the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; }
/// <summary>
/// Gets the mutable collection of media type elements supported by
/// this <see cref="OutputFormatter"/>.
/// </summary>
public MediaTypeCollection SupportedMediaTypes { get; }
private IDictionary<string, string> OutputMediaTypeCache
{
get
{
if (_outputMediaTypeCache == null)
{
var cache = new Dictionary<string, string>();
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;
}
}
/// <summary>
/// Returns a value indicating whether or not the given type can be written by this serializer.
/// </summary>
@ -115,49 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
}
/// <summary>
/// Determines the best <see cref="Encoding"/> amongst the supported encodings
/// for reading or writing an HTTP entity body based on the provided <paramref name="contentTypeHeader"/>.
/// </summary>
/// <param name="context">The formatter context associated with the call.
/// </param>
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
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;
}
/// <inheritdoc />
public virtual bool CanWriteResult(OutputFormatterCanWriteContext context)
{
@ -206,46 +127,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
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
/// <param name="context">The formatter context associated with the call.</param>
/// <returns>A task which can write the response body.</returns>
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<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue> Sort(IList<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue>();
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;
}
}
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <summary>
/// Always writes a string value to the response, regardless of requested content type.
/// </summary>
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);
}
}
}

View File

@ -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
{
/// <summary>
/// Writes an object in a given text format to the output stream.
/// </summary>
public abstract class TextOutputFormatter : OutputFormatter
{
private IDictionary<string, string> _outputMediaTypeCache;
/// <summary>
/// Initializes a new instance of the <see cref="TextOutputFormatter"/> class.
/// </summary>
protected TextOutputFormatter()
{
SupportedEncodings = new List<Encoding>();
}
/// <summary>
/// Gets the mutable collection of character encodings supported by
/// this <see cref="TextOutputFormatter"/>. The encodings are
/// used when writing the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; }
private IDictionary<string, string> OutputMediaTypeCache
{
get
{
if (_outputMediaTypeCache == null)
{
var cache = new Dictionary<string, string>();
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;
}
}
/// <summary>
/// Determines the best <see cref="Encoding"/> amongst the supported encodings
/// for reading or writing an HTTP entity body based on the provided <paramref name="contentTypeHeader"/>.
/// </summary>
/// <param name="context">The formatter context associated with the call.
/// </param>
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
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];
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <summary>
/// Writes the response body.
/// </summary>
/// <param name="context">The formatter context associated with the call.</param>
/// <param name="selectedEncoding">The <see cref="Encoding"/> that should be used to write the response.</param>
/// <returns>A task which can write the response body.</returns>
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<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue> Sort(IList<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue>();
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;
}
}
}

View File

@ -1114,6 +1114,38 @@ namespace Microsoft.AspNetCore.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
}
/// <summary>
/// The list of '{0}' must not be empty. Add at least one supported encoding.
/// </summary>
internal static string TextOutputFormatter_SupportedEncodingsMustNotBeEmpty
{
get { return GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"); }
}
/// <summary>
/// The list of '{0}' must not be empty. Add at least one supported encoding.
/// </summary>
internal static string FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
}
/// <summary>
/// '{0}' is not supported by '{1}'. Use '{2}' instead.
/// </summary>
internal static string TextOutpurFormatter_WriteResponseBodyAsynNotSupported
{
get { return GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"); }
}
/// <summary>
/// '{0}' is not supported by '{1}'. Use '{2}' instead.
/// </summary>
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);

View File

@ -334,4 +334,10 @@
<data name="TextInputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
</data>
<data name="TextOutputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
</data>
<data name="TextOutpurFormatter_WriteResponseBodyAsynNotSupported" xml:space="preserve">
<value>'{0}' is not supported by '{1}'. Use '{2}' instead.</value>
</data>
</root>

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <summary>
/// An output formatter that specializes in writing JSON content.
/// </summary>
public class JsonOutputFormatter : OutputFormatter
public class JsonOutputFormatter : TextOutputFormatter
{
private readonly IArrayPool<char> _charPool;
@ -83,6 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
}
/// <summary>
/// Writes the given <paramref name="value"/> as JSON using the given
/// <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> used to write the <paramref name="value"/></param>
/// <param name="value">The value to write as JSON.</param>
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)
/// <inheritdoc />
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);

View File

@ -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 <see cref="DataContractSerializer"/>
/// </summary>
public class XmlDataContractSerializerOutputFormatter : OutputFormatter
public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter
{
private DataContractSerializerSettings _serializerSettings;
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
@ -180,15 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
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;

View File

@ -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 <see cref="XmlSerializer"/>
/// </summary>
public class XmlSerializerOutputFormatter : OutputFormatter
public class XmlSerializerOutputFormatter : TextOutputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
@ -155,17 +153,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
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;

View File

@ -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();
}

View File

@ -18,228 +18,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
public class OutputFormatterTests
{
public static IEnumerable<object[]> 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<HttpContext>();
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<OutputFormatter>();
formatter
.Setup(f => f.SelectCharacterEncoding(It.IsAny<OutputFormatterWriteContext>()))
.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)

View File

@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Encoding encoding = Encoding.UTF8;
var memoryStream = new MemoryStream();
var response = new Mock<HttpResponse>();
response.SetupProperty<long?>(o => o.ContentLength);
response.SetupProperty(o => o.ContentLength);
response.SetupGet(r => r.Body).Returns(memoryStream);
var httpContext = new Mock<HttpContext>();
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);

View File

@ -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<object[]> 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<HttpContext>();
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);
}
}
}
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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";

View File

@ -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";

View File

@ -15,7 +15,7 @@ namespace BasicWebSite.Formatters
/// <summary>
/// Provides contact information of a person through VCard format.
/// </summary>
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);

View File

@ -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.
/// </summary>
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);