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 _supportedEncodings; private readonly List _supportedMediaTypes; private JsonSerializerSettings _jsonSerializerSettings; public JsonInputFormatter() { _supportedMediaTypes = new List { "application/json", "text/json" }; _supportedEncodings = new List { 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 }; } /// public IList SupportedMediaTypes { get { return _supportedMediaTypes; } } /// public IList SupportedEncodings { get { return _supportedEncodings; } } /// /// Gets or sets the used to configure the . /// public JsonSerializerSettings SerializerSettings { get { return _jsonSerializerSettings; } set { if (value == null) { throw new ArgumentNullException("value"); } _jsonSerializerSettings = value; } } /// /// Gets or sets if deserialization errors are captured. When set, these errors appear in /// the instance of . /// public bool CaptureDeserilizationErrors { get; set; } /// 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); } /// /// Called during deserialization to get the . /// /// The for the read. /// The from which to read. /// The to use when reading. /// The used during deserialization. public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context, [NotNull] Stream readStream, [NotNull] Encoding effectiveEncoding) { return new JsonTextReader(new StreamReader(readStream, effectiveEncoding)); } // /// Called during deserialization to get the . /// /// The used during serialization and deserialization. public virtual JsonSerializer CreateJsonSerializer() { return JsonSerializer.Create(SerializerSettings); } private bool IsSupportedContentType(ContentTypeHeaderValue contentType) { return contentType != null && _supportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase); } private Task 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 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)); } } }