Beispiel #1
0
        /// <inheritdoc />
        public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default)
        {
            EnsureArg.IsNotNull(stream, nameof(stream));

            int    bufferThreshold = DefaultBufferThreshold;
            long?  bufferLimit     = null;
            Stream seekableStream  = null;

            if (!stream.CanSeek)
            {
                try
                {
                    seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                    _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream);
                    await seekableStream.DrainAsync(cancellationToken);
                }
                catch (InvalidDataException)
                {
                    // This will result in bad request, we need to handle this differently when we make the processing serial.
                    throw new DicomFileLengthLimitExceededException(_storeConfiguration.Value.MaxAllowedDicomFileSize);
                }
                catch (IOException ex)
                {
                    throw new InvalidMultipartBodyPartException(ex);
                }
            }
            else
            {
                seekableStream = stream;
            }

            seekableStream.Seek(0, SeekOrigin.Begin);
            return(seekableStream);
        }
        /// <summary>
        /// Resolve the <paramref name="stream"/> for the <see cref="FileUploadProvider"/> and awaits the upload.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing uploaded data.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
        /// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="null"/>, <see cref="ErrorMessageResponse"/> otherwise.</returns>
        public async Task <ErrorMessageResponse> Completion(Stream stream, CancellationToken cancellationToken)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            if (ticketExpiryCts.IsCancellationRequested)
            {
                return(new ErrorMessageResponse(ErrorCode.ResourceNotPresent));
            }

            Stream bufferedStream = null;

            if (requireSynchronousIO)
            {
                // big reads, we should buffer to disk
                bufferedStream = new FileBufferingReadStream(stream, DefaultIOManager.DefaultBufferSize);
                await bufferedStream.DrainAsync(cancellationToken).ConfigureAwait(false);
            }

            using (bufferedStream)
            {
                taskCompletionSource.TrySetResult(bufferedStream ?? stream);

                await completionTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);

                return(errorMessage);
            }
        }
Beispiel #3
0
    private static async ValueTask <byte[]> ReadDataAsync(JsonTranscodingServerCallContext serverCallContext)
    {
        // Buffer to disk if content is larger than 30Kb.
        // Based on value in XmlSerializer and NewtonsoftJson input formatters.
        const int DefaultMemoryThreshold = 1024 * 30;

        var memoryThreshold = DefaultMemoryThreshold;
        var contentLength   = serverCallContext.HttpContext.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;
        }

        using var fs = new FileBufferingReadStream(serverCallContext.HttpContext.Request.Body, memoryThreshold);

        // Read the request body into buffer.
        // No explicit cancellation token. Request body uses underlying request aborted token.
        await fs.DrainAsync(CancellationToken.None);

        fs.Seek(0, SeekOrigin.Begin);

        var data = new byte[fs.Length];
        var read = fs.Read(data);

        Debug.Assert(read == data.Length);

        return(data);
    }
        /// <inheritdoc />
        public override async ValueTask <T> DeserializeFromRequest <T>(RequestExecutionContext context)
        {
            var fileReader = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryThreshold);

            context.HttpContext.Response.RegisterForDispose(fileReader);

            try
            {
                await fileReader.DrainAsync(context.HttpContext.RequestAborted);

                fileReader.Seek(0L, SeekOrigin.Begin);

                using var textReader = new StreamReader(fileReader);

                using var xmlReader = new XmlTextReader(textReader);

                var serializer = GetSerializer(typeof(T));

                return((T)serializer.Deserialize(xmlReader));
            }
            finally
            {
                await fileReader.DisposeAsync();
            }
        }
        private async Task <InputFormatterResult> BufferedStreamAsync(InputFormatterContext context)
        {
            using var readStream = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryBufferThreshold);
            await readStream.DrainAsync(CancellationToken.None);

            readStream.Position = 0;
            var payload = _model.Deserialize(readStream, value: null, type: context.ModelType);

            return(InputFormatterResult.Success(payload));
        }
Beispiel #6
0
        /// <inheritdoc />
        public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default)
        {
            EnsureArg.IsNotNull(stream, nameof(stream));

            int    bufferThreshold = DefaultBufferThreshold;
            long?  bufferLimit     = null;
            Stream seekableStream  = null;

            if (!stream.CanSeek)
            {
                seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream);
                await seekableStream.DrainAsync(cancellationToken);
            }
            else
            {
                seekableStream = stream;
            }

            seekableStream.Seek(0, SeekOrigin.Begin);

            return(seekableStream);
        }
