-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix IAsyncEnumerable controller methods to allow setting headers #57924
base: release/9.0-rc2
Are you sure you want to change the base?
Conversation
Hi @BrennanConroy. This PR was just approved to be included in the upcoming servicing release. Somebody from the @dotnet/aspnet-build team will get it merged when the branches are open. Until then, please make sure all the CI checks pass and the PR is reviewed. |
Should we do something for minimal APIs? I don't think it's as important as MVC because you cannot
However, you can run into the with a named function which isn't super rare. And at that point, the repro code looks very similar to what you have for an MVC action. var app = WebApplication.Create(args);
app.MapGet("/", Test);
app.Run();
async IAsyncEnumerable<string> Test(HttpContext context)
{
var testValue = await myService.GetHeaderValueAsync();
context.Response.Headers["Test"] = testValue;
await foreach (var item in myService.GetItemsAsync())
{
yield return item;
}
} I agree that we probably don't need to update app.MapGet("/", async (HttpContext context) =>
{
async IAsyncEnumerable<string> Test()
{
var testValue = await myService.GetHeaderValueAsync();
context.Response.Headers["Test"] = testValue;
await foreach (var item in myService.GetItemsAsync())
{
yield return item;
}
}
await context.Response.WriteAsJsonAsync(Test());
}); Instead of just: app.MapGet("/", async (HttpContext context) =>
{
async IAsyncEnumerable<string> Test()
{
await foreach (var item in myService.GetItemsAsync())
{
yield return item;
}
}
var testValue = await myService.GetHeaderValueAsync();
context.Response.Headers["Test"] = testValue;
await context.Response.WriteAsJsonAsync(Test());
}); I suppose with weird enough layering it's possible that someone might try to mutate the response in an |
Pretty sure minimal just calls into |
Assert.False(responseBodyFeature.StartCalled); | ||
await Task.Yield(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to test this after yielding?
Assert.False(responseBodyFeature.StartCalled); | |
await Task.Yield(); | |
await Task.Yield(); | |
Assert.False(responseBodyFeature.StartCalled); |
I wonder if we should add an end-to-end test where we try adding a header after yielding.
if (!response.HasStarted) | ||
// Don't call StartAsync for IAsyncEnumerable. Headers might be set at the beginning of the generator which isn't invoked until | ||
// JsonSerializer starts iterating over the IAsyncEnumerable. | ||
if (!response.HasStarted && value is not null && !AsyncEnumerableHelper.IsIAsyncEnumerable(value.GetType())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a method to AsyncEnumerableHelper like the following?
Task StartResponseIfPossibleAsync(this HttpResponse response, Type? typeToSerialize)
It seems like it would reduce a lot of repetitive HasStarted
and null checks.
Fix IAsyncEnumerable controller methods to allow setting headers
Description
When writing Json responses in MVC, we started flushing headers immediately before using
JsonSerializer
in order to avoid some unnecessary buffering in Kestrel when writing starts before headers are flushed. This had the unintended side-effect of makingIAsyncEnumerable<T>
MVC methods no longer able to set headers due to the fact that the whole method becomes theIAsyncEnumerable
and we pass that directly toJsonSerializer
, which means we've already flushed headers by the time the MVC method starts running. Resulting in an exception on the server and a bad response on the client.Fixes #57895
Customer Impact
MVC methods that returned
IAsyncEnumerable<T>
will now fail if they modify response headers.Regression?
Regressed from 8.0
Risk
Small targeted fix. Added test coverage for scenario.
Verification
Packaging changes reviewed?