[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 HeaderModelBinderProvider());
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 CancellationTokenModelBinderProvider());
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());

View File

@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary>
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 />
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (context.Metadata.IsEnum)
{
return new EnumTypeModelBinder(
_allowBindingUndefinedValueToEnumType,
_options.AllowBindingUndefinedValueToEnumType,
context.Metadata.UnderlyingOrModelType);
}

View File

@ -69,8 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
_jsonSerializerSettings,
_charPool,
_objectPoolProvider,
options.SuppressInputFormatterBuffering,
options.SuppressJsonDeserializationExceptionMessagesInModelState));
options));
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
options.InputFormatters.Add(new JsonInputFormatter(
@ -78,8 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
_jsonSerializerSettings,
_charPool,
_objectPoolProvider,
options.SuppressInputFormatterBuffering,
options.SuppressJsonDeserializationExceptionMessagesInModelState));
options));
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 ILogger _logger;
private readonly ObjectPoolProvider _objectPoolProvider;
private readonly MvcOptions _options;
private readonly bool _suppressInputFormatterBuffering;
private readonly bool _suppressJsonDeserializationExceptionMessages;
@ -43,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -65,6 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</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(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -90,6 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</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>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -133,6 +137,59 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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 />
public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
{
@ -172,7 +229,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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
// 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
var isJsonExceptionType =
exception is JsonReaderException || exception is JsonSerializationException;
var suppressJsonDeserializationExceptionMessages = _options?.SuppressJsonDeserializationExceptionMessagesInModelState ?? _suppressJsonDeserializationExceptionMessages;
var suppressOriginalMessage =
_suppressJsonDeserializationExceptionMessages || !isJsonExceptionType;
suppressJsonDeserializationExceptionMessages || !isJsonExceptionType;
return suppressOriginalMessage
? exception
: new InputFormatterException(exception.Message, exception);

View File

@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </param>
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</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(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -74,6 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</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>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
@ -89,6 +92,32 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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 />
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy
{

View File

@ -799,7 +799,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly 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;
}
@ -863,7 +863,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly 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;
}

View File

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

View File

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

View File

@ -33,8 +33,59 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
#pragma warning disable CS0618
var formatter =
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 modelState = new ModelStateDictionary();
@ -78,8 +129,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
#pragma warning disable CS0618
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
#pragma warning restore CS0618
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
@ -105,7 +158,99 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.Equal("Person Name", userModel.Name);
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);
// Assert
@ -135,7 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var loggerMock = GetLogger();
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 httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
@ -161,7 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var loggerMock = GetLogger();
var formatter =
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider);
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
// Act
var mediaType = formatter.SupportedMediaTypes[0];
@ -188,7 +333,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 httpContext = GetHttpContext(contentBytes);
@ -216,7 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
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 httpContext = GetHttpContext(contentBytes);
@ -246,7 +391,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]";
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -280,7 +425,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]";
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -310,7 +455,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: 'not-an-age'}";
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -341,7 +486,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "[0, 23, 300]";
var logger = GetLogger();
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 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 logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -402,7 +547,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var content = "{name: 'Person Name', Age: 'not-an-age'}";
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -440,7 +585,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -488,7 +633,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// by default we ignore missing members, so here explicitly changing it
var serializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error };
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 httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
@ -547,7 +692,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 modelState = new ModelStateDictionary();
@ -579,8 +724,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
var formatter = new JsonInputFormatter(
logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider,
suppressInputFormatterBuffering: false, suppressJsonDeserializationExceptionMessages: true);
logger,
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions()
{
SuppressInputFormatterBuffering = false,
SuppressJsonDeserializationExceptionMessagesInModelState = true
});
var contentBytes = Encoding.UTF8.GetBytes("{");
var modelStateKey = string.Empty;
@ -611,7 +763,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private class TestableJsonInputFormatter : JsonInputFormatter
{
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
var logger = GetLogger();
#pragma warning disable CS0618
var formatter =
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 contentBytes = Encoding.UTF8.GetBytes(content);
@ -77,8 +126,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
// Arrange
var logger = GetLogger();
#pragma warning disable CS0618
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
#pragma warning restore CS0618
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
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("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);
// Assert
@ -119,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 contentBytes = Encoding.UTF8.GetBytes(content);
@ -151,7 +253,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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\"}," +
"{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
@ -190,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 contentBytes = Encoding.UTF8.GetBytes(content);
@ -220,7 +322,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Arrange
var logger = GetLogger();
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 contentBytes = Encoding.UTF8.GetBytes(content);
@ -251,7 +353,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var logger = GetLogger();
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 contentBytes = Encoding.UTF8.GetBytes(content);