Updated formatters to wrap exceptions in InputFormatException for invalid input
This commit is contained in:
parent
f24d71136a
commit
83c3ac62fb
|
|
@ -0,0 +1,19 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// A policy which <see cref="IInputFormatter"/>s can implement to indicate if they want the body model binder
|
||||
/// to handle all exceptions. By default, all default <see cref="IInputFormatter"/>s implement this interface and
|
||||
/// have a default value of <see cref="InputFormatterExceptionModelStatePolicy.MalformedInputExceptions"/>.
|
||||
/// </summary>
|
||||
public interface IInputFormatterExceptionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the flag to indicate if the body model binder should handle all exceptions. If an exception is handled,
|
||||
/// the body model binder converts the exception into model state errors, else the exception is allowed to propagate.
|
||||
/// </summary>
|
||||
InputFormatterExceptionModelStatePolicy ExceptionPolicy { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
public enum InputFormatterExceptionModelStatePolicy
|
||||
{
|
||||
AllExceptions = 0,
|
||||
MalformedInputExceptions = 1,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown by <see cref="IInputFormatter"/> when the input is not in an expected format.
|
||||
/// </summary>
|
||||
public class InputFormatterException : Exception
|
||||
{
|
||||
public InputFormatterException()
|
||||
{
|
||||
}
|
||||
|
||||
public InputFormatterException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InputFormatterException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,10 +177,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
bindingContext.ModelState.AddModelError(modelBindingKey, message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception exception) when (exception is InputFormatterException || ShouldHandleException(formatter))
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(modelBindingKey, ex, bindingContext.ModelMetadata);
|
||||
bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldHandleException(IInputFormatter formatter)
|
||||
{
|
||||
var policy = _options.InputFormatterExceptionModelStatePolicy;
|
||||
|
||||
// Any explicit policy on the formatters takes precedence over the global policy on MvcOptions
|
||||
if (formatter is IInputFormatterExceptionPolicy exceptionPolicy)
|
||||
{
|
||||
policy = exceptionPolicy.ExceptionPolicy;
|
||||
}
|
||||
|
||||
return policy == InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,5 +168,13 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// <see langword="false"/> by default.
|
||||
/// </summary>
|
||||
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)
|
||||
/// 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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,24 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TextInputFormatter"/> for JSON content.
|
||||
/// </summary>
|
||||
public class JsonInputFormatter : TextInputFormatter
|
||||
public class JsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
||||
{
|
||||
private readonly IArrayPool<char> _charPool;
|
||||
private readonly ILogger _logger;
|
||||
|
|
@ -104,6 +105,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetType() == typeof(JsonInputFormatter))
|
||||
{
|
||||
return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
}
|
||||
return InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
|
|
@ -149,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
jsonReader.CloseInput = false;
|
||||
|
||||
var successful = true;
|
||||
|
||||
Exception exception = null;
|
||||
void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
|
||||
{
|
||||
successful = false;
|
||||
|
|
@ -177,6 +191,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
_logger.JsonInputException(eventArgs.ErrorContext.Error);
|
||||
|
||||
exception = eventArgs.ErrorContext.Error;
|
||||
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
|
|
@ -214,6 +230,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
}
|
||||
|
||||
if (!(exception is JsonException || exception is OverflowException))
|
||||
{
|
||||
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
|
||||
exceptionDispatchInfo.Throw();
|
||||
}
|
||||
|
||||
return InputFormatterResult.Failure();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetType() == typeof(JsonPatchInputFormatter))
|
||||
{
|
||||
return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
}
|
||||
return InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async override Task<InputFormatterResult> ReadRequestBodyAsync(
|
||||
InputFormatterContext context,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
internal static string FormatEnumerableWrapperProvider_InvalidSourceEnumerableOfT(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// An error occured while deserializing input data.
|
||||
/// </summary>
|
||||
internal static string ErrorDeserializingInputData
|
||||
{
|
||||
get => GetString("ErrorDeserializingInputData");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occured while deserializing input data.
|
||||
/// </summary>
|
||||
internal static string FormatErrorDeserializingInputData()
|
||||
=> GetString("ErrorDeserializingInputData");
|
||||
|
||||
/// <summary>
|
||||
/// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -120,6 +120,9 @@
|
|||
<data name="EnumerableWrapperProvider_InvalidSourceEnumerableOfT" xml:space="preserve">
|
||||
<value>The type must be an interface and must be or derive from '{0}'.</value>
|
||||
</data>
|
||||
<data name="ErrorDeserializingInputData" xml:space="preserve">
|
||||
<value>An error occured while deserializing input data.</value>
|
||||
</data>
|
||||
<data name="RequiredProperty_MustHaveDataMemberRequired" xml:space="preserve">
|
||||
<value>{0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// This class handles deserialization of input XML data
|
||||
/// to strongly-typed objects using <see cref="DataContractSerializer"/>.
|
||||
/// </summary>
|
||||
public class XmlDataContractSerializerInputFormatter : TextInputFormatter
|
||||
public class XmlDataContractSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
|
||||
|
|
@ -98,6 +98,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetType() == typeof(XmlDataContractSerializerInputFormatter))
|
||||
{
|
||||
return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
}
|
||||
return InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
|
|
@ -124,23 +137,30 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
request.Body.Seek(0L, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
|
||||
try
|
||||
{
|
||||
var type = GetSerializableType(context.ModelType);
|
||||
var serializer = GetCachedSerializer(type);
|
||||
|
||||
var deserializedObject = serializer.ReadObject(xmlReader);
|
||||
|
||||
// Unwrap only if the original type was wrapped.
|
||||
if (type != context.ModelType)
|
||||
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
|
||||
{
|
||||
if (deserializedObject is IUnwrappable unwrappable)
|
||||
{
|
||||
deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
|
||||
}
|
||||
}
|
||||
var type = GetSerializableType(context.ModelType);
|
||||
var serializer = GetCachedSerializer(type);
|
||||
|
||||
return InputFormatterResult.Success(deserializedObject);
|
||||
var deserializedObject = serializer.ReadObject(xmlReader);
|
||||
|
||||
// Unwrap only if the original type was wrapped.
|
||||
if (type != context.ModelType)
|
||||
{
|
||||
if (deserializedObject is IUnwrappable unwrappable)
|
||||
{
|
||||
deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
|
||||
}
|
||||
}
|
||||
|
||||
return InputFormatterResult.Success(deserializedObject);
|
||||
}
|
||||
}
|
||||
catch (SerializationException exception)
|
||||
{
|
||||
throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// This class handles deserialization of input XML data
|
||||
/// to strongly-typed objects using <see cref="XmlSerializer"/>
|
||||
/// </summary>
|
||||
public class XmlSerializerInputFormatter : TextInputFormatter
|
||||
public class XmlSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
|
||||
|
|
@ -77,6 +77,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </summary>
|
||||
public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas => _readerQuotas;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetType() == typeof(XmlSerializerInputFormatter))
|
||||
{
|
||||
return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
}
|
||||
return InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
|
||||
InputFormatterContext context,
|
||||
|
|
@ -105,24 +118,33 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
request.Body.Seek(0L, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
|
||||
try
|
||||
{
|
||||
var type = GetSerializableType(context.ModelType);
|
||||
|
||||
var serializer = GetCachedSerializer(type);
|
||||
|
||||
var deserializedObject = serializer.Deserialize(xmlReader);
|
||||
|
||||
// Unwrap only if the original type was wrapped.
|
||||
if (type != context.ModelType)
|
||||
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding))
|
||||
{
|
||||
if (deserializedObject is IUnwrappable unwrappable)
|
||||
{
|
||||
deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
|
||||
}
|
||||
}
|
||||
var type = GetSerializableType(context.ModelType);
|
||||
|
||||
return InputFormatterResult.Success(deserializedObject);
|
||||
var serializer = GetCachedSerializer(type);
|
||||
|
||||
var deserializedObject = serializer.Deserialize(xmlReader);
|
||||
|
||||
// Unwrap only if the original type was wrapped.
|
||||
if (type != context.ModelType)
|
||||
{
|
||||
if (deserializedObject is IUnwrappable unwrappable)
|
||||
{
|
||||
deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
|
||||
}
|
||||
}
|
||||
|
||||
return InputFormatterResult.Success(deserializedObject);
|
||||
}
|
||||
}
|
||||
// XmlSerializer wraps actual exceptions (like FormatException or XmlException) into an InvalidOperationException
|
||||
// https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs#L652
|
||||
catch (InvalidOperationException exception) when (exception.InnerException is FormatException || exception.InnerException is XmlException)
|
||||
{
|
||||
throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot contain whitespace.
|
||||
/// Value cannot contain HTML space characters.
|
||||
/// </summary>
|
||||
internal static string ArgumentCannotContainHtmlSpace
|
||||
{
|
||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot contain whitespace.
|
||||
/// Value cannot contain HTML space characters.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentCannotContainHtmlSpace()
|
||||
=> GetString("ArgumentCannotContainHtmlSpace");
|
||||
|
|
|
|||
|
|
@ -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 System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -9,9 +10,13 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
|
|
@ -189,23 +194,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomFormatterDeserializationException_AddedToModelState()
|
||||
// Throwing InputFormatterException
|
||||
[Theory]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.AllExceptions)]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions)]
|
||||
public async Task BindModel_CustomFormatter_ThrowingInputFormatterException_AddsErrorToModelState(
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "text/xyz";
|
||||
|
||||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(
|
||||
typeof(Person),
|
||||
httpContext: httpContext,
|
||||
metadataProvider: provider);
|
||||
|
||||
var binder = CreateBinder(new[] { new XyzFormatter() });
|
||||
var expectedFormatException = new FormatException("bad format!");
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var formatter = new XyzFormatter((inputFormatterContext, encoding) =>
|
||||
{
|
||||
throw new InputFormatterException("Bad input!!", expectedFormatException);
|
||||
});
|
||||
var binder = CreateBinder(
|
||||
new[] { formatter },
|
||||
new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
|
@ -218,7 +233,339 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
var entry = Assert.Single(bindingContext.ModelState);
|
||||
Assert.Equal(string.Empty, entry.Key);
|
||||
var errorMessage = Assert.Single(entry.Value.Errors).Exception.Message;
|
||||
Assert.Equal("Your input is bad!", errorMessage);
|
||||
Assert.Equal("Bad input!!", errorMessage);
|
||||
var formatException = Assert.IsType<FormatException>(entry.Value.Errors[0].Exception.InnerException);
|
||||
Assert.Same(expectedFormatException, formatException);
|
||||
}
|
||||
|
||||
public static TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy> BuiltInFormattersThrowingInputFormatterException
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy>()
|
||||
{
|
||||
{ new XmlSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new XmlSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new XmlDataContractSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new XmlDataContractSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BuiltInFormattersThrowingInputFormatterException))]
|
||||
public async Task BindModel_BuiltInXmlInputFormatters_ThrowingInputFormatterException_AddsErrorToModelState(
|
||||
IInputFormatter formatter,
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "application/xml";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(new[] { formatter }, new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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;
|
||||
Assert.Equal("An error occured while deserializing input data.", errorMessage);
|
||||
Assert.IsType<InputFormatterException>(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.AllExceptions)]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions)]
|
||||
public async Task BindModel_BuiltInJsonInputFormatter_ThrowingInputFormatterException_AddsErrorToModelState(
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "application/json";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(
|
||||
new[] { new TestableJsonInputFormatter(throwNonInputFormatterException: false) },
|
||||
new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public static TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy> DerivedFormattersThrowingInputFormatterException
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IInputFormatter, InputFormatterExceptionModelStatePolicy>()
|
||||
{
|
||||
{ new DerivedXmlSerializerInputFormatter(throwNonInputFormatterException: false), InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new DerivedXmlSerializerInputFormatter(throwNonInputFormatterException: false), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new DerivedXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: false), InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new DerivedXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: false), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DerivedFormattersThrowingInputFormatterException))]
|
||||
public async Task BindModel_DerivedXmlInputFormatters_AddsErrorToModelState_(
|
||||
IInputFormatter formatter,
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "application/xml";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(new[] { formatter }, new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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;
|
||||
Assert.Equal("An error occured while deserializing input data.", errorMessage);
|
||||
Assert.IsType<InputFormatterException>(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.AllExceptions)]
|
||||
[InlineData(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions)]
|
||||
public async Task BindModel_DerivedJsonInputFormatter_AddsErrorToModelState(
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "application/json";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(
|
||||
new[] { new DerivedJsonInputFormatter(throwNonInputFormatterException: false) },
|
||||
new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Throwing Non-InputFormatterException
|
||||
public static TheoryData<IInputFormatter, string, InputFormatterExceptionModelStatePolicy> BuiltInFormattersThrowingNonInputFormatterException
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IInputFormatter, string, InputFormatterExceptionModelStatePolicy>()
|
||||
{
|
||||
{ new TestableXmlSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new TestableXmlSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new TestableXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new TestableXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new TestableJsonInputFormatter(throwNonInputFormatterException: true), "text/json", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new TestableJsonInputFormatter(throwNonInputFormatterException: true), "text/json", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BuiltInFormattersThrowingNonInputFormatterException))]
|
||||
public async Task BindModel_BuiltInInputFormatters_ThrowingNonInputFormatterException_Throws(
|
||||
IInputFormatter formatter,
|
||||
string contentType,
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("valid data!"));
|
||||
httpContext.Request.ContentType = contentType;
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(new[] { formatter }, new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<IOException>(() => binder.BindModelAsync(bindingContext));
|
||||
Assert.Equal("Unable to read input stream!!", exception.Message);
|
||||
}
|
||||
|
||||
public static TheoryData<IInputFormatter, string, InputFormatterExceptionModelStatePolicy> DerivedInputFormattersThrowingNonInputFormatterException
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IInputFormatter, string, InputFormatterExceptionModelStatePolicy>()
|
||||
{
|
||||
{ new DerivedXmlSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new DerivedXmlSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new DerivedXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new DerivedXmlDataContractSerializerInputFormatter(throwNonInputFormatterException: true), "text/xml", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
{ new DerivedJsonInputFormatter(throwNonInputFormatterException: true), "text/json", InputFormatterExceptionModelStatePolicy.AllExceptions },
|
||||
{ new DerivedJsonInputFormatter(throwNonInputFormatterException: true), "text/json", InputFormatterExceptionModelStatePolicy.MalformedInputExceptions },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DerivedInputFormattersThrowingNonInputFormatterException))]
|
||||
public async Task BindModel_DerivedXmlInputFormatters_ThrowingNonInputFormatingException_AddsErrorToModelState(
|
||||
IInputFormatter formatter,
|
||||
string contentType,
|
||||
InputFormatterExceptionModelStatePolicy inputFormatterExceptionModelStatePolicy)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("valid data!"));
|
||||
httpContext.Request.ContentType = contentType;
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var binder = CreateBinder(new[] { formatter }, new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = inputFormatterExceptionModelStatePolicy
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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;
|
||||
Assert.Equal("Unable to read input stream!!", errorMessage);
|
||||
Assert.IsType<IOException>(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_CustomFormatter_ThrowingNonInputFormatterException_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("valid data"));
|
||||
httpContext.Request.ContentType = "text/xyz";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var formatter = new XyzFormatter((inputFormatterContext, encoding) =>
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
});
|
||||
var binder = CreateBinder(
|
||||
new[] { formatter },
|
||||
new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = InputFormatterExceptionModelStatePolicy.MalformedInputExceptions
|
||||
});
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<IOException>(
|
||||
() => binder.BindModelAsync(bindingContext));
|
||||
Assert.Equal("Unable to read input stream!!", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_CustomFormatter_ThrowingNonInputFormatterException_AddsErrorToModelState()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||
httpContext.Request.ContentType = "text/xyz";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), httpContext, metadataProvider);
|
||||
var formatter = new XyzFormatter((inputFormatterContext, encoding) =>
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
});
|
||||
var binder = CreateBinder(
|
||||
new[] { formatter },
|
||||
new MvcOptions()
|
||||
{
|
||||
InputFormatterExceptionModelStatePolicy = InputFormatterExceptionModelStatePolicy.AllExceptions
|
||||
});
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
|
||||
// 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;
|
||||
Assert.Equal("Unable to read input stream!!", errorMessage);
|
||||
Assert.IsType<IOException>(entry.Value.Errors[0].Exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -392,23 +739,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
private static BodyModelBinder CreateBinder(IList<IInputFormatter> formatters, bool treatEmptyInputAsDefaultValueOption = false)
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
var options = new MvcOptions { AllowEmptyInputInBodyModelBinding = treatEmptyInputAsDefaultValueOption };
|
||||
return new BodyModelBinder(formatters, new TestHttpRequestStreamReaderFactory(), loggerFactory, options);
|
||||
return CreateBinder(formatters, options);
|
||||
}
|
||||
|
||||
private class Person
|
||||
private static BodyModelBinder CreateBinder(IList<IInputFormatter> formatters, MvcOptions mvcOptions)
|
||||
{
|
||||
public string Name { get; set; }
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
return new BodyModelBinder(formatters, new TestHttpRequestStreamReaderFactory(), loggerFactory, mvcOptions);
|
||||
}
|
||||
|
||||
private class XyzFormatter : TextInputFormatter
|
||||
{
|
||||
public XyzFormatter()
|
||||
private readonly Func<InputFormatterContext, Encoding, Task<InputFormatterResult>> _readRequestBodyAsync;
|
||||
|
||||
public XyzFormatter(Func<InputFormatterContext, Encoding, Task<InputFormatterResult>> readRequestBodyAsync)
|
||||
{
|
||||
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xyz"));
|
||||
SupportedEncodings.Add(Encoding.UTF8);
|
||||
_readRequestBodyAsync = readRequestBodyAsync;
|
||||
}
|
||||
|
||||
protected override bool CanReadType(Type type)
|
||||
|
|
@ -420,7 +770,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
InputFormatterContext context,
|
||||
Encoding effectiveEncoding)
|
||||
{
|
||||
throw new InvalidOperationException("Your input is bad!");
|
||||
return _readRequestBodyAsync(context, effectiveEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,5 +793,144 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
return InputFormatterResult.SuccessAsync(this);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestableJsonInputFormatter : JsonInputFormatter
|
||||
{
|
||||
private readonly bool _throwNonInputFormatterException;
|
||||
|
||||
public TestableJsonInputFormatter(bool throwNonInputFormatterException)
|
||||
: base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider())
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestableXmlSerializerInputFormatter : XmlSerializerInputFormatter
|
||||
{
|
||||
private bool _throwNonInputFormatterException;
|
||||
|
||||
public TestableXmlSerializerInputFormatter(bool throwNonInputFormatterException)
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestableXmlDataContractSerializerInputFormatter : XmlDataContractSerializerInputFormatter
|
||||
{
|
||||
private bool _throwNonInputFormatterException;
|
||||
|
||||
public TestableXmlDataContractSerializerInputFormatter(bool throwNonInputFormatterException)
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedJsonInputFormatter : JsonInputFormatter
|
||||
{
|
||||
private readonly bool _throwNonInputFormatterException;
|
||||
|
||||
public DerivedJsonInputFormatter(bool throwNonInputFormatterException)
|
||||
: base(GetLogger(), new JsonSerializerSettings(), ArrayPool<char>.Shared, new DefaultObjectPoolProvider())
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedXmlSerializerInputFormatter : XmlSerializerInputFormatter
|
||||
{
|
||||
private bool _throwNonInputFormatterException;
|
||||
|
||||
public DerivedXmlSerializerInputFormatter(bool throwNonInputFormatterException)
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedXmlDataContractSerializerInputFormatter : XmlDataContractSerializerInputFormatter
|
||||
{
|
||||
private bool _throwNonInputFormatterException;
|
||||
|
||||
public DerivedXmlDataContractSerializerInputFormatter(bool throwNonInputFormatterException)
|
||||
{
|
||||
_throwNonInputFormatterException = throwNonInputFormatterException;
|
||||
}
|
||||
|
||||
public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.AllExceptions;
|
||||
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (_throwNonInputFormatterException)
|
||||
{
|
||||
throw new IOException("Unable to read input stream!!");
|
||||
}
|
||||
return base.ReadRequestBodyAsync(context, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private static ILogger GetLogger()
|
||||
{
|
||||
return NullLogger.Instance;
|
||||
}
|
||||
|
||||
// 'public' as XmlSerializer does not like non-public types
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<SerializationException>(async () => await formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(async () => await formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -324,7 +324,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<SerializationException>(async () => await formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(async () => await formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -496,7 +496,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<SerializationException>(async () => await formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(async () => await formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -553,7 +553,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<SerializationException>(async () => await formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(async () => await formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(() => formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
|
|
@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => formatter.ReadAsync(context));
|
||||
await Assert.ThrowsAsync<InputFormatterException>(() => formatter.ReadAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var data = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains(
|
||||
string.Format(
|
||||
":There was an error deserializing the object of type {0}.",
|
||||
"There was an error deserializing the object of type {0}.",
|
||||
typeof(DummyClass).FullName),
|
||||
data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("There is an error in XML document", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
|
|
@ -34,26 +35,10 @@ namespace XmlFormattersWebSite
|
|||
var errors = keyModelStatePair.Value.Errors;
|
||||
if (errors != null && errors.Count > 0)
|
||||
{
|
||||
string errorMessage = null;
|
||||
foreach (var modelError in errors)
|
||||
{
|
||||
if (string.IsNullOrEmpty(modelError.ErrorMessage))
|
||||
{
|
||||
if (modelError.Exception != null)
|
||||
{
|
||||
errorMessage = modelError.Exception.Message;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = modelError.ErrorMessage;
|
||||
}
|
||||
|
||||
if (errorMessage != null)
|
||||
{
|
||||
allErrorMessages.Add(string.Format("{0}:{1}", key, errorMessage));
|
||||
}
|
||||
}
|
||||
allErrorMessages.Add(
|
||||
string.Join(
|
||||
",",
|
||||
errors.Select(modelError => $"ErrorMessage:{modelError.ErrorMessage};Exception:{modelError.Exception}")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue