parent
ab4c519dd5
commit
d9825d1547
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
|
|
@ -263,6 +264,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
return TryAddModelError(key, errorMessage);
|
||||
}
|
||||
else if (exception is InputFormatterException && !string.IsNullOrEmpty(exception.Message))
|
||||
{
|
||||
// InputFormatterException is a signal that the message is safe to expose to clients
|
||||
return TryAddModelError(key, exception.Message);
|
||||
}
|
||||
|
||||
ErrorCount++;
|
||||
AddModelErrorCore(key, exception);
|
||||
|
|
|
|||
|
|
@ -170,11 +170,20 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public bool AllowBindingUndefinedValueToEnumType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option to determine if model binding should convert all exceptions(including ones not related to bad input)
|
||||
/// Gets or sets the option to determine if model binding should convert all exceptions (including ones not related to bad input)
|
||||
/// that occur during deserialization in <see cref="IInputFormatter"/>s into model state errors.
|
||||
/// This option applies only to custom <see cref="IInputFormatter"/>s.
|
||||
/// Default is <see cref="InputFormatterExceptionModelStatePolicy.AllExceptions"/>.
|
||||
/// </summary>
|
||||
public InputFormatterExceptionModelStatePolicy InputFormatterExceptionModelStatePolicy { 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; set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: TypeForwardedTo(typeof(InputFormatterException))]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
_jsonSerializerSettings,
|
||||
_charPool,
|
||||
_objectPoolProvider,
|
||||
options.SuppressInputFormatterBuffering));
|
||||
options.SuppressInputFormatterBuffering,
|
||||
options.SuppressJsonDeserializationExceptionMessagesInModelState));
|
||||
|
||||
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
|
||||
options.InputFormatters.Add(new JsonInputFormatter(
|
||||
|
|
@ -77,7 +78,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
_jsonSerializerSettings,
|
||||
_charPool,
|
||||
_objectPoolProvider,
|
||||
options.SuppressInputFormatterBuffering));
|
||||
options.SuppressInputFormatterBuffering,
|
||||
options.SuppressJsonDeserializationExceptionMessagesInModelState));
|
||||
|
||||
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
private readonly ILogger _logger;
|
||||
private readonly ObjectPoolProvider _objectPoolProvider;
|
||||
private readonly bool _suppressInputFormatterBuffering;
|
||||
private readonly bool _suppressJsonDeserializationExceptionMessages;
|
||||
|
||||
private ObjectPool<JsonSerializer> _jsonSerializerPool;
|
||||
|
||||
|
|
@ -70,6 +71,32 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
ArrayPool<char> charPool,
|
||||
ObjectPoolProvider objectPoolProvider,
|
||||
bool suppressInputFormatterBuffering)
|
||||
: this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages: false)
|
||||
{
|
||||
// This constructor by default treats JSON deserialization exceptions as safe
|
||||
// because this is the default for applications generally
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="JsonInputFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||
/// <param name="serializerSettings">
|
||||
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
|
||||
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
|
||||
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
|
||||
/// </param>
|
||||
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
|
||||
/// <param name="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>
|
||||
public JsonInputFormatter(
|
||||
ILogger logger,
|
||||
JsonSerializerSettings serializerSettings,
|
||||
ArrayPool<char> charPool,
|
||||
ObjectPoolProvider objectPoolProvider,
|
||||
bool suppressInputFormatterBuffering,
|
||||
bool suppressJsonDeserializationExceptionMessages)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
|
|
@ -96,6 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
_charPool = new JsonArrayPool<char>(charPool);
|
||||
_objectPoolProvider = objectPoolProvider;
|
||||
_suppressInputFormatterBuffering = suppressInputFormatterBuffering;
|
||||
_suppressJsonDeserializationExceptionMessages = suppressJsonDeserializationExceptionMessages;
|
||||
|
||||
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(UTF16EncodingLittleEndian);
|
||||
|
|
@ -187,7 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
|
||||
var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path);
|
||||
context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error, metadata);
|
||||
var modelStateException = WrapExceptionForModelState(eventArgs.ErrorContext.Error);
|
||||
context.ModelState.TryAddModelError(key, modelStateException, metadata);
|
||||
|
||||
_logger.JsonInputException(eventArgs.ErrorContext.Error);
|
||||
|
||||
|
|
@ -315,5 +344,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private Exception WrapExceptionForModelState(Exception exception)
|
||||
{
|
||||
// It's not known that Json.NET currently ever raises error events with exceptions
|
||||
// other than these two types, but we're being conservative and limiting which ones
|
||||
// we regard as having safe messages to expose to clients
|
||||
var isJsonExceptionType =
|
||||
exception is JsonReaderException || exception is JsonSerializationException;
|
||||
var suppressOriginalMessage =
|
||||
_suppressJsonDeserializationExceptionMessages || !isJsonExceptionType;
|
||||
return suppressOriginalMessage
|
||||
? exception
|
||||
: new InputFormatterException(exception.Message, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
|
||||
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
|
||||
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
|
||||
/// </param>/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// </param>
|
||||
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
|
||||
public JsonPatchInputFormatter(
|
||||
ILogger logger,
|
||||
|
|
@ -46,7 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
|
||||
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
|
||||
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
|
||||
/// </param>/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// </param>
|
||||
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
|
||||
/// <param name="suppressInputFormatterBuffering">Flag to buffer entire request body before deserializing it.</param>
|
||||
public JsonPatchInputFormatter(
|
||||
|
|
@ -55,7 +57,31 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
ArrayPool<char> charPool,
|
||||
ObjectPoolProvider objectPoolProvider,
|
||||
bool suppressInputFormatterBuffering)
|
||||
: base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering)
|
||||
: this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages: false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="JsonPatchInputFormatter"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||
/// <param name="serializerSettings">
|
||||
/// The <see cref="JsonSerializerSettings"/>. Should be either the application-wide settings
|
||||
/// (<see cref="MvcJsonOptions.SerializerSettings"/>) or an instance
|
||||
/// <see cref="JsonSerializerSettingsProvider.CreateSerializerSettings"/> initially returned.
|
||||
/// </param>
|
||||
/// <param name="charPool">The <see cref="ArrayPool{Char}"/>.</param>
|
||||
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
|
||||
/// <param name="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>
|
||||
public JsonPatchInputFormatter(
|
||||
ILogger logger,
|
||||
JsonSerializerSettings serializerSettings,
|
||||
ArrayPool<char> charPool,
|
||||
ObjectPoolProvider objectPoolProvider,
|
||||
bool suppressInputFormatterBuffering,
|
||||
bool suppressJsonDeserializationExceptionMessages)
|
||||
: base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, suppressJsonDeserializationExceptionMessages)
|
||||
{
|
||||
// Clear all values and only include json-patch+json value.
|
||||
SupportedMediaTypes.Clear();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -1005,7 +1006,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_NoErrorMessage_ForNonFormatException()
|
||||
public void ModelStateDictionary_NoErrorMessage_ForUnrecognizedException()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
|
@ -1021,6 +1022,28 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Empty(error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_AddsErrorMessage_ForInputFormatterException()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "This is an InputFormatterException";
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
var bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||
var provider = new DefaultModelMetadataProvider(compositeProvider, new OptionsAccessor());
|
||||
var metadata = provider.GetMetadataForType(typeof(int));
|
||||
|
||||
// Act
|
||||
dictionary.TryAddModelError("key", new InputFormatterException(expectedMessage), metadata);
|
||||
|
||||
// Assert
|
||||
var entry = Assert.Single(dictionary);
|
||||
Assert.Equal("key", entry.Key);
|
||||
var error = Assert.Single(entry.Value.Errors);
|
||||
Assert.Equal(expectedMessage, error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_ClearEntriesThatMatchWithKey_NonEmptyKey()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -232,10 +232,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Key is the empty string because this was a top-level binding.
|
||||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).Exception.Message;
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage;
|
||||
Assert.Equal("Bad input!!", errorMessage);
|
||||
var formatException = Assert.IsType<FormatException>(entry.Value.Errors[0].Exception.InnerException);
|
||||
Assert.Same(expectedFormatException, formatException);
|
||||
Assert.Null(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
public static TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy> BuiltInFormattersThrowingInputFormatterException
|
||||
|
|
@ -282,9 +281,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Key is the empty string because this was a top-level binding.
|
||||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).Exception.Message;
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage;
|
||||
Assert.Equal("An error occured while deserializing input data.", errorMessage);
|
||||
Assert.IsType<InputFormatterException>(entry.Value.Errors[0].Exception);
|
||||
Assert.Null(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -319,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Key is the empty string because this was a top-level binding.
|
||||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
Assert.IsType<JsonReaderException>(entry.Value.Errors[0].Exception);
|
||||
Assert.NotEmpty(entry.Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
public static TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy> DerivedFormattersThrowingInputFormatterException
|
||||
|
|
@ -366,9 +365,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Key is the empty string because this was a top-level binding.
|
||||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).Exception.Message;
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage;
|
||||
Assert.Equal("An error occured while deserializing input data.", errorMessage);
|
||||
Assert.IsType<InputFormatterException>(entry.Value.Errors[0].Exception);
|
||||
Assert.Null(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -403,7 +402,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Key is the empty string because this was a top-level binding.
|
||||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
Assert.IsType<JsonReaderException>(entry.Value.Errors[0].Exception);
|
||||
Assert.NotEmpty(entry.Value.Errors[0].ErrorMessage);
|
||||
Assert.Null(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
// Throwing Non-InputFormatterException
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -47,5 +48,30 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(new[] { "error2", "error3" }, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SerializesErrorsFromModelStateDictionary_AddsDefaultMessage()
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||
modelStateDictionary.AddModelError("unsafeError",
|
||||
new Exception("This message should not be returned to clients"),
|
||||
metadata);
|
||||
|
||||
// Act
|
||||
var problemDescription = new ValidationProblemDetails(modelStateDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("One or more validation errors occured.", problemDescription.Title);
|
||||
Assert.Collection(
|
||||
problemDescription.Errors,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("unsafeError", item.Key);
|
||||
Assert.Equal(new[] { "The input was not valid." }, item.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.True(result.HasError);
|
||||
Assert.Equal(
|
||||
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.",
|
||||
modelState["Age"].Errors[0].Exception.Message);
|
||||
modelState["Age"].Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -392,7 +392,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.True(result.HasError);
|
||||
Assert.Equal(
|
||||
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 59.",
|
||||
modelState["names[1].Small"].Errors[0].Exception.Message);
|
||||
modelState["names[1].Small"].Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -508,7 +508,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.True(result.HasError);
|
||||
Assert.False(modelState.IsValid);
|
||||
|
||||
var modelErrorMessage = modelState.Values.First().Errors[0].Exception.Message;
|
||||
var modelErrorMessage = modelState.Values.First().Errors[0].ErrorMessage;
|
||||
Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -533,6 +533,81 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(settings.DateTimeZoneHandling, actual.DateTimeZoneHandling);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{", "", "Unexpected end when reading JSON. Path '', line 1, position 1.")]
|
||||
[InlineData("{\"a\":{\"b\"}}", "a", "Invalid character after parsing property name. Expected ':' but got: }. Path 'a', line 1, position 9.")]
|
||||
[InlineData("{\"age\":\"x\"}", "age", "Could not convert string to decimal: x. Path 'age', line 1, position 10.")]
|
||||
[InlineData("{\"login\":1}", "login", "Error converting value 1 to type 'Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatterTest+UserLogin'. Path 'login', line 1, position 10.")]
|
||||
[InlineData("{\"login\":{\"username\":\"somevalue\"}}", "login", "Required property 'Password' not found in JSON. Path 'login', line 1, position 33.")]
|
||||
public async Task ReadAsync_RegistersJsonInputExceptionsAsInputFormatterException(
|
||||
string content,
|
||||
string modelStateKey,
|
||||
string expectedMessage)
|
||||
{
|
||||
// Arrange
|
||||
var logger = GetLogger();
|
||||
var formatter =
|
||||
new JsonInputFormatter(logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider);
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = provider.GetMetadataForType(typeof(User));
|
||||
var context = new InputFormatterContext(
|
||||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.True(!modelState.IsValid);
|
||||
Assert.True(modelState.ContainsKey(modelStateKey));
|
||||
|
||||
var modelError = modelState[modelStateKey].Errors.Single();
|
||||
Assert.Equal(expectedMessage, modelError.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenSuppressJsonDeserializationExceptionMessagesIsTrue_DoesNotWrapJsonInputExceptions()
|
||||
{
|
||||
// Arrange
|
||||
var logger = GetLogger();
|
||||
var formatter = new JsonInputFormatter(
|
||||
logger, _serializerSettings, ArrayPool<char>.Shared, _objectPoolProvider,
|
||||
suppressInputFormatterBuffering: false, suppressJsonDeserializationExceptionMessages: true);
|
||||
var contentBytes = Encoding.UTF8.GetBytes("{");
|
||||
var modelStateKey = string.Empty;
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
var provider = new EmptyModelMetadataProvider();
|
||||
var metadata = provider.GetMetadataForType(typeof(User));
|
||||
var context = new InputFormatterContext(
|
||||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.True(!modelState.IsValid);
|
||||
Assert.True(modelState.ContainsKey(modelStateKey));
|
||||
|
||||
var modelError = modelState[modelStateKey].Errors.Single();
|
||||
Assert.IsNotType<InputFormatterException>(modelError.Exception);
|
||||
Assert.Empty(modelError.ErrorMessage);
|
||||
}
|
||||
|
||||
private class TestableJsonInputFormatter : JsonInputFormatter
|
||||
{
|
||||
public TestableJsonInputFormatter(JsonSerializerSettings settings)
|
||||
|
|
@ -609,6 +684,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
public decimal Age { get; set; }
|
||||
|
||||
public byte Small { get; set; }
|
||||
|
||||
public UserLogin Login { get; set; }
|
||||
}
|
||||
|
||||
private sealed class UserLogin
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Contains(exceptionMessage, modelState[""].Errors[0].Exception.Message);
|
||||
Assert.Contains(exceptionMessage, modelState[""].Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
private static ILogger GetLogger()
|
||||
|
|
|
|||
|
|
@ -97,6 +97,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JsonInputFormatter_SuppliedJsonDeserializationErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var content = new StringContent("{", Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync("http://localhost/JsonFormatter/ReturnInput/", content);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
Assert.Equal("{\"\":[\"Unexpected end when reading JSON. Path '', line 1, position 1.\"]}", responseBody);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\"I'm a JSON string!\"")]
|
||||
[InlineData("true")]
|
||||
|
|
|
|||
|
|
@ -46,11 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var data = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
string.Format(
|
||||
"There was an error deserializing the object of type {0}.",
|
||||
typeof(DummyClass).FullName),
|
||||
data);
|
||||
Assert.Contains("An error occured while deserializing input data.", data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var data = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("There is an error in XML document", data);
|
||||
Assert.Contains("An error occured while deserializing input data.", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -485,11 +485,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(entry.Value.AttemptedValue);
|
||||
Assert.Null(entry.Value.RawValue);
|
||||
var error = Assert.Single(entry.Value.Errors);
|
||||
Assert.NotNull(error.Exception);
|
||||
Assert.Null(error.Exception);
|
||||
|
||||
// Json.NET currently throws an exception starting with "No JSON content found and type 'System.Int32' is
|
||||
// not nullable." but do not tie test to a particular Json.NET build.
|
||||
Assert.NotEmpty(error.Exception.Message);
|
||||
Assert.NotEmpty(error.ErrorMessage);
|
||||
}
|
||||
|
||||
private class Person5
|
||||
|
|
@ -586,11 +586,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Null(state.AttemptedValue);
|
||||
Assert.Null(state.RawValue);
|
||||
var error = Assert.Single(state.Errors);
|
||||
Assert.NotNull(error.Exception);
|
||||
Assert.Null(error.Exception);
|
||||
|
||||
// Json.NET currently throws an Exception with a Message starting with "Could not convert string to
|
||||
// integer: not a number." but do not tie test to a particular Json.NET build.
|
||||
Assert.NotEmpty(error.Exception.Message);
|
||||
Assert.NotEmpty(error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace FormatterWebSite.Controllers
|
|||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
return Content(dummyObject.SampleInt.ToString());
|
||||
|
|
|
|||
Loading…
Reference in New Issue