Add `InputFormatterResult` and `InputFormatterContext.ModelName`
- #2722 - make communication of errors from formatters to `BodyModelBinder` explicit - `JsonInputFormatter` now adds errors to `ModelStateDictionary` with correct key - change `InputFormatter.SelectCharacterEncoding()` to add an error and return `null` when it fails - one less `Exception` case and removes some duplicate code nits: - improve some doc comments (more `<inheritdoc/>`, `<paramref/>` and `<see/>`) - add another two `BodyValidationIntegrationTests` tests
This commit is contained in:
parent
0476d53f1d
commit
42017faa21
|
|
@ -11,20 +11,21 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
public interface IInputFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether this <see cref="IInputFormatter"/> can de-serialize
|
||||
/// an object of the specified type.
|
||||
/// Determines whether this <see cref="IInputFormatter"/> can deserialize an object of the
|
||||
/// <paramref name="context"/>'s <see cref="InputFormatterContext.ModelType"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Input formatter context associated with this call.</param>
|
||||
/// <returns>True if this <see cref="IInputFormatter"/> supports the passed in
|
||||
/// request's content-type and is able to de-serialize the request body.
|
||||
/// False otherwise.</returns>
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this <see cref="IInputFormatter"/> can deserialize an object of the
|
||||
/// <paramref name="context"/>'s <see cref="InputFormatterContext.ModelType"/>. <c>false</c> otherwise.
|
||||
/// </returns>
|
||||
bool CanRead(InputFormatterContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to read an object from the request.
|
||||
/// Reads an object from the request body.
|
||||
/// </summary>
|
||||
/// <param name="context">Input formatter context associated with this call.</param>
|
||||
/// <returns>A task that deserializes the request body.</returns>
|
||||
Task<object> ReadAsync(InputFormatterContext context);
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns>
|
||||
Task<InputFormatterResult> ReadAsync(InputFormatterContext context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
/// <param name="httpContext">
|
||||
/// The <see cref="Http.HttpContext"/> for the current operation.
|
||||
/// </param>
|
||||
/// <param name="modelName">The name of the model.</param>
|
||||
/// <param name="modelState">
|
||||
/// The <see cref="ModelStateDictionary"/> for recording errors.
|
||||
/// </param>
|
||||
|
|
@ -27,10 +28,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
/// </param>
|
||||
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
|
|||
/// <summary>
|
||||
/// Gets the <see cref="Http.HttpContext"/> associated with the current operation.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the model. Used as the key or key prefix for errors added to <see cref="ModelState"/>.
|
||||
/// </summary>
|
||||
public string ModelName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelStateDictionary"/> associated with the current operation.
|
||||
|
|
@ -48,6 +56,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
/// <summary>
|
||||
/// Gets the expected <see cref="Type"/> of the model represented by the request body.
|
||||
/// </summary>
|
||||
public Type ModelType { get; private set; }
|
||||
public Type ModelType { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a <see cref="IInputFormatter.ReadAsync"/> operation.
|
||||
/// </summary>
|
||||
public class InputFormatterResult
|
||||
{
|
||||
private static readonly InputFormatterResult _failure = new InputFormatterResult();
|
||||
private static readonly Task<InputFormatterResult> _failureAsync = Task.FromResult(_failure);
|
||||
|
||||
private InputFormatterResult()
|
||||
{
|
||||
HasError = true;
|
||||
}
|
||||
|
||||
private InputFormatterResult(object model)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an indication whether the <see cref="IInputFormatter.ReadAsync"/> operation had an error.
|
||||
/// </summary>
|
||||
public bool HasError { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the deserialized <see cref="object"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>null</c> if <see cref="HasError"/> is <c>true</c>.
|
||||
/// </value>
|
||||
public object Model { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="InputFormatterResult"/> indicating the <see cref="IInputFormatter.ReadAsync"/>
|
||||
/// operation failed.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="InputFormatterResult"/> indicating the <see cref="IInputFormatter.ReadAsync"/>
|
||||
/// operation failed i.e. with <see cref="HasError"/> <c>true</c>.
|
||||
/// </returns>
|
||||
public static InputFormatterResult Failure()
|
||||
{
|
||||
return _failure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task"/> that on completion provides an <see cref="InputFormatterResult"/> indicating
|
||||
/// the <see cref="IInputFormatter.ReadAsync"/> operation failed.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Task"/> that on completion provides an <see cref="InputFormatterResult"/> indicating the
|
||||
/// <see cref="IInputFormatter.ReadAsync"/> operation failed i.e. with <see cref="HasError"/> <c>true</c>.
|
||||
/// </returns>
|
||||
public static Task<InputFormatterResult> FailureAsync()
|
||||
{
|
||||
return _failureAsync;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="InputFormatterResult"/> indicating the <see cref="IInputFormatter.ReadAsync"/>
|
||||
/// operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="model">The deserialized <see cref="object"/>.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="InputFormatterResult"/> indicating the <see cref="IInputFormatter.ReadAsync"/>
|
||||
/// operation succeeded i.e. with <see cref="HasError"/> <c>false</c>.
|
||||
/// </returns>
|
||||
public static InputFormatterResult Success(object model)
|
||||
{
|
||||
return new InputFormatterResult(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task"/> that on completion provides an <see cref="InputFormatterResult"/> indicating
|
||||
/// the <see cref="IInputFormatter.ReadAsync"/> operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="model">The deserialized <see cref="object"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task"/> that on completion provides an <see cref="InputFormatterResult"/> indicating the
|
||||
/// <see cref="IInputFormatter.ReadAsync"/> operation succeeded i.e. with <see cref="HasError"/> <c>false</c>.
|
||||
/// </returns>
|
||||
public static Task<InputFormatterResult> SuccessAsync(object model)
|
||||
{
|
||||
return Task.FromResult(Success(model));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether or not the given type can be read by this serializer.
|
||||
/// Determines whether this <see cref="InputFormatter"/> can deserialize an object of the given
|
||||
/// <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of object that will be read.</param>
|
||||
/// <returns><c>true</c> if the type can be read, otherwise <c>false</c>.</returns>
|
||||
/// <param name="type">The <see cref="Type"/> of object that will be read.</param>
|
||||
/// <returns><c>true</c> if the <paramref name="type"/> can be read, otherwise <c>false</c>.</returns>
|
||||
protected virtual bool CanReadType(Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task<object> ReadAsync(InputFormatterContext context)
|
||||
public virtual Task<InputFormatterResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the request body.
|
||||
/// Reads an object from the request body.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/> associated with the call.</param>
|
||||
/// <returns>A task which can read the request body.</returns>
|
||||
public abstract Task<object> ReadRequestBodyAsync(InputFormatterContext context);
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns>
|
||||
public abstract Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Returns encoding based on content type charset parameter.
|
||||
/// Returns an <see cref="Encoding"/> based on <paramref name="context"/>'s
|
||||
/// <see cref="MediaTypeHeaderValue.Charset"/>.
|
||||
/// </summary>
|
||||
protected Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentType)
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/>.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="Encoding"/> based on <paramref name="context"/>'s
|
||||
/// <see cref="MediaTypeHeaderValue.Charset"/>. <c>null</c> if no supported encoding was found.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,48 +49,74 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
public override Task<InputFormatterResult> 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<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
||||
errorHandler = (sender, e) =>
|
||||
var successful = true;
|
||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,15 +27,19 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
public async override Task<InputFormatterResult> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -86,19 +86,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the input XML.
|
||||
/// </summary>
|
||||
/// <param name="context">The input formatter context which contains the body to be read.</param>
|
||||
/// <returns>Task which reads the input.</returns>
|
||||
public override Task<object> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
/// <inheritdoc />
|
||||
public override Task<InputFormatterResult> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,19 +65,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
get { return _readerQuotas; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the input XML.
|
||||
/// </summary>
|
||||
/// <param name="context">The input formatter context which contains the body to be read.</param>
|
||||
/// <returns>Task which reads the input.</returns>
|
||||
public override Task<object> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
/// <inheritdoc />
|
||||
public override Task<InputFormatterResult> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
.Returns(true)
|
||||
.Verifiable();
|
||||
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
|
||||
.Returns(Task.FromResult<object>(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<object> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
{
|
||||
throw new InvalidOperationException("Your input is bad!");
|
||||
}
|
||||
|
|
@ -325,9 +325,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return _canRead;
|
||||
}
|
||||
|
||||
public Task<object> ReadAsync(InputFormatterContext context)
|
||||
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
|
||||
{
|
||||
return Task.FromResult<object>(this);
|
||||
return InputFormatterResult.SuccessAsync(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<User>(model);
|
||||
Assert.False(result.HasError);
|
||||
var userModel = Assert.IsType<User>(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<TooManyModelErrorsException>(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;
|
||||
|
|
|
|||
|
|
@ -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<Customer>));
|
||||
var context = new InputFormatterContext(
|
||||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
modelType: typeof(JsonPatchDocument<Customer>));
|
||||
|
||||
// Act
|
||||
var model = await formatter.ReadAsync(context);
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(model);
|
||||
Assert.False(result.HasError);
|
||||
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(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<Customer>));
|
||||
var context = new InputFormatterContext(
|
||||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
modelType: typeof(JsonPatchDocument<Customer>));
|
||||
|
||||
// Act
|
||||
var model = await formatter.ReadAsync(context);
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(model);
|
||||
Assert.False(result.HasError);
|
||||
var patchDoc = Assert.IsType<JsonPatchDocument<Customer>>(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<Customer>));
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
modelType: typeof(JsonPatchDocument<Customer>));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<TestLevelOne>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(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<TestLevelTwo>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelTwo>(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<DummyClass>(model);
|
||||
var dummyModel = model as DummyClass;
|
||||
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<DummyClass>(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<TestLevelTwo>(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<TestLevelOne>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(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<DummyClass>(model);
|
||||
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<DummyClass>(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<SomeDummyClass>(model);
|
||||
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
||||
Assert.Equal(expectedString, dummyModel.SampleString);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<SomeDummyClass>(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(
|
||||
|
|
|
|||
|
|
@ -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<TestLevelOne>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(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<TestLevelTwo>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelTwo>(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<DummyClass>(model);
|
||||
var dummyModel = model as DummyClass;
|
||||
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<DummyClass>(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<TestLevelTwo>(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<TestLevelOne>(model);
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.HasError);
|
||||
var model = Assert.IsType<TestLevelOne>(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(
|
||||
|
|
|
|||
|
|
@ -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<Person5>(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<Person5>(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]
|
||||
|
|
|
|||
|
|
@ -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<object> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
public override Task<InputFormatterResult> 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<object>(stringContent);
|
||||
return InputFormatterResult.SuccessAsync(stringContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue