Adding JsonInputFormatter for reading json encoded data from the request
body
This commit is contained in:
parent
4364986137
commit
683c5bf9b3
|
|
@ -1,39 +0,0 @@
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Internal
|
|
||||||
{
|
|
||||||
public static class ActionBindingContextExtensions
|
|
||||||
{
|
|
||||||
public static InputFormatterContext CreateInputFormatterContext(this ActionBindingContext actionBindingContext,
|
|
||||||
ModelStateDictionary modelState,
|
|
||||||
ParameterDescriptor parameter)
|
|
||||||
{
|
|
||||||
var metadataProvider = actionBindingContext.MetadataProvider;
|
|
||||||
var parameterType = parameter.BodyParameterInfo.ParameterType;
|
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType);
|
|
||||||
return new InputFormatterContext(modelMetadata, modelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ModelBindingContext CreateModelBindingContext(this ActionBindingContext actionBindingContext,
|
|
||||||
ModelStateDictionary modelState,
|
|
||||||
ParameterDescriptor parameter)
|
|
||||||
{
|
|
||||||
var metadataProvider = actionBindingContext.MetadataProvider;
|
|
||||||
var parameterType = parameter.ParameterBindingInfo.ParameterType;
|
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType);
|
|
||||||
|
|
||||||
return new ModelBindingContext
|
|
||||||
{
|
|
||||||
ModelName = parameter.Name,
|
|
||||||
ModelState = modelState,
|
|
||||||
ModelMetadata = modelMetadata,
|
|
||||||
ModelBinder = actionBindingContext.ModelBinder,
|
|
||||||
ValueProvider = actionBindingContext.ValueProvider,
|
|
||||||
ValidatorProviders = actionBindingContext.ValidatorProviders,
|
|
||||||
MetadataProvider = metadataProvider,
|
|
||||||
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
|
||||||
FallbackToEmptyPrefix = true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,14 +9,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinder modelBinder,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IInputFormatter inputFormatter,
|
IInputFormatterProvider inputFormatterProvider,
|
||||||
IEnumerable<IModelValidatorProvider> validatorProviders)
|
IEnumerable<IModelValidatorProvider> validatorProviders)
|
||||||
{
|
{
|
||||||
ActionContext = context;
|
ActionContext = context;
|
||||||
MetadataProvider = metadataProvider;
|
MetadataProvider = metadataProvider;
|
||||||
ModelBinder = modelBinder;
|
ModelBinder = modelBinder;
|
||||||
ValueProvider = valueProvider;
|
ValueProvider = valueProvider;
|
||||||
InputFormatter = inputFormatter;
|
InputFormatterProvider = inputFormatterProvider;
|
||||||
ValidatorProviders = validatorProviders;
|
ValidatorProviders = validatorProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public IValueProvider ValueProvider { get; private set; }
|
public IValueProvider ValueProvider { get; private set; }
|
||||||
|
|
||||||
public IInputFormatter InputFormatter { get; private set; }
|
public IInputFormatterProvider InputFormatterProvider { get; private set; }
|
||||||
|
|
||||||
public IEnumerable<IModelValidatorProvider> ValidatorProviders { get; private set; }
|
public IEnumerable<IModelValidatorProvider> ValidatorProviders { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,19 @@ namespace Microsoft.AspNet.Mvc
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly IEnumerable<IModelBinder> _modelBinders;
|
private readonly IEnumerable<IModelBinder> _modelBinders;
|
||||||
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactories;
|
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactories;
|
||||||
private readonly IEnumerable<IInputFormatter> _inputFormatters;
|
private readonly IInputFormatterProvider _inputFormatterProvider;
|
||||||
private readonly IEnumerable<IModelValidatorProvider> _validatorProviders;
|
private readonly IEnumerable<IModelValidatorProvider> _validatorProviders;
|
||||||
|
|
||||||
public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
|
public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
|
||||||
IEnumerable<IModelBinder> modelBinders,
|
IEnumerable<IModelBinder> modelBinders,
|
||||||
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
||||||
IEnumerable<IInputFormatter> inputFormatters,
|
IInputFormatterProvider inputFormatterProvider,
|
||||||
IEnumerable<IModelValidatorProvider> validatorProviders)
|
IEnumerable<IModelValidatorProvider> validatorProviders)
|
||||||
{
|
{
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
_modelBinders = modelBinders.OrderBy(binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0);
|
_modelBinders = modelBinders.OrderBy(binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0);
|
||||||
_valueProviderFactories = valueProviderFactories;
|
_valueProviderFactories = valueProviderFactories;
|
||||||
_inputFormatters = inputFormatters;
|
_inputFormatterProvider = inputFormatterProvider;
|
||||||
_validatorProviders = validatorProviders;
|
_validatorProviders = validatorProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
_modelMetadataProvider,
|
_modelMetadataProvider,
|
||||||
new CompositeModelBinder(_modelBinders),
|
new CompositeModelBinder(_modelBinders),
|
||||||
new CompositeValueProvider(valueProviders),
|
new CompositeValueProvider(valueProviders),
|
||||||
new CompositeInputFormatter(_inputFormatters),
|
_inputFormatterProvider,
|
||||||
_validatorProviders
|
_validatorProviders
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.DependencyInjection;
|
using Microsoft.AspNet.DependencyInjection;
|
||||||
using Microsoft.AspNet.Mvc.Filters;
|
using Microsoft.AspNet.Mvc.Filters;
|
||||||
using Microsoft.AspNet.Mvc.Internal;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
|
@ -129,24 +128,46 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(_actionContext);
|
var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(_actionContext);
|
||||||
var parameters = _descriptor.Parameters;
|
var parameters = _descriptor.Parameters;
|
||||||
|
var metadataProvider = actionBindingContext.MetadataProvider;
|
||||||
var parameterValues = new Dictionary<string, object>(parameters.Count, StringComparer.Ordinal);
|
var parameterValues = new Dictionary<string, object>(parameters.Count, StringComparer.Ordinal);
|
||||||
for (int i = 0; i < parameters.Count; i++)
|
|
||||||
|
for (var i = 0; i < parameters.Count; i++)
|
||||||
{
|
{
|
||||||
var parameter = parameters[i];
|
var parameter = parameters[i];
|
||||||
if (parameter.BodyParameterInfo != null)
|
if (parameter.BodyParameterInfo != null)
|
||||||
{
|
{
|
||||||
var inputFormatterContext = actionBindingContext.CreateInputFormatterContext(
|
var parameterType = parameter.BodyParameterInfo.ParameterType;
|
||||||
modelState,
|
var modelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType);
|
||||||
parameter);
|
var providerContext = new InputFormatterProviderContext(actionBindingContext.ActionContext.HttpContext,
|
||||||
await actionBindingContext.InputFormatter.ReadAsync(inputFormatterContext);
|
modelMetadata,
|
||||||
parameterValues[parameter.Name] = inputFormatterContext.Model;
|
modelState);
|
||||||
|
|
||||||
|
var inputFormatter = actionBindingContext.InputFormatterProvider.GetInputFormatter(providerContext);
|
||||||
|
|
||||||
|
|
||||||
|
var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext.HttpContext,
|
||||||
|
modelMetadata,
|
||||||
|
modelState);
|
||||||
|
await inputFormatter.ReadAsync(formatterContext);
|
||||||
|
parameterValues[parameter.Name] = formatterContext.Model;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var modelBindingContext = actionBindingContext.CreateModelBindingContext(
|
var parameterType = parameter.ParameterBindingInfo.ParameterType;
|
||||||
modelState,
|
var modelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType);
|
||||||
parameter);
|
|
||||||
|
var modelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelName = parameter.Name,
|
||||||
|
ModelState = modelState,
|
||||||
|
ModelMetadata = modelMetadata,
|
||||||
|
ModelBinder = actionBindingContext.ModelBinder,
|
||||||
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
ValidatorProviders = actionBindingContext.ValidatorProviders,
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
||||||
|
FallbackToEmptyPrefix = true
|
||||||
|
};
|
||||||
actionBindingContext.ModelBinder.BindModel(modelBindingContext);
|
actionBindingContext.ModelBinder.BindModel(modelBindingContext);
|
||||||
parameterValues[parameter.Name] = modelBindingContext.Model;
|
parameterValues[parameter.Name] = modelBindingContext.Model;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
||||||
{
|
|
||||||
public class CompositeInputFormatter : IInputFormatter
|
|
||||||
{
|
|
||||||
private IInputFormatter[] _bodyReaders;
|
|
||||||
|
|
||||||
public CompositeInputFormatter(IEnumerable<IInputFormatter> bodyReaders)
|
|
||||||
{
|
|
||||||
_bodyReaders = bodyReaders.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ReadAsync(InputFormatterContext context)
|
|
||||||
{
|
|
||||||
for(int i = 0; i < _bodyReaders.Length; i++)
|
|
||||||
{
|
|
||||||
if (await _bodyReaders[i].ReadAsync(context))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public interface IInputFormatter
|
public interface IInputFormatter
|
||||||
{
|
{
|
||||||
Task<bool> ReadAsync(InputFormatterContext context);
|
/// <summary>
|
||||||
|
/// Gets the mutable collection of media types supported by this <see cref="JsonInputFormatter"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
IList<string> SupportedMediaTypes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mutable collection of character encodings supported by this <see cref="JsonInputFormatter"/>
|
||||||
|
/// instance.
|
||||||
|
/// </summary>
|
||||||
|
IList<Encoding> SupportedEncodings { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called during deserialization to read an object from the request.
|
||||||
|
/// </summary>
|
||||||
|
Task ReadAsync(InputFormatterContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public interface IInputFormatterProvider
|
||||||
|
{
|
||||||
|
IInputFormatter GetInputFormatter(InputFormatterProviderContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class InputFormatterContext
|
public class InputFormatterContext
|
||||||
{
|
{
|
||||||
public InputFormatterContext(ModelMetadata metadata, ModelStateDictionary modelState)
|
public InputFormatterContext([NotNull] HttpContext httpContext,
|
||||||
|
[NotNull] ModelMetadata metadata,
|
||||||
|
[NotNull] ModelStateDictionary modelState)
|
||||||
{
|
{
|
||||||
|
HttpContext = httpContext;
|
||||||
Metadata = metadata;
|
Metadata = metadata;
|
||||||
ModelState = modelState;
|
ModelState = modelState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
|
||||||
public ModelMetadata Metadata { get; private set; }
|
public ModelMetadata Metadata { get; private set; }
|
||||||
|
|
||||||
public ModelStateDictionary ModelState { get; private set; }
|
public ModelStateDictionary ModelState { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class InputFormatterProviderContext
|
||||||
|
{
|
||||||
|
public InputFormatterProviderContext([NotNull] HttpContext httpContext,
|
||||||
|
[NotNull] ModelMetadata metadata,
|
||||||
|
[NotNull] ModelStateDictionary modelState)
|
||||||
|
{
|
||||||
|
HttpContext = httpContext;
|
||||||
|
Metadata = metadata;
|
||||||
|
ModelState = modelState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
|
||||||
|
public ModelMetadata Metadata { get; private set; }
|
||||||
|
|
||||||
|
public ModelStateDictionary ModelState { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,201 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class JsonInputFormatter : IInputFormatter
|
public class JsonInputFormatter : IInputFormatter
|
||||||
{
|
{
|
||||||
public Task<bool> ReadAsync(InputFormatterContext bindingContext)
|
private const int DefaultMaxDepth = 32;
|
||||||
|
private readonly List<Encoding> _supportedEncodings;
|
||||||
|
private readonly List<string> _supportedMediaTypes;
|
||||||
|
private JsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
|
||||||
|
public JsonInputFormatter()
|
||||||
{
|
{
|
||||||
return Task.FromResult(false);
|
_supportedMediaTypes = new List<string>
|
||||||
|
{
|
||||||
|
"application/json",
|
||||||
|
"text/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
_supportedEncodings = new List<Encoding>
|
||||||
|
{
|
||||||
|
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),
|
||||||
|
new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true)
|
||||||
|
};
|
||||||
|
|
||||||
|
_jsonSerializerSettings = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||||
|
|
||||||
|
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
|
||||||
|
// from deserialization errors that might occur from deeply nested objects.
|
||||||
|
MaxDepth = DefaultMaxDepth,
|
||||||
|
|
||||||
|
// Do not change this setting
|
||||||
|
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
|
||||||
|
TypeNameHandling = TypeNameHandling.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IList<string> SupportedMediaTypes
|
||||||
|
{
|
||||||
|
get { return _supportedMediaTypes; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IList<Encoding> SupportedEncodings
|
||||||
|
{
|
||||||
|
get { return _supportedEncodings; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public JsonSerializerSettings SerializerSettings
|
||||||
|
{
|
||||||
|
get { return _jsonSerializerSettings; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
_jsonSerializerSettings = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if deserialization errors are captured. When set, these errors appear in
|
||||||
|
/// the <see cref="ModelStateDictionary"/> instance of <see cref="InputFormatterContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool CaptureDeserilizationErrors { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task ReadAsync([NotNull] InputFormatterContext context)
|
||||||
|
{
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
|
if (request.ContentLength == 0)
|
||||||
|
{
|
||||||
|
var modelType = context.Metadata.ModelType;
|
||||||
|
context.Model = modelType.GetTypeInfo().IsValueType ? Activator.CreateInstance(modelType) :
|
||||||
|
null;
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the character encoding for the content
|
||||||
|
// Never non-null since SelectCharacterEncoding() throws in error / not found scenarios
|
||||||
|
var effectiveEncoding = SelectCharacterEncoding(request.GetContentType());
|
||||||
|
|
||||||
|
context.Model = await ReadInternal(context, effectiveEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called during deserialization to get the <see cref="JsonReader"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
|
||||||
|
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
|
||||||
|
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
|
||||||
|
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
|
||||||
|
public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context,
|
||||||
|
[NotNull] Stream readStream,
|
||||||
|
[NotNull] Encoding effectiveEncoding)
|
||||||
|
{
|
||||||
|
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
// <summary>
|
||||||
|
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
|
||||||
|
public virtual JsonSerializer CreateJsonSerializer()
|
||||||
|
{
|
||||||
|
return JsonSerializer.Create(SerializerSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSupportedContentType(ContentTypeHeaderValue contentType)
|
||||||
|
{
|
||||||
|
return contentType != null &&
|
||||||
|
_supportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<object> ReadInternal(InputFormatterContext context,
|
||||||
|
Encoding effectiveEncoding)
|
||||||
|
{
|
||||||
|
var type = context.Metadata.ModelType;
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
|
|
||||||
|
using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
|
||||||
|
{
|
||||||
|
jsonReader.CloseInput = false;
|
||||||
|
|
||||||
|
var jsonSerializer = CreateJsonSerializer();
|
||||||
|
|
||||||
|
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
||||||
|
if (CaptureDeserilizationErrors)
|
||||||
|
{
|
||||||
|
errorHandler = (sender, e) =>
|
||||||
|
{
|
||||||
|
var exception = e.ErrorContext.Error;
|
||||||
|
context.ModelState.AddModelError(e.ErrorContext.Path, e.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;
|
||||||
|
};
|
||||||
|
jsonSerializer.Error += errorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Task.FromResult(jsonSerializer.Deserialize(jsonReader, type));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
|
||||||
|
if (errorHandler != null)
|
||||||
|
{
|
||||||
|
jsonSerializer.Error -= errorHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Encoding SelectCharacterEncoding(ContentTypeHeaderValue contentType)
|
||||||
|
{
|
||||||
|
if (contentType != null)
|
||||||
|
{
|
||||||
|
// Find encoding based on content type charset parameter
|
||||||
|
var charset = contentType.CharSet;
|
||||||
|
if (!string.IsNullOrWhiteSpace(contentType.CharSet))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _supportedEncodings.Count; i++)
|
||||||
|
{
|
||||||
|
var supportedEncoding = _supportedEncodings[i];
|
||||||
|
if (charset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return supportedEncoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_supportedEncodings.Count > 0)
|
||||||
|
{
|
||||||
|
return _supportedEncodings[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No supported encoding was found so there is no way for us to start reading.
|
||||||
|
throw new InvalidOperationException(Resources.FormatMediaTypeFormatterNoEncoding(GetType().FullName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class TempInputFormatterProvider : IInputFormatterProvider
|
||||||
|
{
|
||||||
|
private readonly IInputFormatter[] _formatters;
|
||||||
|
|
||||||
|
public TempInputFormatterProvider(IEnumerable<IInputFormatter> formatters)
|
||||||
|
{
|
||||||
|
_formatters = formatters.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IInputFormatter GetInputFormatter(InputFormatterProviderContext context)
|
||||||
|
{
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
|
var contentType = request.GetContentType();
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
// TODO: http exception?
|
||||||
|
throw new InvalidOperationException("400: Bad Request");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _formatters.Length; i++)
|
||||||
|
{
|
||||||
|
var formatter = _formatters[i];
|
||||||
|
if (formatter.SupportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return formatter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Http exception
|
||||||
|
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "415: Unsupported content type {0}", contentType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
|
{
|
||||||
|
public class ContentTypeHeaderValue
|
||||||
|
{
|
||||||
|
public ContentTypeHeaderValue([NotNull] string contentType,
|
||||||
|
string charSet)
|
||||||
|
{
|
||||||
|
ContentType = contentType;
|
||||||
|
CharSet = charSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContentType { get; private set; }
|
||||||
|
|
||||||
|
public string CharSet { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
|
{
|
||||||
|
public static class HttpRequestExtensions
|
||||||
|
{
|
||||||
|
private const string ContentTypeHeader = "Content-Type";
|
||||||
|
private const string CharSetToken = "charset=";
|
||||||
|
|
||||||
|
public static ContentTypeHeaderValue GetContentType(this HttpRequest httpRequest)
|
||||||
|
{
|
||||||
|
var headerValue = httpRequest.Headers[ContentTypeHeader];
|
||||||
|
if (!string.IsNullOrEmpty(headerValue))
|
||||||
|
{
|
||||||
|
var tokens = headerValue.Split(new[] { ';' }, 2);
|
||||||
|
string charSet = null;
|
||||||
|
if (tokens.Length > 1 && tokens[1].TrimStart().StartsWith(CharSetToken, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
charSet = tokens[1].TrimStart().Substring(CharSetToken.Length);
|
||||||
|
}
|
||||||
|
return new ContentTypeHeaderValue(tokens[0], charSet);
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,6 +59,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// No encoding found for media type formatter '{0}'. There must be at least one supported encoding registered in order for the media type formatter to read or write content.
|
||||||
|
/// </summary>
|
||||||
|
internal static string MediaTypeFormatterNoEncoding
|
||||||
|
{
|
||||||
|
get { return GetString("MediaTypeFormatterNoEncoding"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No encoding found for media type formatter '{0}'. There must be at least one supported encoding registered in order for the media type formatter to read or write content.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatMediaTypeFormatterNoEncoding(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("MediaTypeFormatterNoEncoding"), p0);
|
||||||
|
}
|
||||||
/// Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].
|
/// Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string MissingDataMemberIsRequired
|
internal static string MissingDataMemberIsRequired
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@
|
||||||
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
|
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
|
||||||
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
|
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MediaTypeFormatterNoEncoding" xml:space="preserve">
|
||||||
|
<value>No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content.</value>
|
||||||
|
</data>
|
||||||
<data name="MissingDataMemberIsRequired" xml:space="preserve">
|
<data name="MissingDataMemberIsRequired" xml:space="preserve">
|
||||||
<value>Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].</value>
|
<value>Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -45,54 +45,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return ConvertTo(type, culture: null);
|
return ConvertTo(type, culture: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual object ConvertTo(Type type, CultureInfo culture)
|
public virtual object ConvertTo([NotNull] Type type, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (type == null)
|
var value = RawValue;
|
||||||
{
|
|
||||||
throw Error.ArgumentNull("type");
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeInfo typeInfo = type.GetTypeInfo();
|
|
||||||
object value = RawValue;
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
// treat null route parameters as though they were the default value for the type
|
// treat null route parameters as though they were the default value for the type
|
||||||
return typeInfo.IsValueType ? Activator.CreateInstance(type) : null;
|
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) :
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.GetType().GetTypeInfo().IsAssignableFrom(typeInfo))
|
if (value.GetType().IsAssignableFrom(type))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
CultureInfo cultureToUse = culture ?? Culture;
|
var cultureToUse = culture ?? Culture;
|
||||||
return UnwrapPossibleArrayType(cultureToUse, value, type);
|
return UnwrapPossibleArrayType(cultureToUse, value, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object ConvertSimpleType(CultureInfo culture, object value, TypeInfo destinationType)
|
private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
|
||||||
{
|
{
|
||||||
if (value == null || value.GetType().GetTypeInfo().IsAssignableFrom(destinationType))
|
if (value == null || value.GetType().IsAssignableFrom(destinationType))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a user-input value but the user didn't type anything, return no value
|
// if this is a user-input value but the user didn't type anything, return no value
|
||||||
string valueAsString = value as string;
|
var valueAsString = value as string;
|
||||||
|
|
||||||
if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
|
if (valueAsString != null && string.IsNullOrWhiteSpace(valueAsString))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destinationType == typeof(int).GetTypeInfo())
|
if (destinationType == typeof(int))
|
||||||
{
|
{
|
||||||
return Convert.ToInt32(value);
|
return Convert.ToInt32(value);
|
||||||
}
|
}
|
||||||
else if (destinationType == typeof(bool).GetTypeInfo())
|
else if (destinationType == typeof(bool))
|
||||||
{
|
{
|
||||||
return Boolean.Parse(value.ToString());
|
return Boolean.Parse(value.ToString());
|
||||||
}
|
}
|
||||||
else if (destinationType == typeof(string).GetTypeInfo())
|
else if (destinationType == typeof(string))
|
||||||
{
|
{
|
||||||
return Convert.ToString(value);
|
return Convert.ToString(value);
|
||||||
}
|
}
|
||||||
|
|
@ -139,25 +134,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
|
private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
|
||||||
{
|
{
|
||||||
// array conversion results in four cases, as below
|
// array conversion results in four cases, as below
|
||||||
Array valueAsArray = value as Array;
|
var valueAsArray = value as Array;
|
||||||
if (destinationType.IsArray)
|
if (destinationType.IsArray)
|
||||||
{
|
{
|
||||||
Type destinationElementType = destinationType.GetElementType();
|
var destinationElementType = destinationType.GetElementType();
|
||||||
TypeInfo destElementTypeInfo = destinationElementType.GetTypeInfo();
|
|
||||||
if (valueAsArray != null)
|
if (valueAsArray != null)
|
||||||
{
|
{
|
||||||
// case 1: both destination + source type are arrays, so convert each element
|
// case 1: both destination + source type are arrays, so convert each element
|
||||||
IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
|
IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
|
||||||
for (int i = 0; i < valueAsArray.Length; i++)
|
for (var i = 0; i < valueAsArray.Length; i++)
|
||||||
{
|
{
|
||||||
converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destElementTypeInfo);
|
converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
|
||||||
}
|
}
|
||||||
return converted;
|
return converted;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// case 2: destination type is array but source is single element, so wrap element in array + convert
|
// case 2: destination type is array but source is single element, so wrap element in array + convert
|
||||||
object element = ConvertSimpleType(culture, value, destElementTypeInfo);
|
var element = ConvertSimpleType(culture, value, destinationElementType);
|
||||||
IList converted = Array.CreateInstance(destinationElementType, 1);
|
IList converted = Array.CreateInstance(destinationElementType, 1);
|
||||||
converted[0] = element;
|
converted[0] = element;
|
||||||
return converted;
|
return converted;
|
||||||
|
|
@ -169,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
if (valueAsArray.Length > 0)
|
if (valueAsArray.Length > 0)
|
||||||
{
|
{
|
||||||
value = valueAsArray.GetValue(0);
|
value = valueAsArray.GetValue(0);
|
||||||
return ConvertSimpleType(culture, value, destinationType.GetTypeInfo());
|
return ConvertSimpleType(culture, value, destinationType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -179,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 4: both destination + source type are single elements, so convert
|
// case 4: both destination + source type are single elements, so convert
|
||||||
return ConvertSimpleType(culture, value, destinationType.GetTypeInfo());
|
return ConvertSimpleType(culture, value, destinationType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"System.Diagnostics.Tools": "4.0.0.0",
|
"System.Diagnostics.Tools": "4.0.0.0",
|
||||||
"System.Dynamic.Runtime": "4.0.0.0",
|
"System.Dynamic.Runtime": "4.0.0.0",
|
||||||
"System.Globalization": "4.0.10.0",
|
"System.Globalization": "4.0.10.0",
|
||||||
|
"System.IO": "4.0.0.0",
|
||||||
"System.Linq": "4.0.0.0",
|
"System.Linq": "4.0.0.0",
|
||||||
"System.Reflection": "4.0.10.0",
|
"System.Reflection": "4.0.10.0",
|
||||||
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
||||||
|
|
@ -35,9 +36,11 @@
|
||||||
"System.Runtime": "4.0.20.0",
|
"System.Runtime": "4.0.20.0",
|
||||||
"System.Runtime.Extensions": "4.0.10.0",
|
"System.Runtime.Extensions": "4.0.10.0",
|
||||||
"System.Runtime.InteropServices": "4.0.20.0",
|
"System.Runtime.InteropServices": "4.0.20.0",
|
||||||
|
"System.Runtime.Serialization.Primitives": "4.0.0.0",
|
||||||
|
"System.Text.Encoding": "4.0.20.0",
|
||||||
|
"System.Text.Encoding.Extensions": "4.0.10.0",
|
||||||
"System.Threading": "4.0.0.0",
|
"System.Threading": "4.0.0.0",
|
||||||
"System.Threading.Tasks": "4.0.0.0",
|
"System.Threading.Tasks": "4.0.0.0"
|
||||||
"System.Runtime.Serialization.Primitives": "4.0.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Transient<IModelBinder, ComplexModelDtoModelBinder>();
|
yield return describe.Transient<IModelBinder, ComplexModelDtoModelBinder>();
|
||||||
|
|
||||||
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
|
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
|
||||||
|
yield return describe.Transient<IInputFormatterProvider, TempInputFormatterProvider>();
|
||||||
|
|
||||||
yield return describe.Transient<INestedProvider<FilterProviderContext>, DefaultFilterProvider>();
|
yield return describe.Transient<INestedProvider<FilterProviderContext>, DefaultFilterProvider>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
#if NET45
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class JsonInputFormatterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DefaultMediaType_ReturnsApplicationJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonInputFormatter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var mediaType = formatter.SupportedMediaTypes[0];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("application/json", mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> JsonFormatterReadSimpleTypesData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new object[] { "100", typeof(int), 100 };
|
||||||
|
yield return new object[] { "'abcd'", typeof(string), "abcd" };
|
||||||
|
yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime),
|
||||||
|
new DateTime(2012, 02, 01, 00, 45, 00) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData("JsonFormatterReadSimpleTypesData")]
|
||||||
|
public async Task JsonFormatterReadsSimpleTypes(string content, Type type, object expected)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new JsonInputFormatter();
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, type);
|
||||||
|
var context = new InputFormatterContext(httpContext, metadata, modelState);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, context.Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonFormatterReadsComplexTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = "{name: 'Person Name', Age: '30'}";
|
||||||
|
var formatter = new JsonInputFormatter();
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User));
|
||||||
|
var context = new InputFormatterContext(httpContext, metadata, modelState);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var model = Assert.IsType<User>(context.Model);
|
||||||
|
Assert.Equal("Person Name", model.Name);
|
||||||
|
Assert.Equal(30, model.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_ThrowsOnDeserializationErrors()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
||||||
|
var formatter = new JsonInputFormatter();
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User));
|
||||||
|
var context = new InputFormatterContext(httpContext, metadata, modelState);
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
await Assert.ThrowsAsync<JsonReaderException>(() => formatter.ReadAsync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsync_AddsModelValidationErrorsToModelState_WhenCaptureErrorsIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
||||||
|
var formatter = new JsonInputFormatter { CaptureDeserilizationErrors = true };
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
var httpContext = GetHttpContext(contentBytes);
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User));
|
||||||
|
var context = new InputFormatterContext(httpContext, metadata, modelState);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.ReadAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.",
|
||||||
|
modelState["Age"].Errors[0].Exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpContext GetHttpContext(byte[] contentBytes,
|
||||||
|
string contentType = "application/json")
|
||||||
|
{
|
||||||
|
var request = new Mock<HttpRequest>();
|
||||||
|
var headers = new Mock<IHeaderDictionary>();
|
||||||
|
headers.SetupGet(h => h["Content-Type"]).Returns(contentType);
|
||||||
|
request.SetupGet(r => r.Headers).Returns(headers.Object);
|
||||||
|
request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes));
|
||||||
|
|
||||||
|
var httpContext = new Mock<HttpContext>();
|
||||||
|
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||||
|
return httpContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class User
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public decimal Age { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"Microsoft.AspNet.PipelineCore": "0.1-alpha-*",
|
"Microsoft.AspNet.PipelineCore": "0.1-alpha-*",
|
||||||
"Microsoft.ComponentModel.DataAnnotations" : "4.0.10.0",
|
"Microsoft.ComponentModel.DataAnnotations" : "4.0.10.0",
|
||||||
"Microsoft.AspNet.Mvc.ModelBinding" : "",
|
"Microsoft.AspNet.Mvc.ModelBinding" : "",
|
||||||
|
"Newtonsoft.Json": "5.0.8",
|
||||||
"TestCommon" : "",
|
"TestCommon" : "",
|
||||||
"Xunit.KRunner": "0.1-alpha-*",
|
"Xunit.KRunner": "0.1-alpha-*",
|
||||||
"xunit.abstractions": "2.0.0-aspnet-*",
|
"xunit.abstractions": "2.0.0-aspnet-*",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue