public void MultipartFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType) { // Arrange var formatter = new MultipartFormatter(); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = requestContentType; var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForType(typeof(void)); var context = new InputFormatterContext( httpContext, modelName: string.Empty, modelState: new ModelStateDictionary(), metadata: metadata, readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader); // Act var result = formatter.CanRead(context); // Assert Assert.True(result); }
/// <inheritdoc /> public override 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; using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), 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) { var unwrappable = deserializedObject as IUnwrappable; if (unwrappable != null) { deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); } } return(InputFormatterResult.SuccessAsync(deserializedObject)); } }
public void SelectCharacterEncoding_ReturnsFirstEncoding_IfContentTypeIsMissingInvalidOrDoesNotHaveEncoding( string contentType) { // Arrange var formatter = new TestFormatter(); formatter.SupportedEncodings.Add(Encoding.UTF8); formatter.SupportedEncodings.Add(Encoding.UTF32); var context = new InputFormatterContext( new DefaultHttpContext(), "something", new ModelStateDictionary(), new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)), (stream, encoding) => new StreamReader(stream, encoding)); context.HttpContext.Request.ContentType = contentType; // Act var result = formatter.TestSelectCharacterEncoding(context); // Assert Assert.Equal(Encoding.UTF8, result); }
public async Task JsonFormatterReadsSimpleTypes(string content, Type type, object expected) { // Arrange var logger = GetLogger(); var formatter = new JsonInputFormatter(logger); var contentBytes = Encoding.UTF8.GetBytes(content); var httpContext = GetHttpContext(contentBytes); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForType(type); var context = new InputFormatterContext( httpContext, modelName: string.Empty, modelState: new ModelStateDictionary(), metadata: metadata, readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader); // Act var result = await formatter.ReadAsync(context); // Assert Assert.False(result.HasError); Assert.Equal(expected, result.Model); }
public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) { // Arrange var loggerMock = GetLogger(); var formatter = new JsonInputFormatter(loggerMock); var contentBytes = Encoding.UTF8.GetBytes("content"); var httpContext = GetHttpContext(contentBytes, contentType: requestContentType); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForType(typeof(string)); var formatterContext = new InputFormatterContext( httpContext, modelName: string.Empty, modelState: new ModelStateDictionary(), metadata: metadata, readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader); // Act var result = formatter.CanRead(formatterContext); // Assert Assert.Equal(expectedCanRead, result); }
public async Task CustomSerializerSettingsObject_TakesEffect() { // Arrange // missing password property here var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}"); var logger = GetLogger(); var jsonFormatter = new JsonInputFormatter(logger); // by default we ignore missing members, so here explicitly changing it jsonFormatter.SerializerSettings = new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error }; var modelState = new ModelStateDictionary(); var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8"); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForType(typeof(UserLogin)); var inputFormatterContext = new InputFormatterContext( httpContext, modelName: string.Empty, modelState: modelState, metadata: metadata, readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader); // Act var result = await jsonFormatter.ReadAsync(inputFormatterContext); // Assert Assert.True(result.HasError); Assert.False(modelState.IsValid); var modelErrorMessage = modelState.Values.First().Errors[0].Exception.Message; Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage); }
/// <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()); } } }
/// <summary> /// Called during deserialization to get the <see cref="JsonSerializer"/>. The formatter context /// that is passed gives an ability to create serializer specific to the context. /// </summary> /// <param name="context">A context object used by an input formatter for deserializing the request body into an object.</param> /// <returns>The <see cref="JsonSerializer"/> used during deserialization.</returns> /// <remarks> /// This method works in tandem with <see cref="ReleaseJsonSerializer(JsonSerializer)"/> to /// manage the lifetimes of <see cref="JsonSerializer"/> instances. /// </remarks> protected virtual JsonSerializer CreateJsonSerializer(InputFormatterContext context) { return(CreateJsonSerializer()); }
/// <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()); } } }
/// <summary> /// Reads an object from the request body. /// </summary> /// <param name="context">The <see cref="InputFormatterContext"/>.</param> /// <param name="encoding">The <see cref="Encoding"/> used to read the request body.</param> /// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns> public abstract Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding);
/// <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)); } }
public override Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context) { throw new NotImplementedException(); }
public new JsonSerializer CreateJsonSerializer(InputFormatterContext _) => base.CreateJsonSerializer(null);
/// <inheritdoc /> public override 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; using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var successful = true; EventHandler <Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = (sender, eventArgs) => { successful = false; var exception = eventArgs.ErrorContext.Error; // 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) { return(InputFormatterResult.SuccessAsync(model)); } return(InputFormatterResult.FailureAsync()); } } }
/// <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(); } } }
public override Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { return(InputFormatterResult.SuccessAsync(_object)); }
public Encoding TestSelectCharacterEncoding(InputFormatterContext context) { return(SelectCharacterEncoding(context)); }
/// <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; }