Recently I’ve run into an ASP.NET Core MVC bug when an HTTP request with multipart content-type and empty body would cause an unhanded IOException exception to be thrown in MVC framework. However, when sending an invalid request I’d expect to receive a 400 error response code.
Seems like MVC team would need to do a lot of work to change code design to fix the bug. Since the bug doesn’t cause any major issues, it might get de-prioritized and we might need to wait quite some time until it’s fixed. However, I would like my API to be able to handle such invalid requests now, and luckily .NET Core pipeline provides us with the tools needed for the fix.
To replicate the bug locally, create a new vanilla .NET Core API project. Mine one references the only Microsoft.AspNetCore.All nuget package of version 2.0.3. And simply run your API application. Then use your favourite REST API testing client (mine one is still Postman, hasn’t changed since this blog post :)) to send an empty multipart content-type request. In Postman I had to add and deactivate one dummy key to make sure boundary header is set as well. See screenshot below.
And the request caused the following exception (see console window of your API) to be thrown. The exception doesn’t contain any useful explanation, just we could guess the body stream was empty, hence an exception occurred when attempting to read it.
fail: Microsoft.AspNetCore.Server.Kestrel Connection id "0HL9VU5PV58QL", Request id "0HL9VU5PV58QL:00000001": An unhandled exception was thrown by the application. System.IO.IOException: Unexpected end of Stream, the content may have already been read by another component. at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.<ReadAsync>d__36.MoveNext()
The idea behind my fix/workaround is to validate requests before they actually hit MVC, and if an empty multipart content-type request is detected, then simply return 400 error response code. There is no point processing a request that is invalid and will definitely fail.
The request in ASP.NET Core travels in a pipeline from top to bottom following the order of middlewares defined in Configure() method in Startup class. I’ve added a new middleware just before the MVC one to check all HTTP requests. My middleware checks for such invalid requests, and returns 400 error response code with a simple message explaining how to fix the request. Source code snippets below.
And after running the API with RequestValidator middleware enabled I received the following result.
Now my API returns 400 error response code with a simple message explaining why the request was treated as invalid!
Also, pay closer attention to the response delivery time! It’s much faster to gracefully return 400 instead of letting MVC crash and throw System.IO.IOException exception!