Nota: He reescrito sustancialmente esta pregunta porque la premisa original era defectuosa.
Lo que estoy tratando de lograr en última instancia es transmitir cientos de miles de registros de una base de datos como un archivo CSV descargable sin azotar la memoria del servidor, pero también quiero que el navegador muestre que la descarga de tamaño indeterminado comienza de inmediato y crece a medida que aumenta. se recuperan registros.
He establecido que Kestrel está descargando contenido mientras lo escribo (independientemente de cómo redacte la salida). Lo sé porque cuando solicito el archivo con curl, veo que se transmite a la salida estándar una parte a la vez. Sin embargo, cuando solicito el archivo en un navegador (Chrome o Firefox), no muestra un archivo de descarga hasta que se emite el último registro, lo que genera una mala experiencia para el usuario porque la URL de descarga parece estar rota. La siguiente acción simple del controlador demuestra el problema.
¿Qué debo hacer con este código para que Chrome muestre inmediatamente la barra de descarga y muestre el archivo como descargando tan pronto como haya emitido los encabezados?
[ApiController] [Route("[controller]")] public class HomeController : ControllerBase { [HttpGet("[action]")] public async Task Test() { Response.StatusCode = 200; Response.ContentType = "text/plain"; Response.ContentLength = null; Response.StatusCode = StatusCodes.Status200OK; Response.Headers.Add(HeaderNames.ContentDisposition, new ContentDispositionHeaderValue("attachment") { FileName = "test.txt" }.ToString()); await Response.StartAsync(); for (var i = 0; i < 5; ++i) { await Response.WriteAsync($"Iteration {i} at {DateTime.Now}\r\n"); await Response.Body.FlushAsync(); await Task.Delay(2000); } } }Resuelto. Cambié el tipo de contenido a application/octet-stream y ahora se comporta como yo quería.
Solución de trabajo completa:
[ApiController] [Route("[controller]")] public class HomeController : ControllerBase { [HttpGet("[action]")] public async Task Test() { Response.StatusCode = 200; Response.ContentType = "application/octet-stream"; Response.ContentLength = null; Response.StatusCode = StatusCodes.Status200OK; Response.Headers.Add(HeaderNames.ContentDisposition, new ContentDispositionHeaderValue("attachment") { FileName = "test.txt" }.ToString()); await Response.StartAsync(); for (var i = 0; i < 5; ++i) { await Response.WriteAsync($"Iteration {i} at {DateTime.Now}\r\n"); await Response.Body.FlushAsync(); await Task.Delay(2000); } } }