Move option for JSON errors to MvcJsonOptions

This was in the wrong place - JSON formatters have their own options
type already.

Moved the option to MvcJsonOptions and updated the naming + defaults to
reflect our plan.

Also did a bunch of general cleanup on these tests, which were a bit
sloppy.
This commit is contained in:
ASP.NET CI 2018-01-06 15:19:56 -08:00 committed by Ryan Nowak
parent bfb5f23647
commit 2e73bab2a4
15 changed files with 601 additions and 566 deletions

View File

@ -51,7 +51,9 @@ namespace Microsoft.AspNetCore.Mvc
/// <remarks> /// <remarks>
/// ASP.NET Core MVC 2.1 introduces compatibility switches for the following: /// ASP.NET Core MVC 2.1 introduces compatibility switches for the following:
/// <list type="bullet"> /// <list type="bullet">
/// <item><description><see cref="MvcOptions.InputFormatterExceptionPolicy"/></description></item>
/// <item><description><see cref="MvcOptions.SuppressBindingUndefinedValueToEnumType"/></description></item> /// <item><description><see cref="MvcOptions.SuppressBindingUndefinedValueToEnumType"/></description></item>
/// <item><description><c>MvcJsonOptions.AllowInputFormatterExceptionMessages</c></description></item>
/// <item><description><c>RazorPagesOptions.AllowAreas</c></description></item> /// <item><description><c>RazorPagesOptions.AllowAreas</c></description></item>
/// </list> /// </list>
/// </remarks> /// </remarks>

View File

@ -24,9 +24,11 @@ namespace Microsoft.AspNetCore.Mvc
// See CompatibilitySwitch.cs for guide on how to implement these. // See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly CompatibilitySwitch<bool> _suppressJsonDeserializationExceptionMessagesInModelState;
private readonly ICompatibilitySwitch[] _switches; private readonly ICompatibilitySwitch[] _switches;
/// <summary>
/// Creates a new instance of <see cref="MvcOptions"/>.
/// </summary>
public MvcOptions() public MvcOptions()
{ {
CacheProfiles = new Dictionary<string, CacheProfile>(StringComparer.OrdinalIgnoreCase); CacheProfiles = new Dictionary<string, CacheProfile>(StringComparer.OrdinalIgnoreCase);
@ -43,12 +45,11 @@ namespace Microsoft.AspNetCore.Mvc
_inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType)); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_suppressJsonDeserializationExceptionMessagesInModelState = new CompatibilitySwitch<bool>(nameof(SuppressJsonDeserializationExceptionMessagesInModelState));
_switches = new ICompatibilitySwitch[] _switches = new ICompatibilitySwitch[]
{ {
_inputFormatterExceptionPolicy, _inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType, _suppressBindingUndefinedValueToEnumType,
_suppressJsonDeserializationExceptionMessagesInModelState,
}; };
} }
@ -241,20 +242,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public bool RequireHttpsPermanent { get; set; } public bool RequireHttpsPermanent { get; set; }
/// <summary>
/// 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.
/// <see langword="false"/> by default, meaning that clients may receive details about
/// why the JSON they posted is considered invalid.
/// </summary>
public bool SuppressJsonDeserializationExceptionMessagesInModelState
{
get => _suppressJsonDeserializationExceptionMessagesInModelState.Value;
set => _suppressJsonDeserializationExceptionMessagesInModelState.Value = value;
}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator() IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{ {
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator(); return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -75,6 +75,8 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcJsonMvcOptionsSetup>()); ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcJsonMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcJsonOptions>, MvcJsonOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, JsonPatchOperationsArrayProvider>()); ServiceDescriptor.Transient<IApiDescriptionProvider, JsonPatchOperationsArrayProvider>());
services.TryAddSingleton<JsonResultExecutor>(); services.TryAddSingleton<JsonResultExecutor>();

View File

@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
@ -20,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
public class MvcJsonMvcOptionsSetup : IConfigureOptions<MvcOptions> public class MvcJsonMvcOptionsSetup : IConfigureOptions<MvcOptions>
{ {
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly JsonSerializerSettings _jsonSerializerSettings; private readonly MvcJsonOptions _jsonOptions;
private readonly ArrayPool<char> _charPool; private readonly ArrayPool<char> _charPool;
private readonly ObjectPoolProvider _objectPoolProvider; private readonly ObjectPoolProvider _objectPoolProvider;
@ -51,14 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
} }
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_jsonSerializerSettings = jsonOptions.Value.SerializerSettings; _jsonOptions = jsonOptions.Value;
_charPool = charPool; _charPool = charPool;
_objectPoolProvider = objectPoolProvider; _objectPoolProvider = objectPoolProvider;
} }
public void Configure(MvcOptions options) 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 // Register JsonPatchInputFormatter before JsonInputFormatter, otherwise
// JsonInputFormatter would consume "application/json-patch+json" requests // JsonInputFormatter would consume "application/json-patch+json" requests
@ -66,18 +65,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
var jsonInputPatchLogger = _loggerFactory.CreateLogger<JsonPatchInputFormatter>(); var jsonInputPatchLogger = _loggerFactory.CreateLogger<JsonPatchInputFormatter>();
options.InputFormatters.Add(new JsonPatchInputFormatter( options.InputFormatters.Add(new JsonPatchInputFormatter(
jsonInputPatchLogger, jsonInputPatchLogger,
_jsonSerializerSettings, _jsonOptions.SerializerSettings,
_charPool, _charPool,
_objectPoolProvider, _objectPoolProvider,
options)); options,
_jsonOptions));
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>(); var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
options.InputFormatters.Add(new JsonInputFormatter( options.InputFormatters.Add(new JsonInputFormatter(
jsonInputLogger, jsonInputLogger,
_jsonSerializerSettings, _jsonOptions.SerializerSettings,
_charPool, _charPool,
_objectPoolProvider, _objectPoolProvider,
options)); options,
_jsonOptions));
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json")); options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));

View File

