/// <inheritdoc /> public sealed override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var inputStream = GetInputStream(httpContext, encoding); object model; try { model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions); } catch (JsonException jsonException) { var path = jsonException.Path; var formatterException = new InputFormatterException(jsonException.Message, jsonException); context.ModelState.TryAddModelError(path, formatterException, context.Metadata); Log.JsonInputException(_logger, jsonException); return(InputFormatterResult.Failure()); } finally { if (inputStream is TranscodingReadStream transcoding) { await transcoding.DisposeAsync(); } } if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { Log.JsonInputSuccess(_logger, context.ModelType); return(InputFormatterResult.Success(model)); } }
/// <inheritdoc /> public sealed override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var inputStream = GetInputStream(httpContext, encoding); object model; try { model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions); } finally { if (inputStream is TranscodingReadStream transcoding) { transcoding.Dispose(); } } if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var request = context.HttpContext.Request; var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. request.EnableBuffering(); Debug.Assert(request.Body.CanSeek); await request.Body.DrainAsync(CancellationToken.None); request.Body.Seek(0L, SeekOrigin.Begin); } using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var successful = true; Exception exception = null; void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // 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 eventArgs.ErrorContext.Handled = true; } var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(); jsonSerializer.Error += ErrorHandler; object model; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (!(exception is JsonException || exception is OverflowException)) { var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); } } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var request = httpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; var readStream = request.Body; var disposeReadStream = false; if (readStream.CanSeek) { // The most common way of getting here is the user has request buffering on. // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous // reads as part of the deserialization. // To avoid this, drain and reset the stream. var position = request.Body.Position; await readStream.DrainAsync(CancellationToken.None); readStream.Position = position; } else if (!suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); // Ensure the file buffer stream is always disposed at the end of a request. httpContext.Response.RegisterForDispose(readStream); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception?exception = null; object model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; if (_jsonOptions.ReadJsonWithRequestCulture) { jsonSerializer.Culture = CultureInfo.CurrentCulture; } try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (exception is not null && exception is not(JsonException or OverflowException or FormatException)) { // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary. // We only need to rethrow an exception if we believe it needs to be handled by something further up // the stack. // JsonException, OverflowException, and FormatException are assumed to be only encountered when // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else // needs to be rethrown. var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member !.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal) && !path.EndsWith("['" + member + "']", StringComparison.Ordinal) && !path.EndsWith("[" + member + "]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // 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 eventArgs.ErrorContext.Handled = true; }
/// <inheritdoc /> public sealed override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var(inputStream, usesTranscodingStream) = GetInputStream(httpContext, encoding); object?model; try { model = await JsonSerializer.DeserializeAsync(inputStream, context.ModelType, SerializerOptions); } catch (JsonException jsonException) { var path = jsonException.Path ?? string.Empty; var modelStateException = WrapExceptionForModelState(jsonException); context.ModelState.TryAddModelError(path, modelStateException, context.Metadata); Log.JsonInputException(_logger, jsonException); return(InputFormatterResult.Failure()); } catch (Exception exception) when(exception is FormatException || exception is OverflowException) { // The code in System.Text.Json never throws these exceptions. However a custom converter could produce these errors for instance when // parsing a value. These error messages are considered safe to report to users using ModelState. context.ModelState.TryAddModelError(string.Empty, exception, context.Metadata); Log.JsonInputException(_logger, exception); return(InputFormatterResult.Failure()); } finally { if (usesTranscodingStream) { await inputStream.DisposeAsync(); } } if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { Log.JsonInputSuccess(_logger, context.ModelType); return(InputFormatterResult.Success(model)); } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var request = context.HttpContext.Request; if (!request.Body.CanSeek && !_suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. BufferingHelper.EnableRewind(request); Debug.Assert(request.Body.CanSeek); await request.Body.DrainAsync(CancellationToken.None); request.Body.Seek(0L, SeekOrigin.Begin); } using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var successful = true; void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // 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; } } var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path); context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error, metadata); _logger.JsonInputException(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 eventArgs.ErrorContext.Handled = true; } var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(); jsonSerializer.Error += ErrorHandler; object model; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } return(InputFormatterResult.Failure()); } } }