diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IInputFormatter.cs
index 42a2e9b037..7336813291 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IInputFormatter.cs
@@ -11,20 +11,21 @@ namespace Microsoft.AspNet.Mvc.Formatters
public interface IInputFormatter
{
///
- /// Determines whether this can de-serialize
- /// an object of the specified type.
+ /// Determines whether this can deserialize an object of the
+ /// 's .
///
- /// Input formatter context associated with this call.
- /// True if this supports the passed in
- /// request's content-type and is able to de-serialize the request body.
- /// False otherwise.
+ /// The .
+ ///
+ /// true if this can deserialize an object of the
+ /// 's . false otherwise.
+ ///
bool CanRead(InputFormatterContext context);
///
- /// Called during deserialization to read an object from the request.
+ /// Reads an object from the request body.
///
- /// Input formatter context associated with this call.
- /// A task that deserializes the request body.
- Task ReadAsync(InputFormatterContext context);
+ /// The .
+ /// A that on completion deserializes the request body.
+ Task ReadAsync(InputFormatterContext context);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
index 55e2f78ab1..16ea436c20 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
@@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
///
/// The for the current operation.
///
+ /// The name of the model.
///
/// The for recording errors.
///
@@ -27,10 +28,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
///
public InputFormatterContext(
[NotNull] HttpContext httpContext,
+ [NotNull] string modelName,
[NotNull] ModelStateDictionary modelState,
[NotNull] Type modelType)
{
HttpContext = httpContext;
+ ModelName = modelName;
ModelState = modelState;
ModelType = modelType;
}
@@ -38,7 +41,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
///
/// Gets the associated with the current operation.
///
- public HttpContext HttpContext { get; private set; }
+ public HttpContext HttpContext { get; }
+
+ ///
+ /// Gets the name of the model. Used as the key or key prefix for errors added to .
+ ///
+ public string ModelName { get; }
///
/// Gets the associated with the current operation.
@@ -48,6 +56,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
///
/// Gets the expected of the model represented by the request body.
///
- public Type ModelType { get; private set; }
+ public Type ModelType { get; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterResult.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterResult.cs
new file mode 100644
index 0000000000..e681455a4b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterResult.cs
@@ -0,0 +1,93 @@
+// 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.Threading.Tasks;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ ///
+ /// Result of a operation.
+ ///
+ public class InputFormatterResult
+ {
+ private static readonly InputFormatterResult _failure = new InputFormatterResult();
+ private static readonly Task _failureAsync = Task.FromResult(_failure);
+
+ private InputFormatterResult()
+ {
+ HasError = true;
+ }
+
+ private InputFormatterResult(object model)
+ {
+ Model = model;
+ }
+
+ ///
+ /// Gets an indication whether the operation had an error.
+ ///
+ public bool HasError { get; }
+
+ ///
+ /// Gets the deserialized .
+ ///
+ ///
+ /// null if is true .
+ ///
+ public object Model { get; }
+
+ ///
+ /// Returns an indicating the
+ /// operation failed.
+ ///
+ ///
+ /// An indicating the
+ /// operation failed i.e. with true .
+ ///
+ public static InputFormatterResult Failure()
+ {
+ return _failure;
+ }
+
+ ///
+ /// Returns a that on completion provides an indicating
+ /// the operation failed.
+ ///
+ ///
+ /// A that on completion provides an indicating the
+ /// operation failed i.e. with true .
+ ///
+ public static Task FailureAsync()
+ {
+ return _failureAsync;
+ }
+
+ ///
+ /// Returns an indicating the
+ /// operation was successful.
+ ///
+ /// The deserialized .
+ ///
+ /// An indicating the
+ /// operation succeeded i.e. with false .
+ ///
+ public static InputFormatterResult Success(object model)
+ {
+ return new InputFormatterResult(model);
+ }
+
+ ///
+ /// Returns a that on completion provides an indicating
+ /// the operation was successful.
+ ///
+ /// The deserialized .
+ ///
+ /// A that on completion provides an indicating the
+ /// operation succeeded i.e. with false .
+ ///
+ public static Task SuccessAsync(object model)
+ {
+ return Task.FromResult(Success(model));
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
index 1a63311c2a..55f9c87724 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
@@ -67,48 +67,58 @@ namespace Microsoft.AspNet.Mvc.Formatters
return false;
}
- return SupportedMediaTypes
- .Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType));
+ return SupportedMediaTypes.Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType));
}
///
- /// Returns a value indicating whether or not the given type can be read by this serializer.
+ /// Determines whether this can deserialize an object of the given
+ /// .
///
- /// The type of object that will be read.
- /// true if the type can be read, otherwise false .
+ /// The of object that will be read.
+ /// true if the can be read, otherwise false .
protected virtual bool CanReadType(Type type)
{
return true;
}
///
- public virtual Task ReadAsync(InputFormatterContext context)
+ public virtual Task ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
if (request.ContentLength == 0)
{
- return Task.FromResult(GetDefaultValueForType(context.ModelType));
+ return InputFormatterResult.SuccessAsync(GetDefaultValueForType(context.ModelType));
}
return ReadRequestBodyAsync(context);
}
///
- /// Reads the request body.
+ /// Reads an object from the request body.
///
- /// The associated with the call.
- /// A task which can read the request body.
- public abstract Task ReadRequestBodyAsync(InputFormatterContext context);
+ /// The .
+ /// A that on completion deserializes the request body.
+ public abstract Task ReadRequestBodyAsync(InputFormatterContext context);
///
- /// Returns encoding based on content type charset parameter.
+ /// Returns an based on 's
+ /// .
///
- protected Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentType)
+ /// The .
+ ///
+ /// An based on 's
+ /// . null if no supported encoding was found.
+ ///
+ protected Encoding SelectCharacterEncoding(InputFormatterContext context)
{
+ var request = context.HttpContext.Request;
+
+ MediaTypeHeaderValue contentType;
+ MediaTypeHeaderValue.TryParse(request.ContentType, out contentType);
if (contentType != null)
{
var charset = contentType.Charset;
- if (!string.IsNullOrWhiteSpace(contentType.Charset))
+ if (!string.IsNullOrWhiteSpace(charset))
{
foreach (var supportedEncoding in SupportedEncodings)
{
@@ -126,7 +136,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
// No supported encoding was found so there is no way for us to start reading.
- throw new InvalidOperationException(Resources.FormatInputFormatterNoEncoding(GetType().FullName));
+ context.ModelState.TryAddModelError(
+ context.ModelName,
+ Resources.FormatInputFormatterNoEncoding(GetType().FullName));
+
+ return null;
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
index eb96fce813..cfac161936 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
@@ -53,6 +53,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var formatterContext = new InputFormatterContext(
httpContext,
+ modelBindingKey,
bindingContext.ModelState,
bindingContext.ModelType);
var formatters = bindingContext.OperationBindingContext.InputFormatters;
@@ -73,14 +74,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
try
{
var previousCount = bindingContext.ModelState.ErrorCount;
- var model = await formatter.ReadAsync(formatterContext);
-
+ var result = await formatter.ReadAsync(formatterContext);
+ var model = result.Model;
+
+ // Ensure a "modelBindingKey" entry exists whether or not formatting was successful.
bindingContext.ModelState.SetModelValue(modelBindingKey, rawValue: model, attemptedValue: null);
- if (bindingContext.ModelState.ErrorCount != previousCount)
+ if (result.HasError)
{
- // Formatter added an error. Do not use the model it returned. As above, tell the model binding
- // system to skip other model binders and never to fall back.
+ // Formatter encountered an error. Do not use the model it returned. As above, tell the model
+ // binding system to skip other model binders and never to fall back.
return ModelBindingResult.Failed(modelBindingKey);
}
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
index 70dc862207..aa8d5b452b 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
@@ -49,48 +49,74 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
///
- public override Task ReadRequestBodyAsync([NotNull] InputFormatterContext context)
+ public override Task ReadRequestBodyAsync([NotNull] InputFormatterContext context)
{
- var type = context.ModelType;
+ // Get the character encoding for the content.
+ var effectiveEncoding = SelectCharacterEncoding(context);
+ if (effectiveEncoding == null)
+ {
+ return InputFormatterResult.FailureAsync();
+ }
+
var request = context.HttpContext.Request;
- MediaTypeHeaderValue requestContentType = null;
- MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
-
- // Get the character encoding for the content
- // Never non-null since SelectCharacterEncoding() throws in error / not found scenarios
- var effectiveEncoding = SelectCharacterEncoding(requestContentType);
-
using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
{
jsonReader.CloseInput = false;
- var jsonSerializer = CreateJsonSerializer();
-
- EventHandler errorHandler = null;
- errorHandler = (sender, e) =>
+ var successful = true;
+ EventHandler errorHandler = (sender, eventArgs) =>
{
- var exception = e.ErrorContext.Error;
- context.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
+ successful = false;
+
+ var exception = eventArgs.ErrorContext.Error;
+
+ // Handle path combinations such as "" + "Property", "Parent" + "Property", or "Parent" + "[12]".
+ var key = eventArgs.ErrorContext.Path;
+ if (!string.IsNullOrEmpty(context.ModelName))
+ {
+ if (string.IsNullOrEmpty(eventArgs.ErrorContext.Path))
+ {
+ key = context.ModelName;
+ }
+ else if (eventArgs.ErrorContext.Path[0] == '[')
+ {
+ key = context.ModelName + eventArgs.ErrorContext.Path;
+ }
+ else
+ {
+ key = context.ModelName + "." + eventArgs.ErrorContext.Path;
+ }
+ }
+
+ context.ModelState.TryAddModelError(key, 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
- e.ErrorContext.Handled = true;
+ eventArgs.ErrorContext.Handled = true;
};
+
+ var type = context.ModelType;
+ var jsonSerializer = CreateJsonSerializer();
jsonSerializer.Error += errorHandler;
+ object model;
try
{
- return Task.FromResult(jsonSerializer.Deserialize(jsonReader, type));
+ model = jsonSerializer.Deserialize(jsonReader, type);
}
finally
{
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
- if (errorHandler != null)
- {
- jsonSerializer.Error -= errorHandler;
- }
+ jsonSerializer.Error -= errorHandler;
}
+
+ if (successful)
+ {
+ return InputFormatterResult.SuccessAsync(model);
+ }
+
+ return InputFormatterResult.FailureAsync();
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
index 011efc4dcf..3e9adc9cf6 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs
@@ -27,15 +27,19 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
///
- public async override Task ReadRequestBodyAsync([NotNull] InputFormatterContext context)
+ public async override Task ReadRequestBodyAsync([NotNull] InputFormatterContext context)
{
- var jsonPatchDocument = (IJsonPatchDocument)(await base.ReadRequestBodyAsync(context));
- if (jsonPatchDocument != null && SerializerSettings.ContractResolver != null)
+ var result = await base.ReadRequestBodyAsync(context);
+ if (!result.HasError)
{
- jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver;
+ var jsonPatchDocument = (IJsonPatchDocument)result.Model;
+ if (jsonPatchDocument != null && SerializerSettings.ContractResolver != null)
+ {
+ jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver;
+ }
}
- return (object)jsonPatchDocument;
+ return result;
}
///
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
index 12720d48f1..199dce70b5 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs
@@ -86,19 +86,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
}
- ///
- /// Reads the input XML.
- ///
- /// The input formatter context which contains the body to be read.
- /// Task which reads the input.
- public override Task ReadRequestBodyAsync(InputFormatterContext context)
+ ///
+ public override Task ReadRequestBodyAsync(InputFormatterContext context)
{
+ var effectiveEncoding = SelectCharacterEncoding(context);
+ if (effectiveEncoding == null)
+ {
+ return InputFormatterResult.FailureAsync();
+ }
+
var request = context.HttpContext.Request;
-
- MediaTypeHeaderValue requestContentType;
- MediaTypeHeaderValue.TryParse(request.ContentType , out requestContentType);
- var effectiveEncoding = SelectCharacterEncoding(requestContentType);
-
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
{
var type = GetSerializableType(context.ModelType);
@@ -116,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
}
- return Task.FromResult(deserializedObject);
+ return InputFormatterResult.SuccessAsync(deserializedObject);
}
}
@@ -145,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
protected virtual Type GetSerializableType([NotNull] Type declaredType)
{
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(
- new WrapperProviderContext(declaredType, isSerialization: false));
+ new WrapperProviderContext(declaredType, isSerialization: false));
return wrapperProvider?.WrappingType ?? declaredType;
}
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
index 6fa5b9a202..c94902f8e6 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs
@@ -65,19 +65,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
get { return _readerQuotas; }
}
- ///
- /// Reads the input XML.
- ///
- /// The input formatter context which contains the body to be read.
- /// Task which reads the input.
- public override Task ReadRequestBodyAsync(InputFormatterContext context)
+ ///
+ public override Task ReadRequestBodyAsync(InputFormatterContext context)
{
+ var effectiveEncoding = SelectCharacterEncoding(context);
+ if (effectiveEncoding == null)
+ {
+ return InputFormatterResult.FailureAsync();
+ }
+
var request = context.HttpContext.Request;
-
- MediaTypeHeaderValue requestContentType;
- MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
- var effectiveEncoding = SelectCharacterEncoding(requestContentType);
-
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
{
var type = GetSerializableType(context.ModelType);
@@ -96,7 +93,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
}
- return Task.FromResult(deserializedObject);
+ return InputFormatterResult.SuccessAsync(deserializedObject);
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
index 365bdb4f0d..1553f383c4 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
@@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Returns(true)
.Verifiable();
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny()))
- .Returns(Task.FromResult(new Person()))
+ .Returns(InputFormatterResult.SuccessAsync(new Person()))
.Verifiable();
var inputFormatter = mockInputFormatter.Object;
@@ -305,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return true;
}
- public override Task ReadRequestBodyAsync(InputFormatterContext context)
+ public override Task ReadRequestBodyAsync(InputFormatterContext context)
{
throw new InvalidOperationException("Your input is bad!");
}
@@ -325,9 +325,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return _canRead;
}
- public Task ReadAsync(InputFormatterContext context)
+ public Task ReadAsync(InputFormatterContext context)
{
- return Task.FromResult(this);
+ return InputFormatterResult.SuccessAsync(this);
}
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
index 6e546e0f07..e34557e898 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
@@ -38,7 +38,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes("content");
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
- var formatterContext = new InputFormatterContext(httpContext, new ModelStateDictionary(), typeof(string));
+ var formatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ modelType: typeof(string));
// Act
var result = formatter.CanRead(formatterContext);
@@ -80,13 +84,18 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var context = new InputFormatterContext(httpContext, new ModelStateDictionary(), type);
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ modelType: type);
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.Equal(expected, model);
+ Assert.False(result.HasError);
+ Assert.Equal(expected, result.Model);
}
[Fact]
@@ -98,13 +107,18 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
- var context = new InputFormatterContext(httpContext, new ModelStateDictionary(), typeof(User));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ modelType: typeof(User));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- var userModel = Assert.IsType(model);
+ Assert.False(result.HasError);
+ var userModel = Assert.IsType(result.Model);
Assert.Equal("Person Name", userModel.Name);
Assert.Equal(30, userModel.Age);
}
@@ -119,13 +133,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
-
- var context = new InputFormatterContext(httpContext, modelState, typeof(User));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(User));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
+ Assert.True(result.HasError);
Assert.Equal(
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.",
modelState["Age"].Errors[0].Exception.Message);
@@ -141,17 +159,21 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
-
- var context = new InputFormatterContext(httpContext, modelState, typeof(User));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(User));
modelState.MaxAllowedErrors = 3;
modelState.AddModelError("key1", "error1");
modelState.AddModelError("key2", "error2");
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
+ Assert.True(result.HasError);
Assert.False(modelState.ContainsKey("age"));
var error = Assert.Single(modelState[""].Errors);
Assert.IsType(error.Exception);
@@ -193,13 +215,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
-
- var inputFormatterContext = new InputFormatterContext(httpContext, modelState, typeof(UserLogin));
+ var inputFormatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(UserLogin));
// Act
- var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
+ var result = await jsonFormatter.ReadAsync(inputFormatterContext);
// Assert
+ Assert.True(result.HasError);
Assert.False(modelState.IsValid);
var modelErrorMessage = modelState.Values.First().Errors[0].Exception.Message;
@@ -222,13 +248,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
-
- var inputFormatterContext = new InputFormatterContext(httpContext, modelState, typeof(UserLogin));
+ var inputFormatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(UserLogin));
// Act
- var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
+ var result = await jsonFormatter.ReadAsync(inputFormatterContext);
// Assert
+ Assert.True(result.HasError);
Assert.False(modelState.IsValid);
var modelErrorMessage = modelState.Values.First().Errors[0].Exception.Message;
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
index abc7ca3758..5bf672bffc 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
@@ -25,13 +25,18 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
- var context = new InputFormatterContext(httpContext, modelState, typeof(JsonPatchDocument));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(JsonPatchDocument));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- var patchDoc = Assert.IsType>(model);
+ Assert.False(result.HasError);
+ var patchDoc = Assert.IsType>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value);
@@ -48,13 +53,18 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
- var context = new InputFormatterContext(httpContext, modelState, typeof(JsonPatchDocument));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(JsonPatchDocument));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- var patchDoc = Assert.IsType>(model);
+ Assert.False(result.HasError);
+ var patchDoc = Assert.IsType>(result.Model);
Assert.Equal("add", patchDoc.Operations[0].op);
Assert.Equal("Customer/Name", patchDoc.Operations[0].path);
Assert.Equal("John", patchDoc.Operations[0].value);
@@ -78,8 +88,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var formatterContext = new InputFormatterContext(
httpContext,
- modelState,
- typeof(JsonPatchDocument));
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(JsonPatchDocument));
// Act
var result = formatter.CanRead(formatterContext);
@@ -100,7 +111,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
- var formatterContext = new InputFormatterContext(httpContext, modelState, modelType);
+ var formatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: modelType);
// Act
var result = formatter.CanRead(formatterContext);
@@ -122,13 +137,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
-
- var context = new InputFormatterContext(httpContext, modelState, typeof(Customer));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(Customer));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
+ Assert.True(result.HasError);
Assert.Contains(exceptionMessage, modelState[""].Errors[0].Exception.Message);
}
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
index e552511514..c103b7be73 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
@@ -73,7 +73,11 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
- var formatterContext = new InputFormatterContext(httpContext, modelState, typeof(string));
+ var formatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(string));
// Act
var result = formatter.CanRead(formatterContext);
@@ -146,15 +150,15 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelOneModel = model as TestLevelOne;
- Assert.Equal(expectedInt, levelOneModel.SampleInt);
- Assert.Equal(expectedString, levelOneModel.sampleString);
+ Assert.Equal(expectedInt, model.SampleInt);
+ Assert.Equal(expectedString, model.sampleString);
}
[ConditionalFact]
@@ -177,16 +181,16 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelTwoModel = model as TestLevelTwo;
- Assert.Equal(expectedLevelTwoString, levelTwoModel.SampleString);
- Assert.Equal(expectedInt, levelTwoModel.TestOne.SampleInt);
- Assert.Equal(expectedString, levelTwoModel.TestOne.sampleString);
+ Assert.Equal(expectedLevelTwoString, model.SampleString);
+ Assert.Equal(expectedInt, model.TestOne.SampleInt);
+ Assert.Equal(expectedString, model.TestOne.sampleString);
}
[ConditionalFact]
@@ -206,13 +210,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
- var dummyModel = model as DummyClass;
- Assert.Equal(expectedInt, dummyModel.SampleInt);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
+ Assert.Equal(expectedInt, model.SampleInt);
}
[ConditionalFact]
@@ -276,10 +280,12 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ Assert.NotNull(result.Model);
Assert.True(context.HttpContext.Request.Body.CanRead);
}
@@ -331,7 +337,11 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16");
- var context = new InputFormatterContext(httpContext, modelState, typeof(TestLevelOne));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(TestLevelOne));
// Act
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
@@ -361,14 +371,15 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- var levelTwoModel = model as TestLevelTwo;
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
Buffer.BlockCopy(sampleStringBytes, 0, expectedBytes, 0, sampleStringBytes.Length);
Buffer.BlockCopy(bom, 0, expectedBytes, sampleStringBytes.Length, bom.Length);
- Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(levelTwoModel.SampleString));
+ Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(model.SampleString));
}
[ConditionalFact]
@@ -390,18 +401,22 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/xml; charset=utf-16");
- var context = new InputFormatterContext(httpContext, modelState, typeof(TestLevelOne));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(TestLevelOne));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelOneModel = model as TestLevelOne;
- Assert.Equal(expectedInt, levelOneModel.SampleInt);
- Assert.Equal(expectedString, levelOneModel.sampleString);
+ Assert.Equal(expectedInt, model.SampleInt);
+ Assert.Equal(expectedString, model.sampleString);
}
[ConditionalFact]
@@ -455,12 +470,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- var dummyModel = Assert.IsType(model);
- Assert.Equal(expectedInt, dummyModel.SampleInt);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
+ Assert.Equal(expectedInt, model.SampleInt);
}
[ConditionalFact]
@@ -515,19 +531,24 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- var dummyModel = Assert.IsType(model);
- Assert.Equal(expectedInt, dummyModel.SampleInt);
- Assert.Equal(expectedString, dummyModel.SampleString);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
+ Assert.Equal(expectedInt, model.SampleInt);
+ Assert.Equal(expectedString, model.SampleString);
}
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
{
var httpContext = GetHttpContext(contentBytes);
- return new InputFormatterContext(httpContext, new ModelStateDictionary(), modelType);
+ return new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ modelType: modelType);
}
private static HttpContext GetHttpContext(
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
index c1b6ae1646..36a6056873 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
@@ -59,7 +59,11 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
- var formatterContext = new InputFormatterContext(httpContext, modelState, typeof(string));
+ var formatterContext = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(string));
// Act
var result = formatter.CanRead(formatterContext);
@@ -148,17 +152,18 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelOneModel = model as TestLevelOne;
- Assert.Equal(expectedInt, levelOneModel.SampleInt);
- Assert.Equal(expectedString, levelOneModel.sampleString);
- Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc),
- levelOneModel.SampleDate);
+ Assert.Equal(expectedInt, model.SampleInt);
+ Assert.Equal(expectedString, model.sampleString);
+ Assert.Equal(
+ XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc),
+ model.SampleDate);
}
[Fact]
@@ -181,18 +186,19 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelTwoModel = model as TestLevelTwo;
- Assert.Equal(expectedLevelTwoString, levelTwoModel.SampleString);
- Assert.Equal(expectedInt, levelTwoModel.TestOne.SampleInt);
- Assert.Equal(expectedString, levelTwoModel.TestOne.sampleString);
- Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc),
- levelTwoModel.TestOne.SampleDate);
+ Assert.Equal(expectedLevelTwoString, model.SampleString);
+ Assert.Equal(expectedInt, model.TestOne.SampleInt);
+ Assert.Equal(expectedString, model.TestOne.sampleString);
+ Assert.Equal(
+ XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc),
+ model.TestOne.SampleDate);
}
[Fact]
@@ -208,15 +214,14 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var contentBytes = Encoding.UTF8.GetBytes(input);
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
-
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
- var dummyModel = model as DummyClass;
- Assert.Equal(expectedInt, dummyModel.SampleInt);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
+ Assert.Equal(expectedInt, model.SampleInt);
}
[ConditionalFact]
@@ -282,10 +287,12 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ Assert.NotNull(result.Model);
Assert.True(context.HttpContext.Request.Body.CanRead);
}
@@ -335,7 +342,11 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16");
- var context = new InputFormatterContext(httpContext, modelState, typeof(TestLevelOne));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(TestLevelOne));
// Act and Assert
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
@@ -363,14 +374,15 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- var levelTwoModel = model as TestLevelTwo;
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
Buffer.BlockCopy(sampleStringBytes, 0, expectedBytes, 0, sampleStringBytes.Length);
Buffer.BlockCopy(bom, 0, expectedBytes, sampleStringBytes.Length, bom.Length);
- Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(levelTwoModel.SampleString));
+ Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(model.SampleString));
}
[Fact]
@@ -391,25 +403,33 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/xml; charset=utf-16");
- var context = new InputFormatterContext(httpContext, modelState, typeof(TestLevelOne));
+ var context = new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: modelState,
+ modelType: typeof(TestLevelOne));
// Act
- var model = await formatter.ReadAsync(context);
+ var result = await formatter.ReadAsync(context);
// Assert
- Assert.NotNull(model);
- Assert.IsType(model);
+ Assert.NotNull(result);
+ Assert.False(result.HasError);
+ var model = Assert.IsType(result.Model);
- var levelOneModel = model as TestLevelOne;
- Assert.Equal(expectedInt, levelOneModel.SampleInt);
- Assert.Equal(expectedString, levelOneModel.sampleString);
- Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), levelOneModel.SampleDate);
+ Assert.Equal(expectedInt, model.SampleInt);
+ Assert.Equal(expectedString, model.sampleString);
+ Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), model.SampleDate);
}
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
{
var httpContext = GetHttpContext(contentBytes);
- return new InputFormatterContext(httpContext, new ModelStateDictionary(), modelType);
+ return new InputFormatterContext(
+ httpContext,
+ modelName: string.Empty,
+ modelState: new ModelStateDictionary(),
+ modelType: modelType);
}
private static HttpContext GetHttpContext(
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
index 58d9df09e5..25d7003540 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs
@@ -7,7 +7,6 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Mvc.ModelBinding;
-using Microsoft.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
@@ -111,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public int Address { get; set; }
}
- [Fact(Skip = "#2722 validation error from formatter is recorded with the wrong key.")]
+ [Fact]
public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_JsonFormatterAddsModelStateError()
{
// Arrange
@@ -146,7 +145,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var entry = Assert.Single(modelState);
Assert.Equal("CustomParameter.Address", entry.Key);
Assert.Null(entry.Value.AttemptedValue);
- Assert.Same(boundPerson, entry.Value.RawValue);
+ Assert.Null(entry.Value.RawValue);
var error = Assert.Single(entry.Value.Errors);
Assert.NotNull(error.Exception);
@@ -155,6 +154,118 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotEmpty(error.Exception.Message);
}
+ private class Person5
+ {
+ [FromBody]
+ public Address5 Address { get; set; }
+ }
+
+ private class Address5
+ {
+ public int Number { get; set; }
+
+ // Required attribute does not cause an error in test scenarios. JSON deserializer ok w/ missing data.
+ [Required]
+ public int RequiredNumber { get; set; }
+ }
+
+ [Fact]
+ public async Task FromBodyAndRequiredOnInnerValueTypeProperty_NotBound_JsonFormatterSuccessful()
+ {
+ // Arrange
+ var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
+ var parameter = new ParameterDescriptor
+ {
+ Name = "Parameter1",
+ BindingInfo = new BindingInfo
+ {
+ BinderModelName = "CustomParameter",
+ },
+ ParameterType = typeof(Person5)
+ };
+
+ var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
+ request =>
+ {
+ request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Number\": 5 }"));
+ request.ContentType = "application/json";
+ });
+
+ var modelState = new ModelStateDictionary();
+
+ // Act
+ var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
+
+ // Assert
+ Assert.True(modelBindingResult.IsModelSet);
+ var boundPerson = Assert.IsType(modelBindingResult.Model);
+ Assert.NotNull(boundPerson.Address);
+ Assert.Equal(5, boundPerson.Address.Number);
+ Assert.Equal(0, boundPerson.Address.RequiredNumber);
+
+ Assert.True(modelState.IsValid);
+ var entry = Assert.Single(modelState);
+ Assert.Equal("CustomParameter.Address", entry.Key);
+ Assert.NotNull(entry.Value);
+ Assert.Null(entry.Value.AttemptedValue);
+ Assert.Same(boundPerson.Address, entry.Value.RawValue);
+ Assert.Empty(entry.Value.Errors);
+ }
+
+ [Fact]
+ public async Task FromBodyWithInvalidPropertyData_JsonFormatterAddsModelError()
+ {
+ // Arrange
+ var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
+ var parameter = new ParameterDescriptor
+ {
+ Name = "Parameter1",
+ BindingInfo = new BindingInfo
+ {
+ BinderModelName = "CustomParameter",
+ },
+ ParameterType = typeof(Person5)
+ };
+
+ var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
+ request =>
+ {
+ request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Number\": \"not a number\" }"));
+ request.ContentType = "application/json";
+ });
+
+ var modelState = new ModelStateDictionary();
+
+ // Act
+ var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
+
+ // Assert
+ Assert.True(modelBindingResult.IsModelSet);
+ var boundPerson = Assert.IsType(modelBindingResult.Model);
+ Assert.Null(boundPerson.Address);
+
+ Assert.False(modelState.IsValid);
+ Assert.Equal(2, modelState.Count);
+ Assert.Equal(1, modelState.ErrorCount);
+
+ var state = modelState["CustomParameter.Address"];
+ Assert.NotNull(state);
+ Assert.Null(state.AttemptedValue);
+ Assert.Null(state.RawValue);
+ Assert.Empty(state.Errors);
+
+ state = modelState["CustomParameter.Address.Number"];
+ Assert.NotNull(state);
+ Assert.Null(state.AttemptedValue);
+ Assert.Null(state.RawValue);
+ var error = Assert.Single(state.Errors);
+ Assert.NotNull(error.Exception);
+
+ // Json.NET currently throws an Exception with a Message starting with "Could not convert string to
+ // integer: not a number." but do not tie test to a particular Json.NET build.
+ Assert.NotEmpty(error.Exception.Message);
+ }
+
private class Person2
{
[FromBody]
diff --git a/test/WebSites/FormatterWebSite/StringInputFormatter.cs b/test/WebSites/FormatterWebSite/StringInputFormatter.cs
index 88f76cc9f7..54a14eda22 100644
--- a/test/WebSites/FormatterWebSite/StringInputFormatter.cs
+++ b/test/WebSites/FormatterWebSite/StringInputFormatter.cs
@@ -4,6 +4,7 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
+using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@@ -19,17 +20,19 @@ namespace FormatterWebSite
SupportedEncodings.Add(Encoding.Unicode);
}
- public override Task ReadRequestBodyAsync(InputFormatterContext context)
+ public override Task ReadRequestBodyAsync(InputFormatterContext context)
{
- var request = context.HttpContext.Request;
- MediaTypeHeaderValue requestContentType = null;
- MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
- var effectiveEncoding = SelectCharacterEncoding(requestContentType);
+ var effectiveEncoding = SelectCharacterEncoding(context);
+ if (effectiveEncoding == null)
+ {
+ return InputFormatterResult.FailureAsync();
+ }
+ var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body, effectiveEncoding))
{
var stringContent = reader.ReadToEnd();
- return Task.FromResult(stringContent);
+ return InputFormatterResult.SuccessAsync(stringContent);
}
}
}