[Fixes 3961] Consider InputFormatter behavior when we can't read the charset

* Broken InputFormatter into InputFormatter and TextInputFormatter
* Added an exception to ModelState inside ReadAsync on TextInputFormatter
  when we can't find a valid encoding to read the body.
This commit is contained in:
jacalvar 2016-02-01 15:58:18 -08:00
parent 8e8de413f8
commit fe639f028f
13 changed files with 420 additions and 133 deletions

View File

@ -3,14 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
@ -19,33 +14,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </summary>
public abstract class InputFormatter : IInputFormatter, IApiRequestFormatMetadataProvider
{
/// <summary>
/// Returns UTF8 Encoding without BOM and throws on invalid bytes.
/// </summary>
protected static readonly Encoding UTF8EncodingWithoutBOM
= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
/// <summary>
/// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
/// </summary>
protected static readonly Encoding UTF16EncodingLittleEndian
= new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true);
/// <summary>
/// Gets the mutable collection of character encodings supported by
/// this <see cref="InputFormatter"/>. The encodings are
/// used when reading the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; } = new List<Encoding>();
/// <summary>
/// Gets the mutable collection of media type elements supported by
/// this <see cref="InputFormatter"/>.
/// </summary>
public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection();
protected object GetDefaultValueForType(Type modelType)
/// <summary>
/// Gets the default value for a given type. Used to return a default value when the body contains no content.
/// </summary>
/// <param name="modelType">The type of the value.</param>
/// <returns>The default value for the <paramref name="modelType"/> type.</returns>
protected virtual object GetDefaultValueForType(Type modelType)
{
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
if (modelType.GetTypeInfo().IsValueType)
{
return Activator.CreateInstance(modelType);
@ -101,6 +87,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc />
public virtual Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var request = context.HttpContext.Request;
if (request.ContentLength == 0)
{
@ -117,50 +108,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns>
public abstract Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context);
/// <summary>
/// Returns an <see cref="Encoding"/> based on <paramref name="context"/>'s
/// character set.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
/// <returns>
/// An <see cref="Encoding"/> based on <paramref name="context"/>'s
/// character set. <c>null</c> if no supported encoding was found.
/// </returns>
protected Encoding SelectCharacterEncoding(InputFormatterContext context)
{
var request = context.HttpContext.Request;
if (request.ContentType != null)
{
var encoding = MediaType.GetEncoding(request.ContentType);
if (encoding != null)
{
foreach (var supportedEncoding in SupportedEncodings)
{
if (string.Equals(
encoding.WebName,
supportedEncoding.WebName,
StringComparison.OrdinalIgnoreCase))
{
return supportedEncoding;
}
}
}
}
if (SupportedEncodings.Count > 0)
{
return SupportedEncodings[0];
}
// No supported encoding was found so there is no way for us to start reading.
context.ModelState.TryAddModelError(
context.ModelName,
Resources.FormatInputFormatterNoEncoding(GetType().FullName));
return null;
}
/// <inheritdoc />
public IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type objectType)
{

View File

@ -0,0 +1,124 @@
// 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.Mvc.Core;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
/// <summary>
/// Reads an object from a request body with a text format.
/// </summary>
public abstract class TextInputFormatter : InputFormatter
{
/// <summary>
/// Returns UTF8 Encoding without BOM and throws on invalid bytes.
/// </summary>
protected static readonly Encoding UTF8EncodingWithoutBOM
= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
/// <summary>
/// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
/// </summary>
protected static readonly Encoding UTF16EncodingLittleEndian
= new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true);
/// <summary>
/// Gets the mutable collection of character encodings supported by
/// this <see cref="TextInputFormatter"/>. The encodings are
/// used when reading the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; } = new List<Encoding>();
/// <inheritdoc />
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var request = context.HttpContext.Request;
var selectedEncoding = SelectCharacterEncoding(context);
if (selectedEncoding == null)
{
var message = Resources.FormatUnsupportedContentType(
context.HttpContext.Request.ContentType);
var exception = new UnsupportedContentTypeException(message);
context.ModelState.AddModelError(context.ModelName, exception, context.Metadata);
return InputFormatterResult.FailureAsync();
}
return ReadRequestBodyAsync(context, selectedEncoding);
}
/// <summary>
/// Reads an object from the request body.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
/// <param name="encoding">The <see cref="Encoding"/> used to read the request body.</param>
/// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns>
public abstract Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding);
/// <summary>
/// Returns an <see cref="Encoding"/> based on <paramref name="context"/>'s
/// character set.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
/// <returns>
/// An <see cref="Encoding"/> based on <paramref name="context"/>'s
/// character set. <c>null</c> if no supported encoding was found.
/// </returns>
protected Encoding SelectCharacterEncoding(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (SupportedEncodings?.Count == 0)
{
var message = Resources.FormatTextInputFormatter_SupportedEncodingsMustNotBeEmpty(nameof(SupportedEncodings));
throw new InvalidOperationException(message);
}
var request = context.HttpContext.Request;
var requestEncoding = request.ContentType == null ? null : MediaType.GetEncoding(request.ContentType);
if (requestEncoding != null)
{
for (int i = 0; i < SupportedEncodings.Count; i++)
{
if (string.Equals(
requestEncoding.WebName,
SupportedEncodings[i].WebName,
StringComparison.OrdinalIgnoreCase))
{
return SupportedEncodings[i];
}
}
// The client specified an encoding in the content type header of the request
// but we don't understand it. In this situation we don't try to pick any other encoding
// from the list of supported encodings and read the body with that encoding.
// Instead, we return null and that will translate later on into a 415 Unsupported Media Type
// response.
return null;
}
// We want to do our best effort to read the body of the request even in the
// cases where the client doesn't send a content type header or sends a content
// type header without encoding. For that reason we pick the first encoding of the
// list of supported encodings and try to use that to read the body. This encoding
// is UTF-8 by default on our formatters, which generally is a safe choice for the
// encoding.
return SupportedEncodings[0];
}
}
}

