Better JSON deserialization errors. Implements #4607, #4862

This commit is contained in:
Steve Sanderson 2017-11-01 15:57:42 +00:00
parent ab4c519dd5
commit d9825d1547
17 changed files with 257 additions and 32 deletions

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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))]

View File

@ -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"));

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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()
{

View File

@ -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

View File

@ -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);
});
}
}
}

View File

@ -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

View File

@ -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()

View File

@ -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")]

View File

@ -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]

View File

@ -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);
}
}
}

View File

@ -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]

View File

@ -47,7 +47,7 @@ namespace FormatterWebSite.Controllers
{
if (!ModelState.IsValid)
{
return BadRequest();
return BadRequest(ModelState);
}
return Content(dummyObject.SampleInt.ToString());