public async Task <(bool success, bool noValue, object model, Exception exception)> ReadAsync(FromBodyPropertyInputFormatterContext context, Func <Func <Stream, JsonSerializerOptions, Task>, Task> readRequestBody, Encoding encoding)
        {
            if (!string.Equals(_actionId, context.ActionId, StringComparison.Ordinal))
            {
                throw new InvalidOperationException("Invalid action '" + context.ActionId + "', initialize for action '" + _actionId + "'.");
            }

            if (!HasReadRequestBody)
            {
                HasReadRequestBody = true;
                await readRequestBody(async (s, options) => {
                    _options          = options;
                    int bufferSize    = 0x4096;
                    var propertyData  = new Dictionary <string, List <byte[]> >(StringComparer.Ordinal);
                    var readerOptions = new JsonReaderOptions {
                        AllowTrailingCommas = options.AllowTrailingCommas,
                        CommentHandling     = options.ReadCommentHandling,
                        MaxDepth            = options.MaxDepth
                    };
                    var readerState      = new JsonReaderState(readerOptions);
                    var buffer           = ArrayPool <byte> .Shared.Rent(bufferSize);
                    Memory <byte> memory = buffer.AsMemory(0, bufferSize);
                    int length;
                    int offset             = 0;
                    string currentProperty = null;
                    int depth = 0;
                    DataBlockMode blockMode = DataBlockMode.Start;
                    bool handled            = false;
                    bool finalBlock         = false;
                    int consumed            = 0;
                    try {
                        length     = await s.ReadAsync(memory);
                        finalBlock = length == 0;
                        do
                        {
                            (handled, consumed) = HandleDataBlock(context, ref blockMode, ref depth, ref readerState, memory.Span.Slice(offset, length), finalBlock, ref currentProperty, propertyData);
                            offset += consumed;
                            if (!handled)
                            {
                                int bytesInBuffer = length - offset;
                                if (bytesInBuffer == 0)
                                {
                                    // read more data
                                    length     = await s.ReadAsync(memory);
                                    finalBlock = length == 0;
                                    offset     = 0;
                                }
                                else if ((uint)bytesInBuffer > ((uint)bufferSize / 2))
                                {
                                    // expand buffer
                                    bufferSize  = (bufferSize < (int.MaxValue / 2)) ? bufferSize * 2 : int.MaxValue;
                                    var buffer2 = ArrayPool <byte> .Shared.Rent(bufferSize);

                                    // copy the unprocessed data
                                    Buffer.BlockCopy(buffer, offset, buffer2, 0, bytesInBuffer);

                                    // return previous buffer
                                    ArrayPool <byte> .Shared.Return(buffer);
                                    buffer = buffer2;
                                    memory = buffer.AsMemory(0, bufferSize);

                                    // read more data
                                    length     = await s.ReadAsync(memory.Slice(bytesInBuffer));
                                    finalBlock = length == 0;
                                    length    += bytesInBuffer;
                                    offset     = 0;
                                }
                                else
                                {
                                    Buffer.BlockCopy(buffer, offset, buffer, 0, bytesInBuffer);

                                    // read more data
                                    length     = await s.ReadAsync(memory.Slice(bytesInBuffer));
                                    finalBlock = length == 0;
                                    length    += bytesInBuffer;
                                    offset     = 0;
                                }
                            }
                        }while (!handled);
                    }
                    finally {
                        ArrayPool <byte> .Shared.Return(buffer);
                    }

                    foreach ((var key, var value) in propertyData)
                    {
                        _values.Add(key, value.SelectMany(z => z).ToArray());
                    }
                });
            }

            if (!_keys.TryGetValue(context.FieldName, out var propertyType))
            {
                throw new InvalidOperationException("Unknown property '" + context.FieldName + "' requested.");
            }
            else if (_values.TryGetValue(context.FieldName, out var propertyData))
            {
                try {
                    var v = JsonSerializer.Deserialize(propertyData, propertyType, _options);
                    return(true, false, v, null);
                }
                catch (Exception ex) {
                    return(false, false, null, ex);
                }
            }

            return(true, true, null, null);
        }
        private (bool handled, int consumed) HandleDataBlock(FromBodyPropertyInputFormatterContext context, ref DataBlockMode blockMode, ref int depth, ref JsonReaderState readerState, ReadOnlySpan <byte> data, bool finalBlock, ref string currentProperty, Dictionary <string, List <byte[]> > propertyData)
        {
            var jr = new Utf8JsonReader(data, finalBlock, readerState);

            int start = 0;

            while (jr.Read())
            {
                var jrt = jr.TokenType;

                if (blockMode == DataBlockMode.Start)
                {
                    if (jrt == JsonTokenType.StartObject)
                    {
                        blockMode = DataBlockMode.Body;
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jrt + "'.");
                    }
                }
                else if (blockMode == DataBlockMode.End)
                {
                    if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected end of object.");
                    }
                }
                else if (blockMode == DataBlockMode.Body)
                {
                    if (jrt == JsonTokenType.EndObject)
                    {
                        blockMode = DataBlockMode.End;
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else if (jrt == JsonTokenType.PropertyName)
                    {
                        currentProperty = jr.GetString();
                        if (propertyData.ContainsKey(currentProperty))
                        {
                            throw new Exception("Duplicate property: " + currentProperty);
                        }
                        propertyData.Add(currentProperty, new List <byte[]>());
                        start     = checked ((int)jr.BytesConsumed);
                        blockMode = DataBlockMode.Property;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected property.");
                    }
                }
                else if (blockMode == DataBlockMode.Property)
                {
                    if (IsValueToken(jrt))
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else if (jrt == JsonTokenType.StartObject)
                    {
                        depth     = jr.CurrentDepth;
                        blockMode = DataBlockMode.SkipObject;
                    }
                    else if (jrt == JsonTokenType.StartArray)
                    {
                        depth     = jr.CurrentDepth;
                        blockMode = DataBlockMode.SkipArray;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected property value.");
                    }
                }
                else if (blockMode == DataBlockMode.SkipObject)
                {
                    if (jrt == JsonTokenType.EndObject && jr.CurrentDepth == depth)
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                }
                else if (blockMode == DataBlockMode.SkipArray)
                {
                    if (jrt == JsonTokenType.EndArray && jr.CurrentDepth == depth)
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                }
                else
                {
                    throw new Exception("Unexpected state");
                }
            }

            if (!jr.IsFinalBlock)
            {
                readerState = jr.CurrentState;
                var consumed = checked ((int)jr.BytesConsumed);
                if (consumed - start > 0 && (blockMode == DataBlockMode.SkipObject || blockMode == DataBlockMode.SkipArray))
                {
                    propertyData[currentProperty].Add(data.Slice(start, consumed - start).ToArray());
                }
                return(false, consumed);
            }

            if (blockMode != DataBlockMode.End)
            {
                throw new Exception("More data expected");
            }

            return(true, 0);
        }
        /// <inheritdoc />
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            AttemptingToBindModel(_logger, bindingContext);

            // Special logic for body, treat the model name as string.Empty for the top level
            // object, but allow an override via BinderModelName. The purpose of this is to try
            // and be similar to the behavior for POCOs bound via traditional model binding.
            string modelBindingKey;

            if (bindingContext.IsTopLevelObject)
            {
                modelBindingKey = bindingContext.BinderModelName ?? string.Empty;
            }
            else
            {
                modelBindingKey = bindingContext.ModelName;
            }

            var httpContext = bindingContext.HttpContext;

            var allowEmptyInputInModelBinding = _options?.AllowEmptyInputInBodyModelBinding == true;

            var serviceProvider       = httpContext.RequestServices;
            var bodyModelBinderHelper = serviceProvider.GetService <IFromBodyPropertyModelBinderHelper>();

            bodyModelBinderHelper.Initialize(bindingContext.ActionContext.ActionDescriptor.Id, bindingContext.ActionContext.ActionDescriptor.Parameters.Concat(bindingContext.ActionContext.ActionDescriptor.BoundProperties));

            var formatterContext = new FromBodyPropertyInputFormatterContext(
                httpContext,
                modelBindingKey,
                bindingContext.ModelState,
                bindingContext.ModelMetadata,
                _readerFactory,
                allowEmptyInputInModelBinding,
                bindingContext.ActionContext.ActionDescriptor.Id,
                bindingContext.FieldName,
                bodyModelBinderHelper);

            var formatter = (IInputFormatter)null;

            for (var i = 0; i < _formatters.Count; i++)
            {
                if (_formatters[i].CanRead(formatterContext))
                {
                    formatter = _formatters[i];
                    InputFormatterSelected(_logger, formatter, formatterContext);
                    break;
                }
                else
                {
                    InputFormatterRejected(_logger, _formatters[i], formatterContext);
                }
            }

            if (formatter == null)
            {
                NoInputFormatterSelected(_logger, formatterContext);

                var message   = "FormatUnsupportedContentType: " + httpContext.Request.ContentType;
                var exception = new UnsupportedContentTypeException(message);
                bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata);
                DoneAttemptingToBindModel(_logger, bindingContext);
                return;
            }

            try {
                var result = await formatter.ReadAsync(formatterContext);

                if (result.HasError)
                {
                    // Formatter encountered an error. Do not use the model it returned.
                    DoneAttemptingToBindModel(_logger, bindingContext);
                    return;
                }

                if (result.IsModelSet)
                {
                    var model = result.Model;
                    bindingContext.Result = ModelBindingResult.Success(model);
                }
                else
                {
                    // If the input formatter gives a "no value" result, that's always a model state error,
                    // because BodyModelBinder implicitly regards input as being required for model binding.
                    // If instead the input formatter wants to treat the input as optional, it must do so by
                    // returning InputFormatterResult.Success(defaultForModelType), because input formatters
                    // are responsible for choosing a default value for the model type.
                    var message = bindingContext
                                  .ModelMetadata
                                  .ModelBindingMessageProvider
                                  .MissingRequestBodyRequiredValueAccessor();
                    bindingContext.ModelState.AddModelError(modelBindingKey, message);
                }
            }
            catch (Exception exception) when(exception is InputFormatterException || ShouldHandleException(formatter))
            {
                bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata);
            }

            DoneAttemptingToBindModel(_logger, bindingContext);
        }