Skip to content

NewtonsoftJsonInputFormatter does not threat FormatException as MalformedInputException #14504

@ig-sinicyn

Description

@ig-sinicyn

Describe the bug

NewtonsoftJsonInputFormatter does not handle malformed input at least for DateTime fields.
So, malformed request results in unhandled exception and http 500 response.

To Reproduce

Net core 3, controller:

	public class SomeRequest
	{
		[Required]
		public DateTime RequestData { get; set; }
	}

	public class SomeResponse
	{
		[Required]
		public DateTime RequestData { get; set; }
	}

	[ApiController]
	[Route("testCalls")]
	public class TestCallsController : ControllerBase
	{
		[HttpPost]
		[Route("echo")]
		[Consumes("application/json")]
		public SomeResponse EchoPost([Required] SomeRequest request) // (1)
		{
			return new SomeResponse
			{
				RequestData = request.RequestData
			};
		}
	}

setup:

	services
		.AddControllers()
		.ConfigureApiBehaviorOptions(
			o =>
			{
				o.InvalidModelStateResponseFactory = c =>
				{
					Debugger.Break(); // Did not get called
					return new BadRequestObjectResult(c.ModelState);
				};
			})
		.AddNewtonsoftJson();

UPD, the

		.AddNewtonsoftJson(
			options =>
			{
				options.AllowInputFormatterExceptionMessages = true;
			});

does not help.

request:

curl -X POST "https://localhost:44310/testCalls/echo" -H "accept: text/plain" -H "Content-Type: application/json" -d "{\"requestData\":\"invalidDate\"}"

Actual behavior

http 500, exception stack:

System.FormatException: The string 'invalidDate' was not recognized as a valid DateTime. There is an unknown word starting at index '0'.
   at System.DateTimeParse.Parse(ReadOnlySpan`1 s, DateTimeFormatInfo dtfi, DateTimeStyles styles)
   at System.DateTime.Parse(String s, IFormatProvider provider, DateTimeStyles styles)
   at Newtonsoft.Json.Converters.IsoDateTimeConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Expected behavior

Invalid input should be handled by InvalidModelStateResponseFactory call (http 400, by default).

Additional context

As a hack the formatter may be replaced with any derived class, explanation:

// Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormatter
public virtual InputFormatterExceptionPolicy ExceptionPolicy
{
	get
	{
		if (GetType() == typeof(NewtonsoftJsonInputFormatter))
		{
			return InputFormatterExceptionPolicy.MalformedInputExceptions;
		}
		return InputFormatterExceptionPolicy.AllExceptions; // FormatException will be handled too
	}
}

Metadata

Metadata

Assignees

Labels

DoneThis issue has been fixedarea-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesenhancementThis issue represents an ask for new feature or an enhancement to an existing one

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions