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