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