[Fixes #6858] Changes to MvcOption's settings (SuppressInputFormatterBuffering & AllowBindingUndefinedValueToEnumType) are not taking affect

This commit is contained in:
Kiran Challa 2017-11-28 13:24:34 -08:00
parent 3ca5955b31
commit 629f87181a
10 changed files with 384 additions and 39 deletions

View File

@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options)); options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options));
options.ModelBinderProviders.Add(new HeaderModelBinderProvider()); options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider()); options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options.AllowBindingUndefinedValueToEnumType)); options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options));
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider()); options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider()); options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider()); options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());

View File

@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary> /// </summary>
public class EnumTypeModelBinderProvider : IModelBinderProvider public class EnumTypeModelBinderProvider : IModelBinderProvider
{ {
private readonly bool _allowBindingUndefinedValueToEnumType; private readonly MvcOptions _options;
public EnumTypeModelBinderProvider(bool allowBindingUndefinedValueToEnumType) public EnumTypeModelBinderProvider(MvcOptions options)
{ {
_allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType; _options = options;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (context.Metadata.IsEnum) if (context.Metadata.IsEnum)
{ {
return new EnumTypeModelBinder( return new EnumTypeModelBinder(
_allowBindingUndefinedValueToEnumType, _options.AllowBindingUndefinedValueToEnumType,
context.Metadata.UnderlyingOrModelType); context.Metadata.UnderlyingOrModelType);
} }

View File

@ -69,8 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
_jsonSerializerSettings, _jsonSerializerSettings,
_charPool, _charPool,
_objectPoolProvider, _objectPoolProvider,
options.SuppressInputFormatterBuffering, options));
options.SuppressJsonDeserializationExceptionMessagesInModelState));
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>(); var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
options.InputFormatters.Add(new JsonInputFormatter( options.InputFormatters.Add(new JsonInputFormatter(
@ -78,8 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
_jsonSerializerSettings, _jsonSerializerSettings,
_charPool, _charPool,
_objectPoolProvider, _objectPoolProvider,
options.SuppressInputFormatterBuffering, options));
options.SuppressJsonDeserializationExceptionMessagesInModelState));
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json")); options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private readonly IArrayPool<char> _charPool; private readonly IArrayPool<char> _charPool;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ObjectPoolProvider _objectPoolProvider; private readonly ObjectPoolProvider _objectPoolProvider;
private readonly MvcOptions _options;
private readonly bool _suppressInputFormatterBuffering; private readonly bool _suppressInputFormatterBuffering;
private readonly bool _suppressJsonDeserializationExceptionMessages; private readonly bool _suppressJsonDeserializationExceptionMessages;
@ -43,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </param> /// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter( public JsonInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -65,6 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter( public JsonInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -90,6 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
/// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param> /// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter( public JsonInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -133,6 +137,59 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
} }
/// <summary>
/// Initializes a new instance of <see cref="JsonInputFormatter"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="serializerSettings">
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
/// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="options">The <see cref="MvcOptions"/>.</param>
public JsonInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider,
MvcOptions options)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (serializerSettings == null)
{
throw new ArgumentNullException(nameof(serializerSettings));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
if (objectPoolProvider == null)
{
throw new ArgumentNullException(nameof(objectPoolProvider));
}
_logger = logger;
SerializerSettings = serializerSettings;
_charPool = new JsonArrayPool<char>(charPool);
_objectPoolProvider = objectPoolProvider;
_options = options;
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
}
/// <inheritdoc /> /// <inheritdoc />
public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
{ {
@ -172,7 +229,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
if (!request.Body.CanSeek && !_suppressInputFormatterBuffering) var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering;
if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
{ {
// JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning. // read everything into a buffer, and then seek back to the beginning.
@ -352,8 +411,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// we regard as having safe messages to expose to clients // we regard as having safe messages to expose to clients
var isJsonExceptionType = var isJsonExceptionType =
exception is JsonReaderException || exception is JsonSerializationException; exception is JsonReaderException || exception is JsonSerializationException;
var suppressJsonDeserializationExceptionMessages = _options?.SuppressJsonDeserializationExceptionMessagesInModelState ?? _suppressJsonDeserializationExceptionMessages;
var suppressOriginalMessage = var suppressOriginalMessage =
_suppressJsonDeserializationExceptionMessages || !isJsonExceptionType; suppressJsonDeserializationExceptionMessages || !isJsonExceptionType;
return suppressOriginalMessage return suppressOriginalMessage
? exception ? exception
: new InputFormatterException(exception.Message, exception); : new InputFormatterException(exception.Message, exception);

View File

@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </param> /// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter( public JsonPatchInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter( public JsonPatchInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -74,6 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
/// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param> /// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter( public JsonPatchInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
@ -89,6 +92,32 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch);
} }
/// <summary>
/// Initializes a new <see cref="JsonPatchInputFormatter"/> instance.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="serializerSettings">
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
/// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="options">The <see cref="MvcOptions"/>.</param>
public JsonPatchInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider,
MvcOptions options)
: base(logger, serializerSettings, charPool, objectPoolProvider, options)
{
// Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch);
}
/// <inheritdoc /> /// <inheritdoc />
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy public override InputFormatterExceptionModelStatePolicy ExceptionPolicy
{ {

View File

@ -799,7 +799,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly bool _throwNonInputFormatterException; private readonly bool _throwNonInputFormatterException;
public TestableJsonInputFormatter(bool throwNonInputFormatterException) public TestableJsonInputFormatter(bool throwNonInputFormatterException)
: base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider()) : base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider(), new MvcOptions())
{ {
_throwNonInputFormatterException = throwNonInputFormatterException; _throwNonInputFormatterException = throwNonInputFormatterException;
} }
@ -863,7 +863,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly bool _throwNonInputFormatterException; private readonly bool _throwNonInputFormatterException;
public DerivedJsonInputFormatter(bool throwNonInputFormatterException) public DerivedJsonInputFormatter(bool throwNonInputFormatterException)
: base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider()) : base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider(), new MvcOptions())
{ {
_throwNonInputFormatterException = throwNonInputFormatterException; _throwNonInputFormatterException = throwNonInputFormatterException;
} }

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ReturnsBinder_ForEnumType(Type modelType) public void ReturnsBinder_ForEnumType(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: true); var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true });
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ReturnsBinder_ForFlagsEnumType(Type modelType) public void ReturnsBinder_ForFlagsEnumType(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: true); var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true });
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType) public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: false); var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = false });
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act

