diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs
new file mode 100644
index 0000000000..2c5694b4c6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs
@@ -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
+{
+ ///
+ /// A policy which s can implement to indicate if they want the body model binder
+ /// to handle all exceptions. By default, all default s implement this interface and
+ /// have a default value of .
+ ///
+ public interface IInputFormatterExceptionPolicy
+ {
+ ///
+ /// 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.
+ ///
+ InputFormatterExceptionModelStatePolicy ExceptionPolicy { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionModelStatePolicy.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionModelStatePolicy.cs
new file mode 100644
index 0000000000..3daf6438e0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionModelStatePolicy.cs
@@ -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,
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/InputFormatterException.cs b/src/Microsoft.AspNetCore.Mvc.Core/InputFormatterException.cs
new file mode 100644
index 0000000000..135d71d701
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/InputFormatterException.cs
@@ -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
+{
+ ///
+ /// Exception thrown by when the input is not in an expected format.
+ ///
+ public class InputFormatterException : Exception
+ {
+ public InputFormatterException()
+ {
+ }
+
+ public InputFormatterException(string message)
+ : base(message)
+ {
+ }
+
+ public InputFormatterException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs
index 2ed481d7b8..9d22fd0de3 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs
@@ -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;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
index 2244af1c51..1f17afa385 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs
@@ -168,5 +168,13 @@ namespace Microsoft.AspNetCore.Mvc
/// by default.
///
public bool AllowBindingUndefinedValueToEnumType { get; set; }
+
+ ///
+ /// 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 s into model state errors.
+ /// This option applies only to custom s.
+ /// Default is .
+ ///
+ public InputFormatterExceptionModelStatePolicy InputFormatterExceptionModelStatePolicy { get; set; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
index 803eec6b17..6ce44c0876 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs
@@ -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
{
///
/// A for JSON content.
///
- public class JsonInputFormatter : TextInputFormatter
+ public class JsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
{
private readonly IArrayPool _charPool;
private readonly ILogger _logger;
@@ -104,6 +105,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
}
+ ///
+ public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
+ {
+ get
+ {
+ if (GetType() == typeof(JsonInputFormatter))
+ {
+ return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
+ }
+ return InputFormatterExceptionModelStatePolicy.AllExceptions;
+ }
+ }
+
///
/// Gets the used to configure the .
///
@@ -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();
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
index 81fc385480..b04522111b 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
@@ -63,6 +63,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch);
}
+ ///
+ public override InputFormatterExceptionModelStatePolicy ExceptionPolicy
+ {
+ get
+ {
+ if (GetType() == typeof(JsonPatchInputFormatter))
+ {
+ return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
+ }
+ return InputFormatterExceptionModelStatePolicy.AllExceptions;
+ }
+ }
+
///
public async override Task ReadRequestBodyAsync(
InputFormatterContext context,
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs
index 6928f7f0bd..9396dad37e 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs
@@ -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);
+ ///
+ /// An error occured while deserializing input data.
+ ///
+ internal static string ErrorDeserializingInputData
+ {
+ get => GetString("ErrorDeserializingInputData");
+ }
+
+ ///
+ /// An error occured while deserializing input data.
+ ///
+ internal static string FormatErrorDeserializingInputData()
+ => GetString("ErrorDeserializingInputData");
+
///
/// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx
index 730dd8c289..b3e8d858f3 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx
@@ -120,6 +120,9 @@
The type must be an interface and must be or derive from '{0}'.
+
+ An error occured while deserializing input data.
+
{0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
index b57aa46dad..e2dde401b1 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
@@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// This class handles deserialization of input XML data
/// to strongly-typed objects using .
///
- public class XmlDataContractSerializerInputFormatter : TextInputFormatter
+ public class XmlDataContractSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
{
private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
@@ -98,6 +98,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
}
+ ///
+ public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
+ {
+ get
+ {
+ if (GetType() == typeof(XmlDataContractSerializerInputFormatter))
+ {
+ return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
+ }
+ return InputFormatterExceptionModelStatePolicy.AllExceptions;
+ }
+ }
+
///
public override async Task 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);
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
index 4ce0807982..02cc381aaf 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
@@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// This class handles deserialization of input XML data
/// to strongly-typed objects using
///
- public class XmlSerializerInputFormatter : TextInputFormatter
+ public class XmlSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
{
private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
@@ -77,6 +77,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
///
public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas => _readerQuotas;
+ ///
+ public virtual InputFormatterExceptionModelStatePolicy ExceptionPolicy
+ {
+ get
+ {
+ if (GetType() == typeof(XmlSerializerInputFormatter))
+ {
+ return InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
+ }
+ return InputFormatterExceptionModelStatePolicy.AllExceptions;
+ }
+ }
+
///
public override async Task 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);
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs
index f51ad6bac7..411940c2a9 100644
--- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs
@@ -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);
///
- /// Value cannot contain whitespace.
+ /// Value cannot contain HTML space characters.
///
internal static string ArgumentCannotContainHtmlSpace
{
@@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
///
- /// Value cannot contain whitespace.
+ /// Value cannot contain HTML space characters.
///
internal static string FormatArgumentCannotContainHtmlSpace()
=> GetString("ArgumentCannotContainHtmlSpace");
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
index 3368825329..addf26f1d2 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs
@@ -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().BindingDetails(d => d.BindingSource = BindingSource.Body);
+ var metadataProvider = new TestModelMetadataProvider();
+ metadataProvider.ForType().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(entry.Value.Errors[0].Exception.InnerException);
+ Assert.Same(expectedFormatException, formatException);
+ }
+
+ public static TheoryData BuiltInFormattersThrowingInputFormatterException
+ {
+ get
+ {
+ return new TheoryData()
+ {
+ { 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().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(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().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(entry.Value.Errors[0].Exception);
+ }
+
+ public static TheoryData DerivedFormattersThrowingInputFormatterException
+ {
+ get
+ {
+ return new TheoryData()
+ {
+ { 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().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(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().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(entry.Value.Errors[0].Exception);
+ }
+
+ // Throwing Non-InputFormatterException
+ public static TheoryData BuiltInFormattersThrowingNonInputFormatterException
+ {
+ get
+ {
+ return new TheoryData()
+ {
+ { 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().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(() => binder.BindModelAsync(bindingContext));
+ Assert.Equal("Unable to read input stream!!", exception.Message);
+ }
+
+ public static TheoryData DerivedInputFormattersThrowingNonInputFormatterException
+ {
+ get
+ {
+ return new TheoryData()
+ {
+ { 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().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(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().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(
+ () => 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().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(entry.Value.Errors[0].Exception);
}
[Fact]
@@ -392,23 +739,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static BodyModelBinder CreateBinder(IList 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 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> _readRequestBodyAsync;
+
+ public XyzFormatter(Func> 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.Shared, new DefaultObjectPoolProvider())
+ {
+ _throwNonInputFormatterException = throwNonInputFormatterException;
+ }
+
+ public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
+
+ public override Task 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 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 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.Shared, new DefaultObjectPoolProvider())
+ {
+ _throwNonInputFormatterException = throwNonInputFormatterException;
+ }
+
+ public override InputFormatterExceptionModelStatePolicy ExceptionPolicy => InputFormatterExceptionModelStatePolicy.AllExceptions;
+
+ public override Task 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 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 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; }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
index 4cc8f27279..4833c0dea9 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
@@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act & Assert
- await Assert.ThrowsAsync(async () => await formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(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(async () => await formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(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(async () => await formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(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(async () => await formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(async () => await formatter.ReadAsync(context));
}
[Fact]
diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
index 1cc85cfa7c..e8b80b3582 100644
--- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
@@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act & Assert
- await Assert.ThrowsAsync(() => formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(() => 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(() => formatter.ReadAsync(context));
+ await Assert.ThrowsAsync(() => formatter.ReadAsync(context));
}
[Fact]
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs
index 87cfc67238..a11073b0b4 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs
@@ -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);
}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs
index ff11635f23..0dfa1016ce 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs
@@ -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);
}
}
}
\ No newline at end of file
diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs
index 70ba5ac3ce..9cf910399d 100644
--- a/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs
+++ b/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs
@@ -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}")));
}
}