diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs
index 155ee032d5..91372a0d19 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs
@@ -51,7 +51,9 @@ namespace Microsoft.AspNetCore.Mvc
///
/// ASP.NET Core MVC 2.1 introduces compatibility switches for the following:
///
+ ///
///
+ /// - MvcJsonOptions.AllowInputFormatterExceptionMessages
/// - RazorPagesOptions.AllowAreas
///
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
index 4e4ca3188a..4c164d162f 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
@@ -24,9 +24,11 @@ namespace Microsoft.AspNetCore.Mvc
// See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType;
- private readonly CompatibilitySwitch _suppressJsonDeserializationExceptionMessagesInModelState;
private readonly ICompatibilitySwitch[] _switches;
+ ///
+ /// Creates a new instance of .
+ ///
public MvcOptions()
{
CacheProfiles = new Dictionary(StringComparer.OrdinalIgnoreCase);
@@ -43,12 +45,11 @@ namespace Microsoft.AspNetCore.Mvc
_inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType));
- _suppressJsonDeserializationExceptionMessagesInModelState = new CompatibilitySwitch(nameof(SuppressJsonDeserializationExceptionMessagesInModelState));
+
_switches = new ICompatibilitySwitch[]
{
_inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType,
- _suppressJsonDeserializationExceptionMessagesInModelState,
};
}
@@ -241,20 +242,6 @@ namespace Microsoft.AspNetCore.Mvc
///
public bool RequireHttpsPermanent { get; set; }
-
- ///
- /// Gets or sets a flag to determine whether, if an action receives invalid JSON in
- /// the request body, the JSON deserialization exception message should be replaced
- /// by a generic error message in model state.
- /// by default, meaning that clients may receive details about
- /// why the JSON they posted is considered invalid.
- ///
- public bool SuppressJsonDeserializationExceptionMessagesInModelState
- {
- get => _suppressJsonDeserializationExceptionMessagesInModelState.Value;
- set => _suppressJsonDeserializationExceptionMessagesInModelState.Value = value;
- }
-
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_switches).GetEnumerator();
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs
index 598e38868b..accd9045eb 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs
@@ -75,6 +75,8 @@ namespace Microsoft.Extensions.DependencyInjection
{
services.TryAddEnumerable(
ServiceDescriptor.Transient, MvcJsonMvcOptionsSetup>());
+ services.TryAddEnumerable(
+ ServiceDescriptor.Transient, MvcJsonOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable(
ServiceDescriptor.Transient());
services.TryAddSingleton();
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
index ad2af947ed..56f14f8185 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs
@@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
-using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
@@ -20,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
public class MvcJsonMvcOptionsSetup : IConfigureOptions
{
private readonly ILoggerFactory _loggerFactory;
- private readonly JsonSerializerSettings _jsonSerializerSettings;
+ private readonly MvcJsonOptions _jsonOptions;
private readonly ArrayPool _charPool;
private readonly ObjectPoolProvider _objectPoolProvider;
@@ -51,14 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
}
_loggerFactory = loggerFactory;
- _jsonSerializerSettings = jsonOptions.Value.SerializerSettings;
+ _jsonOptions = jsonOptions.Value;
_charPool = charPool;
_objectPoolProvider = objectPoolProvider;
}
public void Configure(MvcOptions options)
{
- options.OutputFormatters.Add(new JsonOutputFormatter(_jsonSerializerSettings, _charPool));
+ options.OutputFormatters.Add(new JsonOutputFormatter(_jsonOptions.SerializerSettings, _charPool));
// Register JsonPatchInputFormatter before JsonInputFormatter, otherwise
// JsonInputFormatter would consume "application/json-patch+json" requests
@@ -66,18 +65,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
var jsonInputPatchLogger = _loggerFactory.CreateLogger();
options.InputFormatters.Add(new JsonPatchInputFormatter(
jsonInputPatchLogger,
- _jsonSerializerSettings,
+ _jsonOptions.SerializerSettings,
_charPool,
_objectPoolProvider,
- options));
+ options,
+ _jsonOptions));
var jsonInputLogger = _loggerFactory.CreateLogger();
options.InputFormatters.Add(new JsonInputFormatter(
jsonInputLogger,
- _jsonSerializerSettings,
+ _jsonOptions.SerializerSettings,
_charPool,
_objectPoolProvider,
- options));
+ options,
+ _jsonOptions));
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 bb1c251186..8201e07634 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
@@ -28,8 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private readonly ILogger _logger;
private readonly ObjectPoolProvider _objectPoolProvider;
private readonly MvcOptions _options;
+ private readonly MvcJsonOptions _jsonOptions;
+
+ // These fields are used when one of the legacy constructors is called that doesn't provide the MvcOptions or
+ // MvcJsonOptions.
private readonly bool _suppressInputFormatterBuffering;
- private readonly bool _suppressJsonDeserializationExceptionMessages;
+ private readonly bool _allowInputFormatterExceptionMessages;
private ObjectPool _jsonSerializerPool;
@@ -74,10 +78,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering)
- : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages: false)
+ : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages: false)
{
- // This constructor by default treats JSON deserialization exceptions as safe
- // because this is the default for applications generally
+ // This constructor by default treats JSON deserialization exceptions as unsafe
+ // because this is the default in 2.0
}
///
@@ -92,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// The .
/// The .
/// Flag to buffer entire request body before deserializing it.
- /// If , JSON deserialization exception messages will replaced by a generic message in model state.
+ /// 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,
@@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering,
- bool suppressJsonDeserializationExceptionMessages)
+ bool allowInputFormatterExceptionMessages)
{
if (logger == null)
{
@@ -127,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_charPool = new JsonArrayPool(charPool);
_objectPoolProvider = objectPoolProvider;
_suppressInputFormatterBuffering = suppressInputFormatterBuffering;
- _suppressJsonDeserializationExceptionMessages = suppressJsonDeserializationExceptionMessages;
+ _allowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages;
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
@@ -149,12 +153,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// The .
/// The .
/// The .
+ /// The .
public JsonInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
- MvcOptions options)
+ MvcOptions options,
+ MvcJsonOptions jsonOptions)
{
if (logger == null)
{
@@ -181,6 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_charPool = new JsonArrayPool(charPool);
_objectPoolProvider = objectPoolProvider;
_options = options;
+ _jsonOptions = jsonOptions;
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
@@ -406,17 +413,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private Exception WrapExceptionForModelState(Exception exception)
{
+ // In 2.0 and earlier we always gave a generic error message for errors that come from JSON.NET
+ // We only allow it in 2.1 and newer if the app opts-in.
+ if (!(_jsonOptions?.AllowInputFormatterExceptionMessages ?? _allowInputFormatterExceptionMessages))
+ {
+ // This app is not opted-in to JSON.NET messages, return the original exception.
+ return exception;
+ }
+
// It's not known that Json.NET currently ever raises error events with exceptions
// other than these two types, but we're being conservative and limiting which ones
// 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;
- return suppressOriginalMessage
- ? exception
- : new InputFormatterException(exception.Message, exception);
+ if (exception is JsonReaderException || exception is JsonSerializationException)
+ {
+ // InputFormatterException specifies that the message is safe to return to a client, it will
+ // be added to model state.
+ return new InputFormatterException(exception.Message, exception);
+ }
+
+ // Not a known exception type, so we're not going to assume that it's safe.
+ return exception;
}
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
index 58c5b3a4f3..96549d44d5 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
@@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering)
- : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages: false)
+ : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages: false)
{
}
@@ -75,7 +75,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// The .
/// The .
/// Flag to buffer entire request body before deserializing it.
- /// If , JSON deserialization exception messages will replaced by a generic message in model state.
+ ///
+ /// 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,
@@ -83,8 +85,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering,
- bool suppressJsonDeserializationExceptionMessages)
- : base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages)
+ bool allowInputFormatterExceptionMessages)
+ : base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages)
{
// Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear();
@@ -104,13 +106,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// The .
/// The .
/// The .
+ /// The .
public JsonPatchInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool charPool,
ObjectPoolProvider objectPoolProvider,
- MvcOptions options)
- : base(logger, serializerSettings, charPool, objectPoolProvider, options)
+ MvcOptions options,
+ MvcJsonOptions jsonOptions)
+ : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
// Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear();
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs
index 3e674a58c9..9c94d7b55c 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs
@@ -1,7 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Collections;
+using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Formatters;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc
@@ -9,12 +13,69 @@ namespace Microsoft.AspNetCore.Mvc
///
/// Provides programmatic configuration for JSON in the MVC framework.
///
- public class MvcJsonOptions
+ public class MvcJsonOptions : IEnumerable
{
+ private readonly CompatibilitySwitch _allowInputFormatterExceptionMessages;
+ private readonly ICompatibilitySwitch[] _switches;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public MvcJsonOptions()
+ {
+ _allowInputFormatterExceptionMessages = new CompatibilitySwitch(nameof(AllowInputFormatterExceptionMessages));
+
+ _switches = new ICompatibilitySwitch[]
+ {
+ _allowInputFormatterExceptionMessages,
+ };
+ }
+
+ ///
+ /// Gets or sets a flag to determine whether error messsages from JSON deserialization by the
+ /// will be added to the . The default
+ /// value is false, meaning that a generic error message will be used instead.
+ ///
+ ///
+ ///
+ /// Error messages in the are often communicated to clients, either in HTML
+ /// or using . In effect, this setting controls whether clients can recieve
+ /// detailed error messages about submitted JSON data.
+ ///
+ ///
+ /// This property is associated with a compatibility switch and can provide a different behavior depending on
+ /// the configured compatibility version for the application. See for
+ /// guidance and examples of setting the application's compatibility version.
+ ///
+ ///
+ /// Configuring the desired of the value compatibility switch by calling this property's setter will take precedence
+ /// over the value implied by the application's .
+ ///
+ ///
+ /// If the application's compatibility version is set to then
+ /// this setting will have value false if not explicitly configured.
+ ///
+ ///
+ /// If the application's compatibility version is set to or
+ /// higher then this setting will have value true if not explicitly configured.
+ ///
+ ///
+ public bool AllowInputFormatterExceptionMessages
+ {
+ get => _allowInputFormatterExceptionMessages.Value;
+ set => _allowInputFormatterExceptionMessages.Value = value;
+ }
+
///
/// Gets the that are used by this application.
///
- public JsonSerializerSettings SerializerSettings { get; } =
- JsonSerializerSettingsProvider.CreateSerializerSettings();
+ public JsonSerializerSettings SerializerSettings { get; } = JsonSerializerSettingsProvider.CreateSerializerSettings();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_switches).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs
new file mode 100644
index 0000000000..f33e6dc108
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+ internal class MvcJsonOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions
+ {
+ public MvcJsonOptionsConfigureCompatibilityOptions(
+ ILoggerFactory loggerFactory,
+ IOptions compatibilityOptions)
+ : base(loggerFactory, compatibilityOptions)
+ {
+ }
+
+ protected override IReadOnlyDictionary DefaultValues
+ {
+ get
+ {
+ var values = new Dictionary();
+
+ if (Version >= CompatibilityVersion.Version_2_1)
+ {
+ values[nameof(MvcJsonOptions.AllowInputFormatterExceptionMessages)] = true;
+ }
+
+ return values;
+ }
+ }
+ }
+}
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 10e1641f26..2babd023db 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,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly bool _throwNonInputFormatterException;
public TestableJsonInputFormatter(bool throwNonInputFormatterException)
- : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider(), new MvcOptions())
+ : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider(), new MvcOptions(), new MvcJsonOptions()
+ {
+ // The tests that use this class rely on the 2.1 behavior of this formatter.
+ AllowInputFormatterExceptionMessages = true,
+ })
{
_throwNonInputFormatterException = throwNonInputFormatterException;
}
@@ -865,7 +869,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private readonly bool _throwNonInputFormatterException;
public DerivedJsonInputFormatter(bool throwNonInputFormatterException)
- : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider(), new MvcOptions())
+ : base(GetLogger(), new JsonSerializerSettings(), ArrayPool.Shared, new DefaultObjectPoolProvider(), new MvcOptions(), new MvcJsonOptions()
+ {
+ // The tests that use this class rely on the 2.1 behavior of this formatter.
+ AllowInputFormatterExceptionMessages = true,
+ })
{
_throwNonInputFormatterException = throwNonInputFormatterException;
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
index 88e712ae90..0441961c2a 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
@@ -28,36 +28,32 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings();
[Fact]
- public async Task BuffersRequestBody_ByDefault()
+ public async Task Version_2_0_Constructor_BuffersRequestBody_ByDefault()
{
// Arrange
- var content = "{name: 'Person Name', Age: '30'}";
- var logger = GetLogger();
#pragma warning disable CS0618
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider);
+ var formatter = new JsonInputFormatter(
+ GetLogger(),
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider);
#pragma warning restore CS0618
- var contentBytes = Encoding.UTF8.GetBytes(content);
- var modelState = new ModelStateDictionary();
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
+
var userModel = Assert.IsType(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
@@ -65,48 +61,43 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
- result = await formatter.ReadAsync(context);
+ result = await formatter.ReadAsync(formatterContext);
// 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()
+ public async Task Version_2_1_Constructor_BuffersRequestBody_UsingDefaultOptions()
{
// Arrange
- var content = "{name: 'Person Name', Age: '30'}";
- var logger = GetLogger();
var formatter = new JsonInputFormatter(
- logger,
+ GetLogger(),
_serializerSettings,
ArrayPool.Shared,
_objectPoolProvider,
- new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ new MvcOptions(),
+ new MvcJsonOptions());
- var modelState = new ModelStateDictionary();
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
+
var userModel = Assert.IsType(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
@@ -114,99 +105,50 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
- result = await formatter.ReadAsync(context);
+ result = await formatter.ReadAsync(formatterContext);
// 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 SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody()
+ public async Task Version_2_0_Constructor_SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody()
{
// 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();
- 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_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,
+ GetLogger(),
_serializerSettings,
ArrayPool.Shared,
_objectPoolProvider,
- mvcOptions);
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ suppressInputFormatterBuffering: true);
+#pragma warning restore CS0618
- var modelState = new ModelStateDictionary();
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+ result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -214,44 +156,87 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
[Fact]
- public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions()
+ public async Task Version_2_1_Constructor_SuppressInputFormatterBuffering_UsingMvcOptions_DoesNotBufferRequestBody()
{
// 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 mvcOptions = new MvcOptions()
+ {
+ SuppressInputFormatterBuffering = true,
+ };
+ var formatter = new JsonInputFormatter(
+ GetLogger(),
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ mvcOptions,
+ new MvcJsonOptions());
- var modelState = new ModelStateDictionary();
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
+
+ // Act
+ var result = await formatter.ReadAsync(formatterContext);
+
+ // 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(formatterContext);
+
+ // Assert
+ Assert.False(result.HasError);
+ Assert.Null(result.Model);
+ }
+
+ [Fact]
+ public async Task Version_2_1_Constructor_SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions()
+ {
+ // Arrange
+ var mvcOptions = new MvcOptions()
+ {
+ SuppressInputFormatterBuffering = false,
+ };
+ var formatter = new JsonInputFormatter(
+ GetLogger(),
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ mvcOptions,
+ new MvcJsonOptions());
+
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = new DefaultHttpContext();
+ httpContext.Features.Set(new TestResponseFeature());
+ httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
+ httpContext.Request.ContentType = "application/json";
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// 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);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+ result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -277,21 +262,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead)
{
// Arrange
- var loggerMock = GetLogger();
+ var formatter = CreateFormatter();
- var formatter =
- new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes("content");
-
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(string));
- var formatterContext = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: new ModelStateDictionary(),
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(string), httpContext);
// Act
var result = formatter.CanRead(formatterContext);
@@ -304,9 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void DefaultMediaType_ReturnsApplicationJson()
{
// Arrange
- var loggerMock = GetLogger();
- var formatter =
- new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = CreateFormatter();
// Act
var mediaType = formatter.SupportedMediaTypes[0];
@@ -321,8 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
yield return new object[] { "100", typeof(int), 100 };
yield return new object[] { "'abcd'", typeof(string), "abcd" };
- yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime),
- new DateTime(2012, 02, 01, 00, 45, 00) };
+ yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime), new DateTime(2012, 02, 01, 00, 45, 00) };
}
}
@@ -331,23 +304,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task JsonFormatterReadsSimpleTypes(string content, Type type, object expected)
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter();
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(type);
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: new ModelStateDictionary(),
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(type, httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -358,24 +323,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task JsonFormatterReadsComplexTypes()
{
// 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 formatter = CreateFormatter();
+ var content = "{name: 'Person Name', Age: '30'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(User));
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: new ModelStateDictionary(),
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -388,25 +345,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_ReadsValidArray()
{
// Arrange
- var content = "[0, 23, 300]";
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter();
- var modelState = new ModelStateDictionary();
+ var content = "[0, 23, 300]";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(int[]));
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(int[]), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -422,25 +370,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_ReadsValidArray_AsList(Type requestedType)
{
// Arrange
- var content = "[0, 23, 300]";
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter();
- var modelState = new ModelStateDictionary();
+ var content = "[0, 23, 300]";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(requestedType);
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(requestedType, httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -452,126 +391,90 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_AddsModelValidationErrorsToModelState()
{
// Arrange
- var content = "{name: 'Person Name', Age: 'not-an-age'}";
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
- var modelState = new ModelStateDictionary();
+ var content = "{name: 'Person Name', Age: 'not-an-age'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- 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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.Equal(
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.",
- modelState["Age"].Errors[0].ErrorMessage);
+ formatterContext.ModelState["Age"].Errors[0].ErrorMessage);
}
[Fact]
public async Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
{
// Arrange
- var content = "[0, 23, 300]";
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
- var modelState = new ModelStateDictionary();
+ var content = "[0, 23, 300]";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(byte[]));
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.Equal("The supplied value is invalid.", modelState["[2]"].Errors[0].ErrorMessage);
- Assert.Null(modelState["[2]"].Errors[0].Exception);
+ Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage);
+ Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception);
}
[Fact]
public async Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
{
// Arrange
- 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 MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
- var modelState = new ModelStateDictionary();
+ var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(User[]));
- var context = new InputFormatterContext(
- httpContext,
- modelName: "names",
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User[]), httpContext, modelName: "names");
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
Assert.Equal(
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 59.",
- modelState["names[1].Small"].Errors[0].ErrorMessage);
+ formatterContext.ModelState["names[1].Small"].Errors[0].ErrorMessage);
}
[Fact]
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
{
// Arrange
+ var formatter = CreateFormatter();
+
var content = "{name: 'Person Name', Age: 'not-an-age'}";
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
-
- var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
- 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);
- modelState.MaxAllowedErrors = 3;
- modelState.AddModelError("key1", "error1");
- modelState.AddModelError("key2", "error2");
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
+ formatterContext.ModelState.MaxAllowedErrors = 3;
+ formatterContext.ModelState.AddModelError("key1", "error1");
+ formatterContext.ModelState.AddModelError("key2", "error2");
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.False(modelState.ContainsKey("age"));
- var error = Assert.Single(modelState[""].Errors);
+
+ Assert.False(formatterContext.ModelState.ContainsKey("age"));
+ var error = Assert.Single(formatterContext.ModelState[""].Errors);
Assert.IsType(error.Exception);
}
@@ -580,28 +483,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
[InlineData("null", false, false)]
[InlineData(" ", true, true)]
[InlineData(" ", false, false)]
- public async Task ReadAsync_WithInputThatDeserializesToNull_SetsModelOnlyIfAllowingEmptyInput(string content, bool allowEmptyInput, bool expectedIsModelSet)
+ public async Task ReadAsync_WithInputThatDeserializesToNull_SetsModelOnlyIfAllowingEmptyInput(
+ string content,
+ bool treatEmptyInputAsDefaultValue,
+ bool expectedIsModelSet)
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter();
- var modelState = new ModelStateDictionary();
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(object));
- var context = new InputFormatterContext(
+
+ var formatterContext = CreateInputFormatterContext(
+ typeof(object),
httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader,
- treatEmptyInputAsDefaultValue: allowEmptyInput);
+ treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -616,45 +515,35 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var serializerSettings = new JsonSerializerSettings();
// Act
- var jsonFormatter = new TestableJsonInputFormatter(serializerSettings);
+ var formatter = new TestableJsonInputFormatter(serializerSettings);
// Assert
- Assert.Same(serializerSettings, jsonFormatter.SerializerSettings);
+ Assert.Same(serializerSettings, formatter.SerializerSettings);
}
[Fact]
public async Task CustomSerializerSettingsObject_TakesEffect()
{
// Arrange
- // missing password property here
- var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
- var logger = GetLogger();
-
// 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 MvcOptions());
+ var formatter = CreateFormatter(serializerSettings, allowInputFormatterExceptionMessages: true);
- var modelState = new ModelStateDictionary();
+ // missing password property here
+ var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(UserLogin));
- var inputFormatterContext = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+
+ var formatterContext = CreateInputFormatterContext(typeof(UserLogin), httpContext);
// Act
- var result = await jsonFormatter.ReadAsync(inputFormatterContext);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.False(modelState.IsValid);
+ Assert.False(formatterContext.ModelState.IsValid);
- var modelErrorMessage = modelState.Values.First().Errors[0].ErrorMessage;
- Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage);
+ var message = formatterContext.ModelState.Values.First().Errors[0].ErrorMessage;
+ Assert.Contains("Required property 'Password' not found in JSON", message);
}
[Fact]
@@ -684,86 +573,98 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
[InlineData("{\"age\":\"x\"}", "age", "Could not convert string to decimal: x. Path 'age', line 1, position 10.")]
[InlineData("{\"login\":1}", "login", "Error converting value 1 to type 'Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatterTest+UserLogin'. Path 'login', line 1, position 10.")]
[InlineData("{\"login\":{\"username\":\"somevalue\"}}", "login", "Required property 'Password' not found in JSON. Path 'login', line 1, position 33.")]
- public async Task ReadAsync_RegistersJsonInputExceptionsAsInputFormatterException(
+ public async Task ReadAsync_WithAllowInputFormatterExceptionMessages_RegistersJsonInputExceptionsAsInputFormatterException(
string content,
string modelStateKey,
string expectedMessage)
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
- var contentBytes = Encoding.UTF8.GetBytes(content);
+ var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
- var modelState = new ModelStateDictionary();
+ var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- 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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.True(!modelState.IsValid);
- Assert.True(modelState.ContainsKey(modelStateKey));
+ Assert.True(!formatterContext.ModelState.IsValid);
+ Assert.True(formatterContext.ModelState.ContainsKey(modelStateKey));
- var modelError = modelState[modelStateKey].Errors.Single();
+ var modelError = formatterContext.ModelState[modelStateKey].Errors.Single();
Assert.Equal(expectedMessage, modelError.ErrorMessage);
}
[Fact]
- public async Task ReadAsync_WhenSuppressJsonDeserializationExceptionMessagesIsTrue_DoesNotWrapJsonInputExceptions()
+ public async Task ReadAsync_DefaultOptions_DoesNotWrapJsonInputExceptions()
{
// Arrange
- var logger = GetLogger();
var formatter = new JsonInputFormatter(
- logger,
+ GetLogger(),
_serializerSettings,
ArrayPool.Shared,
_objectPoolProvider,
- new MvcOptions()
- {
- SuppressInputFormatterBuffering = false,
- SuppressJsonDeserializationExceptionMessagesInModelState = true
- });
- var contentBytes = Encoding.UTF8.GetBytes("{");
- var modelStateKey = string.Empty;
+ new MvcOptions(),
+ new MvcJsonOptions());
- var modelState = new ModelStateDictionary();
+ var contentBytes = Encoding.UTF8.GetBytes("{");
var httpContext = GetHttpContext(contentBytes);
- 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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.True(!modelState.IsValid);
- Assert.True(modelState.ContainsKey(modelStateKey));
+ Assert.True(!formatterContext.ModelState.IsValid);
+ Assert.True(formatterContext.ModelState.ContainsKey(string.Empty));
- var modelError = modelState[modelStateKey].Errors.Single();
+ var modelError = formatterContext.ModelState[string.Empty].Errors.Single();
Assert.IsNotType(modelError.Exception);
Assert.Empty(modelError.ErrorMessage);
}
+ [Fact]
+ public async Task ReadAsync_AllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
+ {
+ // Arrange
+ var formatter = new JsonInputFormatter(
+ GetLogger(),
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ new MvcOptions(),
+ new MvcJsonOptions()
+ {
+ AllowInputFormatterExceptionMessages = true,
+ });
+
+ var contentBytes = Encoding.UTF8.GetBytes("{");
+ var httpContext = GetHttpContext(contentBytes);
+
+ var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
+
+ // Act
+ var result = await formatter.ReadAsync(formatterContext);
+
+ // Assert
+ Assert.True(result.HasError);
+ Assert.True(!formatterContext.ModelState.IsValid);
+ Assert.True(formatterContext.ModelState.ContainsKey(string.Empty));
+
+ var modelError = formatterContext.ModelState[string.Empty].Errors.Single();
+ Assert.Null(modelError.Exception);
+ Assert.NotEmpty(modelError.ErrorMessage);
+ }
+
private class TestableJsonInputFormatter : JsonInputFormatter
{
public TestableJsonInputFormatter(JsonSerializerSettings settings)
- : base(GetLogger(), settings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions())
+ : base(GetLogger(), settings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions(), new MvcJsonOptions())
{
}
@@ -777,6 +678,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return NullLogger.Instance;
}
+ private JsonInputFormatter CreateFormatter(JsonSerializerSettings serializerSettings = null, bool allowInputFormatterExceptionMessages = false)
+ {
+ return new JsonInputFormatter(
+ GetLogger(),
+ serializerSettings ?? _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ new MvcOptions(),
+ new MvcJsonOptions()
+ {
+ AllowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages,
+ });
+ }
+
private static HttpContext GetHttpContext(
byte[] contentBytes,
string contentType = "application/json")
@@ -800,6 +715,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return httpContext.Object;
}
+ private InputFormatterContext CreateInputFormatterContext(
+ Type modelType,
+ HttpContext httpContext,
+ string modelName = null,
+ bool treatEmptyInputAsDefaultValue = false)
+ {
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = provider.GetMetadataForType(modelType);
+
+ return new InputFormatterContext(
+ httpContext,
+ modelName: modelName ?? string.Empty,
+ modelState: new ModelStateDictionary(),
+ metadata: metadata,
+ readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader,
+ treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
+ }
+
private IEnumerable GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary)
{
var allErrorMessages = new List();
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
index a998486402..dc097cd5c4 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
@@ -26,136 +26,128 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings();
[Fact]
- public async Task BuffersRequestBody_ByDefault()
+ public async Task Version_2_0_Constructor_BuffersRequestBody_ByDefault()
{
// Arrange
- var logger = GetLogger();
#pragma warning disable CS0618
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider);
+ var formatter = new JsonPatchInputFormatter(
+ GetLogger(),
+ _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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
- result = await formatter.ReadAsync(context);
+ result = await formatter.ReadAsync(formatterContext);
// 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);
+ patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
}
[Fact]
- public async Task BuffersRequestBody_UsingDefaultOptions()
+ public async Task Version_2_1_Constructor_BuffersRequestBody_ByDefault()
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = new JsonPatchInputFormatter(
+ GetLogger(),
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ new MvcOptions(),
+ new MvcJsonOptions());
+
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
- result = await formatter.ReadAsync(context);
+ result = await formatter.ReadAsync(formatterContext);
// 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);
+ patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
}
[Fact]
- public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody()
+ public async Task Version_2_0_Constructor_SuppressInputFormatterBuffering_DoesNotBufferRequestBody()
{
// Arrange
- var logger = GetLogger();
#pragma warning disable CS0618
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
+ var formatter = new JsonPatchInputFormatter(
+ GetLogger(),
+ _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);
- 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);
+
+ var context = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// 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);
+
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context);
@@ -166,49 +158,46 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
[Fact]
- public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions_DoesNotBufferRequestBody()
+ public async Task Version_2_1_Constructor_SuppressInputFormatterBuffering_DoesNotBufferRequestBody()
{
// Arrange
- var logger = GetLogger();
- var mvcOptions = new MvcOptions();
- mvcOptions.SuppressInputFormatterBuffering = false;
+ var mvcOptions = new MvcOptions()
+ {
+ SuppressInputFormatterBuffering = false,
+ };
var formatter = new JsonPatchInputFormatter(
- logger,
+ GetLogger(),
_serializerSettings,
ArrayPool.Shared,
_objectPoolProvider,
- mvcOptions);
+ mvcOptions,
+ new MvcJsonOptions());
+
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);
+
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// 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);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek);
- result = await formatter.ReadAsync(context);
+ result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.False(result.HasError);
@@ -219,67 +208,49 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task JsonPatchInputFormatter_ReadsOneOperation_Successfully()
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = CreateFormatter();
+
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = CreateHttpContext(contentBytes);
- var modelState = new ModelStateDictionary();
- var httpContext = GetHttpContext(contentBytes);
- 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);
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// 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);
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
}
[Fact]
public async Task JsonPatchInputFormatter_ReadsMultipleOperations_Successfully()
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = CreateFormatter();
+
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," +
"{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = CreateHttpContext(contentBytes);
- var modelState = new ModelStateDictionary();
- var httpContext = GetHttpContext(contentBytes);
- 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);
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// 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.Equal("remove", patchDoc.Operations[1].op);
- Assert.Equal("Customer/Name", patchDoc.Operations[1].path);
+ var patchDocument = Assert.IsType>(result.Model);
+ Assert.Equal("add", patchDocument.Operations[0].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
+ Assert.Equal("John", patchDocument.Operations[0].value);
+ Assert.Equal("remove", patchDocument.Operations[1].op);
+ Assert.Equal("Customer/Name", patchDocument.Operations[1].path);
}
[Theory]
@@ -290,22 +261,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead)
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = CreateFormatter();
+
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = CreateHttpContext(contentBytes, contentType: requestContentType);
- var modelState = new ModelStateDictionary();
- var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument));
- var formatterContext = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+ var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument), httpContext);
// Act
var result = formatter.CanRead(formatterContext);
@@ -320,22 +282,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsFalse_NonJsonPatchContentType(Type modelType)
{
// Arrange
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ var formatter = CreateFormatter();
+
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = CreateHttpContext(contentBytes, contentType: "application/json-patch+json");
- var modelState = new ModelStateDictionary();
- var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType);
- var formatterContext = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+ var formatterContext = CreateInputFormatterContext(modelType, httpContext);
// Act
var result = formatter.CanRead(formatterContext);
@@ -351,29 +306,21 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var exceptionMessage = "Cannot deserialize the current JSON array (e.g. [1,2,3]) into type " +
$"'{typeof(Customer).FullName}' because the type requires a JSON object ";
- var logger = GetLogger();
- var formatter =
- new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool.Shared, _objectPoolProvider, new MvcOptions());
+ // This test relies on 2.1 error message behavior
+ var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
+
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
+ var httpContext = CreateHttpContext(contentBytes, contentType: "application/json-patch+json");
- var modelState = new ModelStateDictionary();
- var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
- var provider = new EmptyModelMetadataProvider();
- var metadata = provider.GetMetadataForType(typeof(Customer));
- var context = new InputFormatterContext(
- httpContext,
- modelName: string.Empty,
- modelState: modelState,
- metadata: metadata,
- readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+ var formatterContext = CreateInputFormatterContext(typeof(Customer), httpContext);
// Act
- var result = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(formatterContext);
// Assert
Assert.True(result.HasError);
- Assert.Contains(exceptionMessage, modelState[""].Errors[0].ErrorMessage);
+ Assert.Contains(exceptionMessage, formatterContext.ModelState[""].Errors[0].ErrorMessage);
}
private static ILogger GetLogger()
@@ -381,7 +328,34 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return NullLogger.Instance;
}
- private static HttpContext GetHttpContext(
+ private JsonPatchInputFormatter CreateFormatter(bool allowInputFormatterExceptionMessages = false)
+ {
+ return new JsonPatchInputFormatter(
+ NullLogger.Instance,
+ _serializerSettings,
+ ArrayPool.Shared,
+ _objectPoolProvider,
+ new MvcOptions(),
+ new MvcJsonOptions()
+ {
+ AllowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages,
+ });
+ }
+
+ private InputFormatterContext CreateInputFormatterContext(Type modelType, HttpContext httpContext)
+ {
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = provider.GetMetadataForType(modelType);
+
+ return new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ metadata: metadata,
+ readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
+ }
+
+ private static HttpContext CreateHttpContext(
byte[] contentBytes,
string contentType = "application/json-patch+json")
{
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
index 0b6c52aab2..21a4b615d7 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
@@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
- [Fact]
+ [Fact] // This test covers the 2.0 behavior. JSON.Net error messages are not preserved.
public async Task JsonInputFormatter_SuppliedJsonDeserializationErrorMessage()
{
// Arrange
@@ -109,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
- Assert.Equal("{\"\":[\"Unexpected end when reading JSON. Path '', line 1, position 1.\"]}", responseBody);
+
+ // Update me in 3.0 xD
+ Assert.Equal("{\"\":[\"The input was not valid.\"]}", responseBody);
}
[Theory]
diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
index 788b0d8da8..82970be594 100644
--- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
@@ -448,7 +449,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public int Address { get; set; }
}
- [Fact]
+ [Fact] // This tests the 2.0 behavior. Error messages from JSON.NET are not preserved.
public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_JsonFormatterAddsModelStateError()
{
// Arrange
@@ -485,11 +486,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.Value.AttemptedValue);
Assert.Null(entry.Value.RawValue);
var error = Assert.Single(entry.Value.Errors);
- Assert.Null(error.Exception);
-
- // Json.NET currently throws an exception starting with "No JSON content found and type 'System.Int32' is
- // not nullable." but do not tie test to a particular Json.NET build.
- Assert.NotEmpty(error.ErrorMessage);
+
+ // Update me in 3.0 when MvcJsonOptions.AllowInputFormatterExceptionMessages is removed
+ Assert.IsType(error.Exception);
+ Assert.Empty(error.ErrorMessage);
}
private class Person5
@@ -545,7 +545,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Empty(modelState);
}
- [Fact]
+ [Fact] // This test covers the 2.0 behavior. Error messages from JSON.Net are preserved.
public async Task FromBodyWithInvalidPropertyData_JsonFormatterAddsModelError()
{
// Arrange
@@ -586,18 +586,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(state.AttemptedValue);
Assert.Null(state.RawValue);
var error = Assert.Single(state.Errors);
- Assert.Null(error.Exception);
- // Json.NET currently throws an Exception with a Message starting with "Could not convert string to
- // integer: not a number." but do not tie test to a particular Json.NET build.
- Assert.NotEmpty(error.ErrorMessage);
+ // Update me in 3.0 when MvcJsonOptions.AllowInputFormatterExceptionMessages is removed
+ Assert.IsType(error.Exception);
+ Assert.Empty(error.ErrorMessage);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, true)]
public async Task FromBodyWithEmptyBody_JsonFormatterAddsModelErrorWhenExpected(
- bool allowEmptyInputInBodyModelBindingSetting, bool expectedModelStateIsValid)
+ bool allowEmptyInputInBodyModelBindingSetting,
+ bool expectedModelStateIsValid)
{
// Arrange
var parameter = new ParameterDescriptor
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs
index c7ca86a35f..3c73fb507f 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs
@@ -28,11 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Act
var mvcOptions = services.GetRequiredService>().Value;
+ var jsonOptions = services.GetRequiredService>().Value;
// Assert
Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy);
- Assert.False(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
+ Assert.False(jsonOptions.AllowInputFormatterExceptionMessages);
}
[Fact(Skip = "#7157 - some settings have the wrong values, this test should pass once #7157 is fixed")]
@@ -47,11 +48,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Act
var mvcOptions = services.GetRequiredService>().Value;
+ var jsonOptions = services.GetRequiredService>().Value;
// Assert
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
- Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
+ Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
}
[Fact(Skip = "#7157 - some settings have the wrong values, this test should pass once #7157 is fixed")]
@@ -66,11 +68,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Act
var mvcOptions = services.GetRequiredService>().Value;
+ var jsonOptions = services.GetRequiredService>().Value;
// Assert
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
- Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
+ Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
}
// This just does the minimum needed to be able to resolve these options.
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
index b4fdbfc7b7..5fa8242a70 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -386,6 +386,13 @@ namespace Microsoft.AspNetCore.Mvc
typeof(RazorPagesOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptionsConfigureCompatibilityOptions", throwOnError: true),
}
},
+ {
+ typeof(IPostConfigureOptions),
+ new[]
+ {
+ typeof(MvcJsonOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.MvcJsonOptionsConfigureCompatibilityOptions", throwOnError: true),
+ }
+ },
{
typeof(IActionConstraintProvider),
new Type[]