aspnetcore/src/Microsoft.AspNet.Mvc.ModelB.../Formatters/JsonInputFormatter.cs

201 lines
7.8 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class JsonInputFormatter : IInputFormatter
{
private const int DefaultMaxDepth = 32;
private readonly List<Encoding> _supportedEncodings;
private readonly List<string> _supportedMediaTypes;
private JsonSerializerSettings _jsonSerializerSettings;
public JsonInputFormatter()
{
_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 (string.Equals(charset, 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));
}
}
}