@ -28,8 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ObjectPoolProvider _objectPoolProvider; private readonly ObjectPoolProvider _objectPoolProvider;
private readonly MvcOptions _options; 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 _suppressInputFormatterBuffering;
private readonly bool _suppressJsonDeserializationExceptionMessages; private readonly bool _allowInputFormatterExceptionMessages;
private ObjectPool<JsonSerializer> _jsonSerializerPool; private ObjectPool<JsonSerializer> _jsonSerializerPool;
@ -74,10 +78,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering) 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 // This constructor by default treats JSON deserialization exceptions as unsafe
// because this is the default for applications generally // because this is the default in 2.0
} }
/// <summary> /// <summary>
@ -92,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
/// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param> /// <param name="allowInputFormatterExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")] [Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonInputFormatter( public JsonInputFormatter(
ILogger logger, ILogger logger,
@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering, bool suppressInputFormatterBuffering,
bool suppressJsonDeserializationExceptionMessages) bool allowInputFormatterExceptionMessages)
{ {
if (logger == null) if (logger == null)
{ {
@ -127,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_charPool = new JsonArrayPool<char>(charPool); _charPool = new JsonArrayPool<char>(charPool);
_objectPoolProvider = objectPoolProvider; _objectPoolProvider = objectPoolProvider;
_suppressInputFormatterBuffering = suppressInputFormatterBuffering; _suppressInputFormatterBuffering = suppressInputFormatterBuffering;
_suppressJsonDeserializationExceptionMessages = suppressJsonDeserializationExceptionMessages; _allowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages;
SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian); SupportedEncodings.Add(UTF16EncodingLittleEndian);
@ -149,12 +153,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="options">The <see cref="MvcOptions"/>.</param> /// <param name="options">The <see cref="MvcOptions"/>.</param>
/// <param name="jsonOptions">The <see cref="MvcJsonOptions"/>.</param>
public JsonInputFormatter( public JsonInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
MvcOptions options) MvcOptions options,
MvcJsonOptions jsonOptions)
{ {
if (logger == null) if (logger == null)
{ {
@ -181,6 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_charPool = new JsonArrayPool<char>(charPool); _charPool = new JsonArrayPool<char>(charPool);
_objectPoolProvider = objectPoolProvider; _objectPoolProvider = objectPoolProvider;
_options = options; _options = options;
_jsonOptions = jsonOptions;
SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian); SupportedEncodings.Add(UTF16EncodingLittleEndian);
@ -406,17 +413,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private Exception WrapExceptionForModelState(Exception exception) 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 // 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 // other than these two types, but we're being conservative and limiting which ones
// we regard as having safe messages to expose to clients // we regard as having safe messages to expose to clients
var isJsonExceptionType = if (exception is JsonReaderException || exception is JsonSerializationException)
exception is JsonReaderException || exception is JsonSerializationException; {
var suppressJsonDeserializationExceptionMessages = _options?.SuppressJsonDeserializationExceptionMessagesInModelState ?? _suppressJsonDeserializationExceptionMessages; // InputFormatterException specifies that the message is safe to return to a client, it will
var suppressOriginalMessage = // be added to model state.
suppressJsonDeserializationExceptionMessages || !isJsonExceptionType; return new InputFormatterException(exception.Message, exception);
return suppressOriginalMessage }
? exception
: new InputFormatterException(exception.Message, exception); // Not a known exception type, so we're not going to assume that it's safe.
return exception;
} }
} }
} }

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering) 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
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param> /// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
/// <param name="suppressJsonDeserializationExceptionMessages">If <see langword="true"/>, JSON deserialization exception messages will replaced by a generic message in model state.</param> /// <param name="allowInputFormatterExceptionMessages">
/// If <see langword="false"/>, JSON deserialization exception messages will replaced by a generic message in model state.
/// </param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")] [Obsolete("This constructor is obsolete and will be removed in a future version.")]
public JsonPatchInputFormatter( public JsonPatchInputFormatter(
ILogger logger, ILogger logger,
@ -83,8 +85,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
bool suppressInputFormatterBuffering, bool suppressInputFormatterBuffering,
bool suppressJsonDeserializationExceptionMessages) bool allowInputFormatterExceptionMessages)
: base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages) : base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages)
{ {
// Clear all values and only include json-patch+json value. // Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
@ -104,13 +106,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param> /// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param> /// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
/// <param name="options">The <see cref="MvcOptions"/>.</param> /// <param name="options">The <see cref="MvcOptions"/>.</param>
/// <param name="jsonOptions">The <see cref="MvcJsonOptions"/>.</param>
public JsonPatchInputFormatter( public JsonPatchInputFormatter(
ILogger logger, ILogger logger,
JsonSerializerSettings serializerSettings, JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool, ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider, ObjectPoolProvider objectPoolProvider,
MvcOptions options) MvcOptions options,
: base(logger, serializerSettings, charPool, objectPoolProvider, options) MvcJsonOptions jsonOptions)
: base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{ {
// Clear all values and only include json-patch+json value. // Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();

View File

@ -1,7 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
@ -9,12 +13,69 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Provides programmatic configuration for JSON in the MVC framework. /// Provides programmatic configuration for JSON in the MVC framework.
/// </summary> /// </summary>
public class MvcJsonOptions public class MvcJsonOptions : IEnumerable<ICompatibilitySwitch>
{ {
private readonly CompatibilitySwitch<bool> _allowInputFormatterExceptionMessages;
private readonly ICompatibilitySwitch[] _switches;
/// <summary>
/// Creates a new instance of <see cref="MvcJsonOptions"/>.
/// </summary>
public MvcJsonOptions()
{
_allowInputFormatterExceptionMessages = new CompatibilitySwitch<bool>(nameof(AllowInputFormatterExceptionMessages));
_switches = new ICompatibilitySwitch[]
{
_allowInputFormatterExceptionMessages,
};
}
/// <summary>
/// Gets or sets a flag to determine whether error messsages from JSON deserialization by the
/// <see cref="JsonInputFormatter"/> will be added to the <see cref="ModelStateDictionary"/>. The default
/// value is <c>false</c>, meaning that a generic error message will be used instead.
/// </summary>
/// <remarks>
/// <para>
/// Error messages in the <see cref="ModelStateDictionary"/> are often communicated to clients, either in HTML
/// or using <see cref="BadRequestObjectResult"/>. In effect, this setting controls whether clients can recieve
/// detailed error messages about submitted JSON data.
/// </para>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// 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 <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have value <c>false</c> if not explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have value <c>true</c> if not explicitly configured.
/// </para>
/// </remarks>
public bool AllowInputFormatterExceptionMessages
{
get => _allowInputFormatterExceptionMessages.Value;
set => _allowInputFormatterExceptionMessages.Value = value;
}
/// <summary> /// <summary>
/// Gets the <see cref="JsonSerializerSettings"/> that are used by this application. /// Gets the <see cref="JsonSerializerSettings"/> that are used by this application.
/// </summary> /// </summary>
public JsonSerializerSettings SerializerSettings { get; } = public JsonSerializerSettings SerializerSettings { get; } = JsonSerializerSettingsProvider.CreateSerializerSettings();
JsonSerializerSettingsProvider.CreateSerializerSettings();
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
} }
} }

View File

@ -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<MvcJsonOptions>
{
public MvcJsonOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
var values = new Dictionary<string, object>();
if (Version >= CompatibilityVersion.Version_2_1)
{
values[nameof(MvcJsonOptions.AllowInputFormatterExceptionMessages)] = true;
}
return values;
}
}
}
}

View File

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

View File

@ -28,36 +28,32 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings(); private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings();
[Fact] [Fact]
public async Task BuffersRequestBody_ByDefault() public async Task Version_2_0_Constructor_BuffersRequestBody_ByDefault()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
#pragma warning disable CS0618 #pragma warning disable CS0618
var formatter = var formatter = new JsonInputFormatter(
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider);
#pragma warning restore CS0618 #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(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model); var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
@ -65,48 +61,43 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.True(httpContext.Request.Body.CanSeek); Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
userModel = Assert.IsType<User>(result.Model); userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
} }
[Fact] [Fact]
public async Task BuffersRequestBody_UsingDefaultOptions() public async Task Version_2_1_Constructor_BuffersRequestBody_UsingDefaultOptions()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
var formatter = new JsonInputFormatter( var formatter = new JsonInputFormatter(
logger, GetLogger(),
_serializerSettings, _serializerSettings,
ArrayPool<char>.Shared, ArrayPool<char>.Shared,
_objectPoolProvider, _objectPoolProvider,
new MvcOptions()); new MvcOptions(),
var contentBytes = Encoding.UTF8.GetBytes(content); new MvcJsonOptions());
var modelState = new ModelStateDictionary(); var content = "{name: 'Person Name', Age: '30'}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model); var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
@ -114,99 +105,50 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.True(httpContext.Request.Body.CanSeek); Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
userModel = Assert.IsType<User>(result.Model); userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
} }
[Fact] [Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody() public async Task Version_2_0_Constructor_SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
#pragma warning disable CS0618 #pragma warning disable CS0618
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true);
#pragma warning restore CS0618
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context);
// Assert
Assert.False(result.HasError);
Assert.Null(result.Model);
}
[Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMvcOptions_DoesNotBufferRequestBody()
{
// Arrange
var content = "{name: 'Person Name', Age: '30'}";
var logger = GetLogger();
var mvcOptions = new MvcOptions();
mvcOptions.SuppressInputFormatterBuffering = true;
var formatter = new JsonInputFormatter( var formatter = new JsonInputFormatter(
logger, GetLogger(),
_serializerSettings, _serializerSettings,
ArrayPool<char>.Shared, ArrayPool<char>.Shared,
_objectPoolProvider, _objectPoolProvider,
mvcOptions); suppressInputFormatterBuffering: true);
var contentBytes = Encoding.UTF8.GetBytes(content); #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(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model); var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek); Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -214,44 +156,87 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
} }
[Fact] [Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions() public async Task Version_2_1_Constructor_SuppressInputFormatterBuffering_UsingMvcOptions_DoesNotBufferRequestBody()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}"; var mvcOptions = new MvcOptions()
var logger = GetLogger(); {
var mvcOptions = new MvcOptions(); SuppressInputFormatterBuffering = true,
mvcOptions.SuppressInputFormatterBuffering = false; };
var formatter = var formatter = new JsonInputFormatter(
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, mvcOptions); GetLogger(),
var contentBytes = Encoding.UTF8.GetBytes(content); _serializerSettings,
ArrayPool<char>.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(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext, // Act
modelName: string.Empty, var result = await formatter.ReadAsync(formatterContext);
modelState: modelState,
metadata: metadata, // Assert
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader); Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(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<char>.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<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json";
var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act // Act
// Mutate options after passing into the constructor to make sure that the value type is not store in the constructor // Mutate options after passing into the constructor to make sure that the value type is not store in the constructor
mvcOptions.SuppressInputFormatterBuffering = true; mvcOptions.SuppressInputFormatterBuffering = true;
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var userModel = Assert.IsType<User>(result.Model); var userModel = Assert.IsType<User>(result.Model);
Assert.Equal("Person Name", userModel.Name); Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age); Assert.Equal(30, userModel.Age);
Assert.False(httpContext.Request.Body.CanSeek); Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -277,21 +262,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead)
{ {
// Arrange // Arrange
var loggerMock = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes("content"); var contentBytes = Encoding.UTF8.GetBytes("content");
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType); var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(string)); var formatterContext = CreateInputFormatterContext(typeof(string), httpContext);
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = formatter.CanRead(formatterContext); var result = formatter.CanRead(formatterContext);
@ -304,9 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void DefaultMediaType_ReturnsApplicationJson() public void DefaultMediaType_ReturnsApplicationJson()
{ {
// Arrange // Arrange
var loggerMock = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonInputFormatter(loggerMock, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
// Act // Act
var mediaType = formatter.SupportedMediaTypes[0]; 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[] { "100", typeof(int), 100 };
yield return new object[] { "'abcd'", typeof(string), "abcd" }; yield return new object[] { "'abcd'", typeof(string), "abcd" };
yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime), yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime), new DateTime(2012, 02, 01, 00, 45, 00) };
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) public async Task JsonFormatterReadsSimpleTypes(string content, Type type, object expected)
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(type); var formatterContext = CreateInputFormatterContext(type, httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -358,24 +323,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task JsonFormatterReadsComplexTypes() public async Task JsonFormatterReadsComplexTypes()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: '30'}"; var formatter = CreateFormatter();
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var content = "{name: 'Person Name', Age: '30'}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -388,25 +345,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_ReadsValidArray() public async Task ReadAsync_ReadsValidArray()
{ {
// Arrange // Arrange
var content = "[0, 23, 300]"; var formatter = CreateFormatter();
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var content = "[0, 23, 300]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(int[])); var formatterContext = CreateInputFormatterContext(typeof(int[]), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -422,25 +370,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_ReadsValidArray_AsList(Type requestedType) public async Task ReadAsync_ReadsValidArray_AsList(Type requestedType)
{ {
// Arrange // Arrange
var content = "[0, 23, 300]"; var formatter = CreateFormatter();
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var content = "[0, 23, 300]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(requestedType); var formatterContext = CreateInputFormatterContext(requestedType, httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -452,126 +391,90 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task ReadAsync_AddsModelValidationErrorsToModelState() public async Task ReadAsync_AddsModelValidationErrorsToModelState()
{ {
// Arrange // Arrange
var content = "{name: 'Person Name', Age: 'not-an-age'}"; var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var content = "{name: 'Person Name', Age: 'not-an-age'}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.Equal( Assert.Equal(
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.", "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] [Fact]
public async Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState() public async Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
{ {
// Arrange // Arrange
var content = "[0, 23, 300]"; var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var content = "[0, 23, 300]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(byte[])); var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.Equal("The supplied value is invalid.", modelState["[2]"].Errors[0].ErrorMessage); Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage);
Assert.Null(modelState["[2]"].Errors[0].Exception); Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception);
} }
[Fact] [Fact]
public async Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState() public async Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
{ {
// Arrange // Arrange
var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]"; var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User[])); var formatterContext = CreateInputFormatterContext(typeof(User[]), httpContext, modelName: "names");
var context = new InputFormatterContext(
httpContext,
modelName: "names",
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.Equal( Assert.Equal(
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 59.", "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] [Fact]
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState() public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
{ {
// Arrange // Arrange
var formatter = CreateFormatter();
var content = "{name: 'Person Name', Age: 'not-an-age'}"; var content = "{name: 'Person Name', Age: 'not-an-age'}";
var logger = GetLogger();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes); 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; var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
modelState.AddModelError("key1", "error1"); formatterContext.ModelState.MaxAllowedErrors = 3;
modelState.AddModelError("key2", "error2"); formatterContext.ModelState.AddModelError("key1", "error1");
formatterContext.ModelState.AddModelError("key2", "error2");
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); 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<TooManyModelErrorsException>(error.Exception); Assert.IsType<TooManyModelErrorsException>(error.Exception);
} }
@ -580,28 +483,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
[InlineData("null", false, false)] [InlineData("null", false, false)]
[InlineData(" ", true, true)] [InlineData(" ", true, true)]
[InlineData(" ", false, false)] [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 // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(object)); var formatterContext = CreateInputFormatterContext(
var context = new InputFormatterContext( typeof(object),
httpContext, httpContext,
modelName: string.Empty, treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader,
treatEmptyInputAsDefaultValue: allowEmptyInput);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -616,45 +515,35 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var serializerSettings = new JsonSerializerSettings(); var serializerSettings = new JsonSerializerSettings();
// Act // Act
var jsonFormatter = new TestableJsonInputFormatter(serializerSettings); var formatter = new TestableJsonInputFormatter(serializerSettings);
// Assert // Assert
Assert.Same(serializerSettings, jsonFormatter.SerializerSettings); Assert.Same(serializerSettings, formatter.SerializerSettings);
} }
[Fact] [Fact]
public async Task CustomSerializerSettingsObject_TakesEffect() public async Task CustomSerializerSettingsObject_TakesEffect()
{ {
// Arrange // 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 // by default we ignore missing members, so here explicitly changing it
var serializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error }; var serializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error };
var jsonFormatter = var formatter = CreateFormatter(serializerSettings, allowInputFormatterExceptionMessages: true);
new JsonInputFormatter(logger, serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
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 httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(UserLogin)); var formatterContext = CreateInputFormatterContext(typeof(UserLogin), httpContext);
var inputFormatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await jsonFormatter.ReadAsync(inputFormatterContext); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.False(modelState.IsValid); Assert.False(formatterContext.ModelState.IsValid);
var modelErrorMessage = modelState.Values.First().Errors[0].ErrorMessage; var message = formatterContext.ModelState.Values.First().Errors[0].ErrorMessage;
Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage); Assert.Contains("Required property 'Password' not found in JSON", message);
} }
[Fact] [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("{\"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\":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.")] [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 content,
string modelStateKey, string modelStateKey,
string expectedMessage) string expectedMessage)
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
var formatter =
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary(); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.True(!modelState.IsValid); Assert.True(!formatterContext.ModelState.IsValid);
Assert.True(modelState.ContainsKey(modelStateKey)); Assert.True(formatterContext.ModelState.ContainsKey(modelStateKey));
var modelError = modelState[modelStateKey].Errors.Single(); var modelError = formatterContext.ModelState[modelStateKey].Errors.Single();
Assert.Equal(expectedMessage, modelError.ErrorMessage); Assert.Equal(expectedMessage, modelError.ErrorMessage);
} }
[Fact] [Fact]
public async Task ReadAsync_WhenSuppressJsonDeserializationExceptionMessagesIsTrue_DoesNotWrapJsonInputExceptions() public async Task ReadAsync_DefaultOptions_DoesNotWrapJsonInputExceptions()
{ {
// Arrange // Arrange
var logger = GetLogger();
var formatter = new JsonInputFormatter( var formatter = new JsonInputFormatter(
logger, GetLogger(),
_serializerSettings, _serializerSettings,
ArrayPool<char>.Shared, ArrayPool<char>.Shared,
_objectPoolProvider, _objectPoolProvider,
new MvcOptions() new MvcOptions(),
{ new MvcJsonOptions());
SuppressInputFormatterBuffering = false,
SuppressJsonDeserializationExceptionMessagesInModelState = true
});
var contentBytes = Encoding.UTF8.GetBytes("{");
var modelStateKey = string.Empty;
var modelState = new ModelStateDictionary(); var contentBytes = Encoding.UTF8.GetBytes("{");
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User)); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.True(!modelState.IsValid); Assert.True(!formatterContext.ModelState.IsValid);
Assert.True(modelState.ContainsKey(modelStateKey)); Assert.True(formatterContext.ModelState.ContainsKey(string.Empty));
var modelError = modelState[modelStateKey].Errors.Single(); var modelError = formatterContext.ModelState[string.Empty].Errors.Single();
Assert.IsNotType<InputFormatterException>(modelError.Exception); Assert.IsNotType<InputFormatterException>(modelError.Exception);
Assert.Empty(modelError.ErrorMessage); Assert.Empty(modelError.ErrorMessage);
} }
[Fact]
public async Task ReadAsync_AllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
{
// Arrange
var formatter = new JsonInputFormatter(
GetLogger(),
_serializerSettings,
ArrayPool<char>.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 private class TestableJsonInputFormatter : JsonInputFormatter
{ {
public TestableJsonInputFormatter(JsonSerializerSettings settings) public TestableJsonInputFormatter(JsonSerializerSettings settings)
: base(GetLogger(), settings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions()) : base(GetLogger(), settings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions(), new MvcJsonOptions())
{ {
} }
@ -777,6 +678,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return NullLogger.Instance; return NullLogger.Instance;
} }
private JsonInputFormatter CreateFormatter(JsonSerializerSettings serializerSettings = null, bool allowInputFormatterExceptionMessages = false)
{
return new JsonInputFormatter(
GetLogger(),
serializerSettings ?? _serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions(),
new MvcJsonOptions()
{
AllowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages,
});
}
private static HttpContext GetHttpContext( private static HttpContext GetHttpContext(
byte[] contentBytes, byte[] contentBytes,
string contentType = "application/json") string contentType = "application/json")
@ -800,6 +715,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return httpContext.Object; 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<string> GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary) private IEnumerable<string> GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary)
{ {
var allErrorMessages = new List<string>(); var allErrorMessages = new List<string>();

View File

@ -26,136 +26,128 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings(); private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings();
[Fact] [Fact]
public async Task BuffersRequestBody_ByDefault() public async Task Version_2_0_Constructor_BuffersRequestBody_ByDefault()
{ {
// Arrange // Arrange
var logger = GetLogger();
#pragma warning disable CS0618 #pragma warning disable CS0618
var formatter = var formatter = new JsonPatchInputFormatter(
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider); GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider);
#pragma warning restore CS0618 #pragma warning restore CS0618
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>)); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
Assert.True(httpContext.Request.Body.CanSeek); Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
} }
[Fact] [Fact]
public async Task BuffersRequestBody_UsingDefaultOptions() public async Task Version_2_1_Constructor_BuffersRequestBody_ByDefault()
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = new JsonPatchInputFormatter(
var formatter = GetLogger(),
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions()); _serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions(),
new MvcJsonOptions());
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>)); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
Assert.True(httpContext.Request.Body.CanSeek); Assert.True(httpContext.Request.Body.CanSeek);
httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); httpContext.Request.Body.Seek(0L, SeekOrigin.Begin);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
} }
[Fact] [Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody() public async Task Version_2_0_Constructor_SuppressInputFormatterBuffering_DoesNotBufferRequestBody()
{ {
// Arrange // Arrange
var logger = GetLogger();
#pragma warning disable CS0618 #pragma warning disable CS0618
var formatter = var formatter = new JsonPatchInputFormatter(
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, suppressInputFormatterBuffering: true); GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
suppressInputFormatterBuffering: true);
#pragma warning restore CS0618 #pragma warning restore CS0618
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>)); var context = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(context);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDocument.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek); Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(context);
@ -166,49 +158,46 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
} }
[Fact] [Fact]
public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions_DoesNotBufferRequestBody() public async Task Version_2_1_Constructor_SuppressInputFormatterBuffering_DoesNotBufferRequestBody()
{ {
// Arrange // Arrange
var logger = GetLogger(); var mvcOptions = new MvcOptions()
var mvcOptions = new MvcOptions(); {
mvcOptions.SuppressInputFormatterBuffering = false; SuppressInputFormatterBuffering = false,
};
var formatter = new JsonPatchInputFormatter( var formatter = new JsonPatchInputFormatter(
logger, GetLogger(),
_serializerSettings, _serializerSettings,
ArrayPool<char>.Shared, ArrayPool<char>.Shared,
_objectPoolProvider, _objectPoolProvider,
mvcOptions); mvcOptions,
new MvcJsonOptions());
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>)); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
// Mutate options after passing into the constructor to make sure that the value type is not store in the constructor // Mutate options after passing into the constructor to make sure that the value type is not store in the constructor
mvcOptions.SuppressInputFormatterBuffering = true; mvcOptions.SuppressInputFormatterBuffering = true;
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDocument.Operations[0].value);
Assert.False(httpContext.Request.Body.CanSeek); Assert.False(httpContext.Request.Body.CanSeek);
result = await formatter.ReadAsync(context); result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
@ -219,67 +208,49 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public async Task JsonPatchInputFormatter_ReadsOneOperation_Successfully() public async Task JsonPatchInputFormatter_ReadsOneOperation_Successfully()
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]"; var content = "[{\"op\":\"add\",\"path\":\"Customer/Name\",\"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = CreateHttpContext(contentBytes);
var modelState = new ModelStateDictionary(); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
} }
[Fact] [Fact]
public async Task JsonPatchInputFormatter_ReadsMultipleOperations_Successfully() public async Task JsonPatchInputFormatter_ReadsMultipleOperations_Successfully()
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," + var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}," +
"{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]"; "{\"op\": \"remove\", \"path\" : \"Customer/Name\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = CreateHttpContext(contentBytes);
var modelState = new ModelStateDictionary(); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.False(result.HasError); Assert.False(result.HasError);
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(result.Model); var patchDocument = Assert.IsType<JsonPatchDocument<Customer>>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op); Assert.Equal("add", patchDocument.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path); Assert.Equal("Customer/Name", patchDocument.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value); Assert.Equal("John", patchDocument.Operations[0].value);
Assert.Equal("remove", patchDoc.Operations[1].op); Assert.Equal("remove", patchDocument.Operations[1].op);
Assert.Equal("Customer/Name", patchDoc.Operations[1].path); Assert.Equal("Customer/Name", patchDocument.Operations[1].path);
} }
[Theory] [Theory]
@ -290,22 +261,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead) public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead)
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = CreateHttpContext(contentBytes, contentType: requestContentType);
var modelState = new ModelStateDictionary(); var formatterContext = CreateInputFormatterContext(typeof(JsonPatchDocument<Customer>), httpContext);
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = formatter.CanRead(formatterContext); var result = formatter.CanRead(formatterContext);
@ -320,22 +282,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public void CanRead_ReturnsFalse_NonJsonPatchContentType(Type modelType) public void CanRead_ReturnsFalse_NonJsonPatchContentType(Type modelType)
{ {
// Arrange // Arrange
var logger = GetLogger(); var formatter = CreateFormatter();
var formatter =
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
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 provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType); var metadata = provider.GetMetadataForType(modelType);
var formatterContext = new InputFormatterContext( var formatterContext = CreateInputFormatterContext(modelType, httpContext);
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata,
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
// Act // Act
var result = formatter.CanRead(formatterContext); 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 " + 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 "; $"'{typeof(Customer).FullName}' because the type requires a JSON object ";
var logger = GetLogger(); // This test relies on 2.1 error message behavior
var formatter = var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
new JsonPatchInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider, new MvcOptions());
var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]"; var content = "[{\"op\": \"add\", \"path\" : \"Customer/Name\", \"value\":\"John\"}]";
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = CreateHttpContext(contentBytes, contentType: "application/json-patch+json");
var modelState = new ModelStateDictionary(); var formatterContext = CreateInputFormatterContext(typeof(Customer), httpContext);
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);
// Act // Act
var result = await formatter.ReadAsync(context); var result = await formatter.ReadAsync(formatterContext);
// Assert // Assert
Assert.True(result.HasError); Assert.True(result.HasError);
Assert.Contains(exceptionMessage, modelState[""].Errors[0].ErrorMessage); Assert.Contains(exceptionMessage, formatterContext.ModelState[""].Errors[0].ErrorMessage);
} }
private static ILogger GetLogger() private static ILogger GetLogger()
@ -381,7 +328,34 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
return NullLogger.Instance; return NullLogger.Instance;
} }
private static HttpContext GetHttpContext( private JsonPatchInputFormatter CreateFormatter(bool allowInputFormatterExceptionMessages = false)
{
return new JsonPatchInputFormatter(
NullLogger.Instance,
_serializerSettings,
ArrayPool<char>.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, byte[] contentBytes,
string contentType = "application/json-patch+json") string contentType = "application/json-patch+json")
{ {

View File

@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); 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() public async Task JsonInputFormatter_SuppliedJsonDeserializationErrorMessage()
{ {
// Arrange // Arrange
@ -109,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert // Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); 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] [Theory]

View File

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests namespace Microsoft.AspNetCore.Mvc.IntegrationTests
@ -448,7 +449,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public int Address { get; set; } 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() public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_JsonFormatterAddsModelStateError()
{ {
// Arrange // Arrange
@ -485,11 +486,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.Value.AttemptedValue); Assert.Null(entry.Value.AttemptedValue);
Assert.Null(entry.Value.RawValue); Assert.Null(entry.Value.RawValue);
var error = Assert.Single(entry.Value.Errors); var error = Assert.Single(entry.Value.Errors);
Assert.Null(error.Exception);
// Update me in 3.0 when MvcJsonOptions.AllowInputFormatterExceptionMessages is removed
// Json.NET currently throws an exception starting with "No JSON content found and type 'System.Int32' is Assert.IsType<JsonSerializationException>(error.Exception);
// not nullable." but do not tie test to a particular Json.NET build. Assert.Empty(error.ErrorMessage);
Assert.NotEmpty(error.ErrorMessage);
} }
private class Person5 private class Person5
@ -545,7 +545,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Empty(modelState); Assert.Empty(modelState);
} }
[Fact] [Fact] // This test covers the 2.0 behavior. Error messages from JSON.Net are preserved.
public async Task FromBodyWithInvalidPropertyData_JsonFormatterAddsModelError() public async Task FromBodyWithInvalidPropertyData_JsonFormatterAddsModelError()
{ {
// Arrange // Arrange
@ -586,18 +586,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(state.AttemptedValue); Assert.Null(state.AttemptedValue);
Assert.Null(state.RawValue); Assert.Null(state.RawValue);
var error = Assert.Single(state.Errors); 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 // Update me in 3.0 when MvcJsonOptions.AllowInputFormatterExceptionMessages is removed
// integer: not a number." but do not tie test to a particular Json.NET build. Assert.IsType<JsonReaderException>(error.Exception);
Assert.NotEmpty(error.ErrorMessage); Assert.Empty(error.ErrorMessage);
} }
[Theory] [Theory]
[InlineData(false, false)] [InlineData(false, false)]
[InlineData(true, true)] [InlineData(true, true)]
public async Task FromBodyWithEmptyBody_JsonFormatterAddsModelErrorWhenExpected( public async Task FromBodyWithEmptyBody_JsonFormatterAddsModelErrorWhenExpected(
bool allowEmptyInputInBodyModelBindingSetting, bool expectedModelStateIsValid) bool allowEmptyInputInBodyModelBindingSetting,
bool expectedModelStateIsValid)
{ {
// Arrange // Arrange
var parameter = new ParameterDescriptor var parameter = new ParameterDescriptor

View File

@ -28,11 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
// Act // Act
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
// Assert // Assert
Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy); 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")] [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 // Act
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); 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")] [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 // Act
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); 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. // This just does the minimum needed to be able to resolve these options.

View File

@ -386,6 +386,13 @@ namespace Microsoft.AspNetCore.Mvc
typeof(RazorPagesOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptionsConfigureCompatibilityOptions", throwOnError: true), typeof(RazorPagesOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptionsConfigureCompatibilityOptions", throwOnError: true),
} }
}, },
{
typeof(IPostConfigureOptions<MvcJsonOptions>),
new[]
{
typeof(MvcJsonOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.MvcJsonOptionsConfigureCompatibilityOptions", throwOnError: true),
}
},
{ {
typeof(IActionConstraintProvider), typeof(IActionConstraintProvider),
new Type[] new Type[]