5

It seems that AspNet.Core starts sending response that is IEnumerable right away without iterating over the whole collection. E.g.:

[HttpGet("")]
public async Task<IActionResult> GetData()
{
    IEnumerable<MyData> result = await _service.GetData();
    return Ok(result.Select(_mapper.MapMyDataToMyDataWeb));
}

Now there is an exception that happens during mapping of one of the elements, so I would assume a 500 response, but in reality what happens is that I get a 200 with only partial (and incorrect) Json.

I assume it's a feature and not a bug in Asp.Net Core that provides this behavior and it is additionally relatively easy to fix by calling e.g. ToList(), but I am wondering if there is some kind of flag that can prevent this situation from happening since it does not really make sense for e.g. API project and standard JSON response.

I was not able to find anything in documentation that describes this behavior and how to prevent it.

P.S. I have verified that calling ToList() fixes the issue and the response is 500 with correct exception (with UseDeveloperExceptionPage)

27
  • 1
    Your assumptions here are incorrect. A HTTP call is a single atomic unit. It doesn't stream the enumerable, it can't. The enumerator will be being enumerated in it's entirety before/as part of being serialised into JSON. If you getting an exception in you JSON then that's the serialisation process Commented Mar 21, 2019 at 11:21
  • 1
    @Liam agree, I bet the problem is in the mapper which stops mapping at some point and returns partial result which is converted to json and sent to client Commented Mar 21, 2019 at 11:22
  • 2
    While I don't have enough knowledge to argue about how it theoretically can or can not work, I am 100% sure though is that I get incorrect JSON and want to prevent it globally instead of writing ToList() each time with possibility of forgetting it and client getting an invalid json instead of 500. Any ideas on how to prevent it? I assume that this is because it starts responding stream, and that's why it is happening. If there are other reasons for it, it does not change the fact unfortunately Commented Mar 21, 2019 at 11:33
  • 1
    Did you confirm that this does work if you ToList() it? Commented Mar 21, 2019 at 11:46
  • 1
    Yes, with ToList() I get an expected 500. I have added it to the question Commented Mar 21, 2019 at 11:46

1 Answer 1

6

It seems that this is actually "by design", this issue was raised few times on Asp.Net Core github repository.

What happens is that header with 200 is already sent, while the body is not. While I would think that enumeration must proceed before sending headers, asp.net team says it will use more resources on the server and that's why it is like that.

Here is a quote:

It is very likely the case that your exception is thrown while writing to the body, after headers have already been sent to the client, so there's no take-backs on the 200 that was already sent as part of the response. The client will see an error because the body will come back as incomplete.

If you want to deterministically report a 500 when this happens you'll need to either:

I can confirm that this solution worked:

  1. Reference Microsoft.AspNetCore.Buffering package
  2. Write app.UseResponseBuffering() before app.UseMvc()
Sign up to request clarification or add additional context in comments.

10 Comments

Looks like time has taken its toll on that link, sadly, although I can't find its new home
That's archived - the separate repos got subsumed into the main AspNetCore repo, although BufferingWriteStream and ResponseBufferingMiddleware are nowhere to be found. Perhaps it was just abandoned.
The library exists, I have checked that it works and I assume that it is actually similar to this DeChunkMiddleware only provided by Microsoft out of the box. Version is 0.2.2 for some reason and the documentation is an empty stub...
It does exist, although where the source is I have no idea! Anyway thanks for posting your solution, of course do be aware this will affect things like streaming files.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.