View File

@ -5,6 +5,7 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Options;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
@ -281,7 +282,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ modelName, valueProviderValue } { modelName, valueProviderValue }
} }
}; };
var binderProvider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType); var binderProvider = new EnumTypeModelBinderProvider(new MvcOptions
{
AllowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType
});
var binder = binderProvider.GetBinder(binderProviderContext); var binder = binderProvider.GetBinder(binderProviderContext);
return (bindingContext, binder); return (bindingContext, binder);
} }

View File

@ -33,8 +33,59 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}"; var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger(); var logger = GetLogger();
#pragma warning disable CS0618
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider);
#pragma warning restore CS0618
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
}
[Fact]
public async Task BuffersRequestBody_UsingDefaultOptions()
{
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
var formatter = new JsonInputFormatter(
logger,
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -78,8 +129,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}"; var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger(); var logger = GetLogger();
#pragma warning disable CS0618
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
#pragma warning restore CS0618
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -105,7 +158,99 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
// Reading again should not fail as the request body should have been buffered by the formatter Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
Assert.Null(result.Model);
}
[Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMvcOptions_DoesNotBufferRequestBody()
{
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
var mvcOptions = new MvcOptions();
mvcOptions.SuppressInputFormatterBuffering = true;
var formatter = new JsonInputFormatter(
logger,
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
mvcOptions);
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
Assert.Null(result.Model);
}
[Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions()
{
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
var mvcOptions = new MvcOptions();
mvcOptions.SuppressInputFormatterBuffering = false;
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, mvcOptions);
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
// Mutate options after passing into the constructor to make sure that the value type is not store in the constructor
mvcOptions.SuppressInputFormatterBuffering = true;
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(context);
// Assert // Assert
@ -135,7 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var loggerMock = GetLogger(); var loggerMock = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes("content"); var contentBytes = Encoding.UTF8.GetBytes("content");
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType); var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
@ -161,7 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var loggerMock = GetLogger(); var loggerMock = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
// Act // Act
var mediaType = formatter.SupportedMediaTypes[0]; var mediaType = formatter.SupportedMediaTypes[0];
@ -188,7 +333,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
@ -216,7 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: '30'}"; var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
@ -246,7 +391,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]"; var content = "[0, 23, 300]";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -280,7 +425,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]"; var content = "[0, 23, 300]";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -310,7 +455,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: 'not-an-age'}"; var content = "{name: 'Person Name', Age: 'not-an-age'}";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -341,7 +486,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]"; var content = "[0, 23, 300]";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -371,7 +516,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]"; var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -402,7 +547,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: 'not-an-age'}"; var content = "{name: 'Person Name', Age: 'not-an-age'}";
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -440,7 +585,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -488,7 +633,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// by default we ignore missing members, so here explicitly changing it // by default we ignore missing members, so here explicitly changing it
var serializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error }; var serializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error };
var jsonFormatter = var jsonFormatter =
new JsonInputFormatter(logger, serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8"); var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
@ -547,7 +692,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
@ -579,8 +724,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = new JsonInputFormatter( var formatter = new JsonInputFormatter(
logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, logger,
suppressInputFormatterBuffering: false, suppressJsonDeserializationExceptionMessages: true); _serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions()
{
SuppressInputFormatterBuffering = false,
SuppressJsonDeserializationExceptionMessagesInModelState = true
});
var contentBytes = Encoding.UTF8.GetBytes("{"); var contentBytes = Encoding.UTF8.GetBytes("{");
var modelStateKey = string.Empty; var modelStateKey = string.Empty;
@ -611,7 +763,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private class TestableJsonInputFormatter : JsonInputFormatter private class TestableJsonInputFormatter : JsonInputFormatter
{ {
public TestableJsonInputFormatter(JsonSerializerSettings settings) public TestableJsonInputFormatter(JsonSerializerSettings settings)
: base(GetLogger(), settings, ArrayPool<char>.Shared, _objectPoolProvider) : base(GetLogger(), settings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions())
{ {
} }

View File

@ -30,8 +30,57 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
#pragma warning disable CS0618
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider);
#pragma warning restore CS0618
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value);
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value);
}
[Fact]
public async Task BuffersRequestBody_UsingDefaultOptions()
{
// Arrange
var logger = GetLogger();
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -77,8 +126,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
#pragma warning disable CS0618
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
#pragma warning restore CS0618
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -106,6 +157,57 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDoc.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
Assert.Null(result.Model);
}
[Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions_DoesNotBufferRequestBody()
{
// Arrange
var logger = GetLogger();
var mvcOptions = new MvcOptions();
mvcOptions.SuppressInputFormatterBuffering = false;
var formatter = new JsonPatchInputFormatter(
logger,
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
mvcOptions);
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
// Mutate options after passing into the constructor to make sure that the value type is not store in the constructor
mvcOptions.SuppressInputFormatterBuffering = true;
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(context);
// Assert // Assert
@ -119,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -151,7 +253,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," + var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," +
"{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]"; "{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -190,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -220,7 +322,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange // Arrange
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
@ -251,7 +353,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var logger = GetLogger(); var logger = GetLogger();
var formatter = var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);