Example #1
0
        /// <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));
            }
        }
Example #2
0
        /// <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());
                }
            }
        }
Example #3
0
        /// <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));
            }
        }
Example #4
0
        /// <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;
            }
Example #5
0
        /// <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());
                }
            }
        }