Beispiel #7
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)
        {
            // Skipping error, if it's already marked as handled
            // This allows user code to implement its own error handling
            if (eventArgs.ErrorContext.Handled)
            {
                return;
            }

            successful = false;

            // The following addMember logic is intended to append the names of missing required properties to the
            // ModelStateDictionary key. Normally, just the ModelName and ErrorContext.Path is used for this key,
            // but ErrorContext.Path does not include the missing required property name like we want it to.
            // For example, given the following class and input missing the required "Name" property:
            //
            // class Person
            // {
            //     [JsonProperty(Required = Required.Always)]
            //     public string Name { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Required property 'Name' not found in JSON. Path 'Person'..."} System.Exception {Newtonsoft.Json.JsonSerializationException}
            // Member  "Name"   object {string}
            // Path    "Person" string
            //
            // So we update the path used for the ModelStateDictionary key to be "Person.Name" instead of just "Person".
            // See https://github.com/aspnet/Mvc/issues/8509
            var path   = eventArgs.ErrorContext.Path;
            var member = eventArgs.ErrorContext.Member as string;

            // There are some deserialization exceptions that include the member in the path but not at the end.
            // For example, given the following classes and invalid input like { "b": { "c": { "d": abc } } }:
            //
            // class A
            // {
            //     public B B { get; set; }
            // }
            // class B
            // {
            //     public C C { get; set; }
            // }
            // class C
            // {
            //     public string D { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Unexpected character encountered while parsing value: b. Path 'b.c.d'..."} System.Exception {Newtonsoft.Json.JsonReaderException}
            // Member  "c"     object {string}
            // Path    "b.c.d" string
            //
            // Notice that Member "c" is in the middle of the Path "b.c.d". The error handler gets invoked for each level of nesting.
            // null, "b", "c" and "d" are each a Member in different ErrorContexts all reporting the same parsing error.
            //
            // The parsing error is reported as a JsonReaderException instead of as a JsonSerializationException like
            // for missing required properties. We use the exception type to filter out these errors and keep the path used
            // for the ModelStateDictionary key as "b.c.d" instead of "b.c.d.c"
            // See https://github.com/dotnet/aspnetcore/issues/33451
            var addMember = !string.IsNullOrEmpty(member) && eventArgs.ErrorContext.Error is JsonSerializationException;

            // There are still JsonSerilizationExceptions that set ErrorContext.Member but include it at the
            // end of ErrorContext.Path already. The following logic attempts to filter these out.
            if (addMember)
            {
                // Path.Member case (path.Length < member.Length) needs no further checks.
                if (path.Length == member !.Length)
                {
                    // Add Member in Path.Member 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 or starts 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;
        }
Beispiel #8
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;

            var readStream        = request.Body;
            var disposeReadStream = false;

            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.
                var memoryThreshold = DefaultMemoryThreshold;
                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);

                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;
                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 JsonException || exception is OverflowException))
            {
                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;
            }
        }
Beispiel #9
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;
            Stream readStream = new NonDisposableStream(request.Body);

            if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
            {
                // XmlSerializer 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 = DefaultMemoryThreshold;
                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);

                await readStream.DrainAsync(CancellationToken.None);

                readStream.Seek(0L, SeekOrigin.Begin);
            }

            try
            {
                using var xmlReader = CreateXmlReader(readStream, encoding);
                var type = GetSerializableType(context.ModelType);

                var serializer = GetCachedSerializer(type);

                var deserializedObject = serializer.Deserialize(xmlReader);

                // Unwrap only if the original type was wrapped.
                if (type != context.ModelType)
                {
                    if (deserializedObject is IUnwrappable unwrappable)
                    {
                        deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
                    }
                }

                return(InputFormatterResult.Success(deserializedObject));
            }
            // XmlSerializer wraps actual exceptions (like FormatException or XmlException) into an InvalidOperationException
            // https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs#L652
            catch (InvalidOperationException exception) when(exception.InnerException != null &&
                                                             exception.InnerException.InnerException == null &&
                                                             string.Equals("Microsoft.GeneratedCode", exception.InnerException.Source, StringComparison.InvariantCulture))
            {
                // Know this was an XML parsing error because the inner Exception was thrown in the (generated)
                // assembly the XmlSerializer uses for parsing. The problem did not arise lower in the stack i.e. it's
                // not (for example) an out-of-memory condition.
                throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException);
            }
            catch (InvalidOperationException exception) when(exception.InnerException is FormatException ||
                                                             exception.InnerException is XmlException)
            {
                throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException);
            }
            finally
            {
                if (readStream is FileBufferingReadStream fileBufferingReadStream)
                {
                    await fileBufferingReadStream.DisposeAsync();
                }
            }
        }
Beispiel #10
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)
        {
            // Skipping error, if it's already marked as handled
            // This allows user code to implement its own error handling
            if (eventArgs.ErrorContext.Handled)
            {
                return;
            }

            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;
        }