diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index e487da16af..88aa088830 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -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());
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs
index c7ef8be646..5bf700e536 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs
@@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
///
public class EnumTypeModelBinderProvider : IModelBinderProvider
{
- private readonly bool _allowBindingUndefinedValueToEnumType;
+ private readonly MvcOptions _options;
- public EnumTypeModelBinderProvider(bool allowBindingUndefinedValueToEnumType)
+ public EnumTypeModelBinderProvider(MvcOptions options)
{
- _allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType;
+ _options = options;
}
///
@@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (context.Metadata.IsEnum)
{
return new EnumTypeModelBinder(
- _allowBindingUndefinedValueToEnumType,
+ _options.AllowBindingUndefinedValueToEnumType,
context.Metadata.UnderlyingOrModelType);
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
index ad32ab6505..ad2af947ed 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
@@ -69,8 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
_jsonSerializerSettings,
_charPool,
_objectPoolProvider,
- options.SuppressInputFormatterBuffering,
- options.SuppressJsonDeserializationExceptionMessagesInModelState));
+ options));
var jsonInputLogger = _loggerFactory.CreateLogger();
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"));
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
index e0c5ed650a..2500296a6a 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
@@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private readonly IArrayPool _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
///
/// The .
/// The .
+ [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
/// The .
/// The .
/// Flag to buffer entire request body before deserializing it.
+ [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
/// The .
/// Flag to buffer entire request body before deserializing it.
/// If , JSON deserialization exception messages will replaced by a generic message in model state.
+ [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);
}
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ ///
+ /// The . Should be either the application-wide settings
+ /// () or an instance
+ /// initially returned.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ public JsonInputFormatter(
+ ILogger logger,
+ JsonSerializerSettings serializerSettings,
+ ArrayPool 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(charPool);
+ _objectPoolProvider = objectPoolProvider;
+ _options = options;
+
+ SupportedEncodings.Add(UTF8EncodingWithoutBOM);
+ SupportedEncodings.Add(UTF16EncodingLittleEndian);
+
+ SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
+ SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
+ SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
+ }
+
///
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);
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
index 9f1c94945b..6e704e8348 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
@@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
///
/// The .
/// The .
+ [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
/// The .
/// The .
/// Flag to buffer entire request body before deserializing it.
+ [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
/// The .
/// Flag to buffer entire request body before deserializing it.
/// If , JSON deserialization exception messages will replaced by a generic message in model state.
+ [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);
}
+ ///
+ /// Initializes a new instance.
+ ///
+ /// The .
+ ///
+ /// The . Should be either the application-wide settings
+ /// () or an instance
+ /// initially returned.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ public JsonPatchInputFormatter(
+ ILogger logger,
+ JsonSerializerSettings serializerSettings,
+ ArrayPool 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);
+ }
+
///
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
index 84e0c5cb69..b50afa25cf 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
@@ -799,7 +799,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly bool _throwNonInputFormatterException;
public TestableJsonInputFormatter(bool throwNonInputFormatterException)
- : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider())
+ : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.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.Shared, new DefaultObjectPoolProvider())
+ : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider(), new MvcOptions())
{
_throwNonInputFormatterException = throwNonInputFormatterException;
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs
index 6773822709..70444d731c 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs
@@ -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
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs
index 88d461d78b..2379c0c933 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs
@@ -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);
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
index e879f4f424..88e712ae90 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
@@ -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.Shared, _objectPoolProvider);
+#pragma warning restore CS0618
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+
+ var modelState = new ModelStateDictionary();
+ var httpContext = new DefaultHttpContext();
+ httpContext.Features.Set(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(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(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.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.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.Shared,
+ _objectPoolProvider,
+ mvcOptions);
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+
+ var modelState = new ModelStateDictionary();
+ var httpContext = new DefaultHttpContext();
+ httpContext.Features.Set(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(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.Shared, _objectPoolProvider, mvcOptions);
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+
+ var modelState = new ModelStateDictionary();
+ var httpContext = new DefaultHttpContext();
+ httpContext.Features.Set(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(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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider,
- suppressInputFormatterBuffering: false, suppressJsonDeserializationExceptionMessages: true);
+ logger,
+ _serializerSettings,
+ ArrayPool.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.Shared, _objectPoolProvider)
+ : base(GetLogger(), settings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions())
{
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
index 202d008019..a998486402 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
@@ -30,8 +30,57 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
// Arrange
var logger = GetLogger();
+#pragma warning disable CS0618
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.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(new TestResponseFeature());
+ httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
+ httpContext.Request.ContentType = "application/json";
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument));
+ 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>(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>(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.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.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.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(new TestResponseFeature());
+ httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
+ httpContext.Request.ContentType = "application/json";
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument));
+ 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>(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.Shared, _objectPoolProvider);
+ new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.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.Shared, _objectPoolProvider);
+ new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);