View File

@ -1098,6 +1098,22 @@ namespace Microsoft.AspNetCore.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueMustBeNumber"), p0);
}
/// <summary>
/// The list of '{0}' must not be empty. Add at least one supported encoding.
/// </summary>
internal static string TextInputFormatter_SupportedEncodingsMustNotBeEmpty
{
get { return GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"); }
}
/// <summary>
/// The list of '{0}' must not be empty. Add at least one supported encoding.
/// </summary>
internal static string FormatTextInputFormatter_SupportedEncodingsMustNotBeEmpty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -331,4 +331,7 @@
<data name="HtmlGeneration_ValueMustBeNumber" xml:space="preserve">
<value>The field {0} must be a number.</value>
</data>
<data name="TextInputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
</data>
</root>

View File

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -13,9 +14,9 @@ using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
/// <summary>
/// An <see cref="InputFormatter"/> for JSON content.
/// An <see cref="TextInputFormatter"/> for JSON content.
/// </summary>
public class JsonInputFormatter : InputFormatter
public class JsonInputFormatter : TextInputFormatter
{
private readonly IArrayPool<char> _charPool;
private readonly ILogger _logger;
@ -116,22 +117,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding encoding)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Get the character encoding for the content.
var effectiveEncoding = SelectCharacterEncoding(context);
if (effectiveEncoding == null)
if (encoding == null)
{
return InputFormatterResult.FailureAsync();
throw new ArgumentNullException(nameof(encoding));
}
var request = context.HttpContext.Request;
using (var streamReader = context.ReaderFactory(request.Body, effectiveEncoding))
using (var streamReader = context.ReaderFactory(request.Body, encoding))
{
using (var jsonReader = new JsonTextReader(streamReader))
{

View File

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
@ -38,14 +39,21 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
public async override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public async override Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding encoding)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var result = await base.ReadRequestBodyAsync(context);
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
var result = await base.ReadRequestBodyAsync(context, encoding);
if (!result.HasError)
{
var jsonPatchDocument = (IJsonPatchDocument)result.Model;
@ -61,6 +69,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc />
public override bool CanRead(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var modelTypeInfo = context.ModelType.GetTypeInfo();
if (!typeof(IJsonPatchDocument).GetTypeInfo().IsAssignableFrom(modelTypeInfo) ||
!modelTypeInfo.IsGenericType)

View File

@ -12,7 +12,6 @@ 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 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// This class handles deserialization of input XML data
/// to strongly-typed objects using <see cref="DataContractSerializer"/>.
/// </summary>
public class XmlDataContractSerializerInputFormatter : InputFormatter
public class XmlDataContractSerializerInputFormatter : TextInputFormatter
{
private DataContractSerializerSettings _serializerSettings;
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
@ -86,16 +85,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var effectiveEncoding = SelectCharacterEncoding(context);
if (effectiveEncoding == null)
if (context == null)
{
return InputFormatterResult.FailureAsync();
throw new ArgumentNullException(nameof(context));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
var request = context.HttpContext.Request;
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
{
var type = GetSerializableType(context.ModelType);
var serializer = GetCachedSerializer(type);
@ -197,6 +200,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>The <see cref="DataContractSerializer"/> instance.</returns>
protected virtual DataContractSerializer GetCachedSerializer(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{

View File

@ -12,7 +12,6 @@ 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 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// This class handles deserialization of input XML data
/// to strongly-typed objects using <see cref="XmlSerializer"/>
/// </summary>
public class XmlSerializerInputFormatter : InputFormatter
public class XmlSerializerInputFormatter : TextInputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
@ -65,16 +64,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
/// <inheritdoc />
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding encoding)
{
var effectiveEncoding = SelectCharacterEncoding(context);
if (effectiveEncoding == null)
if (context == null)
{
return InputFormatterResult.FailureAsync();
throw new ArgumentNullException(nameof(context));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
var request = context.HttpContext.Request;
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
{
var type = GetSerializableType(context.ModelType);
@ -171,6 +176,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>The <see cref="XmlSerializer"/> instance.</returns>
protected virtual XmlSerializer GetCachedSerializer(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
@ -1455,11 +1456,13 @@ namespace Microsoft.AspNetCore.Mvc.Description
public int Id { get; set; }
}
private class MockInputFormatter : InputFormatter
private class MockInputFormatter : TextInputFormatter
{
public List<Type> SupportedTypes { get; } = new List<Type>();
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding effectiveEncoding)
{
throw new NotImplementedException();
}

View File

@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Net.Http.Headers;
using Xunit;

View File

@ -0,0 +1,167 @@
// 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.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
public class TextInputFormatterTest
{
[Fact]
public async Task ReadAsync_ReturnsFailure_IfItCanNotUnderstandTheContentTypeEncoding()
{
// Arrange
var formatter = new TestFormatter();
formatter.SupportedEncodings.Add(Encoding.ASCII);
var context = new InputFormatterContext(
new DefaultHttpContext(),
"something",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(stream, encoding) => new StreamReader(stream, encoding));
context.HttpContext.Request.ContentType = "application/json;charset=utf-8";
context.HttpContext.Request.ContentLength = 1;
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.Equal(true, result.HasError);
Assert.Equal(true, context.ModelState.ContainsKey("something"));
Assert.Equal(1, context.ModelState["something"].Errors.Count);
var error = context.ModelState["something"].Errors[0];
Assert.IsType<UnsupportedContentTypeException>(error.Exception);
}
[Fact]
public void SelectCharacterEncoding_ThrowsInvalidOperationException_IfItDoesNotHaveAValidEncoding()
{
// Arrange
var formatter = new TestFormatter();
var context = new InputFormatterContext(
new DefaultHttpContext(),
"something",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(stream, encoding) => new StreamReader(stream, encoding));
context.HttpContext.Request.ContentLength = 1;
// Act & Assert
Assert.Throws<InvalidOperationException>(() => formatter.TestSelectCharacterEncoding(context));
}
[Fact]
public void SelectCharacterEncoding_ReturnsNull_IfItCanNotUnderstandContentTypeEncoding()
{
// Arrange
var formatter = new TestFormatter();
formatter.SupportedEncodings.Add(Encoding.UTF32);
var context = new InputFormatterContext(
new DefaultHttpContext(),
"something",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(stream, encoding) => new StreamReader(stream, encoding));
context.HttpContext.Request.ContentType = "application/json;charset=utf-8";
// Act
var result = formatter.TestSelectCharacterEncoding(context);
// Assert
Assert.Null(result);
}
[Fact]
public void SelectCharacterEncoding_ReturnsContentTypeEncoding_IfItCanUnderstandIt()
{
// Arrange
var formatter = new TestFormatter();
formatter.SupportedEncodings.Add(Encoding.UTF32);
formatter.SupportedEncodings.Add(Encoding.UTF8);
var context = new InputFormatterContext(
new DefaultHttpContext(),
"something",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(stream, encoding) => new StreamReader(stream, encoding));
context.HttpContext.Request.ContentType = "application/json;charset=utf-8";
// Act
var result = formatter.TestSelectCharacterEncoding(context);
// Assert
Assert.Equal(Encoding.UTF8, result);
}
[Theory]
[InlineData("application/json")]
[InlineData("")]
public void SelectCharacterEncoding_ReturnsFirstEncoding_IfContentTypeIsNotSpecifiedOrDoesNotHaveEncoding(string contentType)
{
// Arrange
var formatter = new TestFormatter();
formatter.SupportedEncodings.Add(Encoding.UTF8);
formatter.SupportedEncodings.Add(Encoding.UTF32);
var context = new InputFormatterContext(
new DefaultHttpContext(),
"something",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(stream, encoding) => new StreamReader(stream, encoding));
context.HttpContext.Request.ContentType = contentType;
// Act
var result = formatter.TestSelectCharacterEncoding(context);
// Assert
Assert.Equal(Encoding.UTF8, result);
}
private class TestFormatter : TextInputFormatter
{
private readonly object _object;
public TestFormatter() : this(null) { }
public TestFormatter(object @object)
{
_object = @object;
}
public IList<Type> SupportedTypes { get; } = new List<Type>();
protected override bool CanReadType(Type type)
{
return SupportedTypes.Count == 0 ? true : SupportedTypes.Contains(type);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
return InputFormatterResult.SuccessAsync(_object);
}
public Encoding TestSelectCharacterEncoding(InputFormatterContext context)
{
return SelectCharacterEncoding(context);
}
}
}
}

View File

@ -312,7 +312,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public string Name { get; set; }
}
private class XyzFormatter : InputFormatter
private class XyzFormatter : TextInputFormatter
{
public XyzFormatter()
{
@ -325,7 +325,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return true;
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding effectiveEncoding)
{
throw new InvalidOperationException("Your input is bad!");
}

View File

@ -10,7 +10,7 @@ using Microsoft.Net.Http.Headers;
namespace FormatterWebSite
{
public class StringInputFormatter : InputFormatter
public class StringInputFormatter : TextInputFormatter
{
public StringInputFormatter()
{
@ -20,14 +20,8 @@ namespace FormatterWebSite
SupportedEncodings.Add(Encoding.Unicode);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
{
var effectiveEncoding = SelectCharacterEncoding(context);
if (effectiveEncoding == null)
{
return InputFormatterResult.FailureAsync();
}
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body, effectiveEncoding))
{