Introduction
An ASP.NET Core API normally follows a familiar request and response model. A client sends a request, the API processes it, and the server returns a response.
That model becomes more interesting when the API is placed behind a streaming gateway.
Instead of waiting for the complete response, the gateway can forward data to the client as it becomes available. This is useful for large responses, long-running operations, real-time updates, AI responses, server-sent events, and other workloads where keeping the connection open is part of the application design.
However, adding a streaming gateway changes more than the network path.
Timeouts, buffering, response headers, cancellation, connection lifetime, error handling, and observability all become important.
A request that works correctly when the client communicates directly with ASP.NET Core can behave differently when traffic passes through a gateway.
This article explains what changes in the request flow and what developers should consider when running an ASP.NET Core API behind a streaming gateway.
Understanding the Request Path
Without a gateway, the request path may look like this:
Client
|
v
ASP.NET Core API
|
v
ResponseWith a streaming gateway:
Client
|
v
Streaming Gateway
|
v
ASP.NET Core API
|
v
Streaming Gateway
|
v
ClientThe gateway now sits between the client and the application.
That means there are potentially multiple components involved in:
Connection establishment
Request forwarding
Authentication
Timeout handling
Response buffering
Connection termination
Retry behavior
Error handling
For a normal short-lived API request, these differences may be barely noticeable.
For a streaming response, they can become critical.
What Is Response Streaming?
Response streaming means that the server sends data progressively instead of waiting until the entire response is ready.
For example, an API might generate several pieces of data:
Chunk 1
Chunk 2
Chunk 3
Chunk 4Instead of returning:
Complete responsethe API can send:
Chunk 1 → client
Chunk 2 → client
Chunk 3 → client
Chunk 4 → clientThis reduces the time before the client receives the first useful data.
It is particularly useful for:
Large files
Long-running processing
AI-generated responses
Server-sent events
Progress updates
Log streams
Real-time application data
A Simple ASP.NET Core Streaming Endpoint
ASP.NET Core supports asynchronous response streaming.
For example:
app.MapGet("/stream", async (
HttpResponse response,
CancellationToken cancellationToken) =>
{
response.ContentType = "text/plain";
for (var i = 1; i <= 10; i++)
{
await response.WriteAsync(
$"Message {i}\n",
cancellationToken);
await response.Body.FlushAsync(cancellationToken);
await Task.Delay(
TimeSpan.FromSeconds(1),
cancellationToken);
}
});The endpoint writes a message, flushes the response, waits, and continues.
When the client connects directly to the application, the behavior is relatively easy to understand.
Once a gateway is introduced, the gateway must also support forwarding the streamed response correctly.
The Gateway Can Change the Streaming Behavior
One of the most common issues is buffering.
Suppose ASP.NET Core produces:
Chunk 1and immediately flushes it.
The developer may expect the client to receive that chunk immediately.
However, if the gateway buffers the response, the client might receive:
Chunk 1
Chunk 2
Chunk 3
Chunk 4all at once.
The API is still technically streaming.
The end-to-end system is not.
This distinction is important:
Application streaming
≠
End-to-end streamingEvery component between the producer and consumer needs to preserve the intended behavior.
Why Buffering Matters
Consider an AI response that generates one token group every few hundred milliseconds.
Without buffering:
Gateway
↓
Token 1
Token 2
Token 3
Token 4The user sees the response gradually.
With buffering:
Gateway
↓
Wait
↓
Token 1 + Token 2 + Token 3 + Token 4The user may see a long pause followed by a large block of output.
This can make a streaming application feel slow even when the backend is producing data quickly.
Configure the Response Correctly
The response headers should accurately describe the streaming behavior.
For example:
app.MapGet("/events", async (
HttpResponse response,
CancellationToken cancellationToken) =>
{
response.ContentType = "text/event-stream";
for (var i = 1; i <= 5; i++)
{
await response.WriteAsync(
$"data: Event {i}\n\n",
cancellationToken);
await response.Body.FlushAsync(cancellationToken);
await Task.Delay(
TimeSpan.FromSeconds(1),
cancellationToken);
}
});The text/event-stream content type is commonly used for Server-Sent Events.
The gateway must preserve the response correctly rather than converting the response into a normal buffered response.
Streaming Does Not Mean Infinite Connections
A streaming endpoint may keep a connection open for a long time, but that does not mean the connection should remain open indefinitely.
You should define expected behavior for:
Maximum connection duration
Idle timeout
Request timeout
Gateway timeout
Backend timeout
Client cancellation
For example, an API may take several minutes to generate a response.
If the gateway has a 60-second timeout, the API may continue processing while the client receives a gateway timeout.
That creates an important operational problem.
Understand Timeout Layers
With a gateway in the middle, there may be several timeout settings:
Client timeout
|
v
Gateway timeout
|
v
ASP.NET Core timeout
|
v
Downstream service timeoutThese values should be designed intentionally.
For example:
Client: 5 minutes
Gateway: 4 minutes
API: 3 minutes
Database: 2 minutesThis provides a predictable timeout hierarchy.
The exact values depend on the workload.
The important point is that the gateway timeout cannot be treated as an unrelated infrastructure setting.
Streaming Changes Cancellation Handling
Client cancellation becomes especially important for streaming APIs.
Suppose a user starts an AI request and closes the browser.
The client connection disappears.
The gateway may detect that the client has disconnected.
ASP.NET Core should also stop unnecessary backend work when cancellation reaches the application.
The CancellationToken passed to the endpoint can help:
app.MapGet("/stream", async (
HttpResponse response,
CancellationToken cancellationToken) =>
{
response.ContentType = "text/plain";
while (!cancellationToken.IsCancellationRequested)
{
await response.WriteAsync(
"Processing...\n",
cancellationToken);
await response.Body.FlushAsync(
cancellationToken);
await Task.Delay(
TimeSpan.FromSeconds(1),
cancellationToken);
}
});The goal is to avoid continuing expensive processing after the client no longer needs the result.
This is especially important when the API is performing:
AI inference
Database queries
External API calls
File processing
Large computations
Do Not Swallow Cancellation Exceptions
Streaming applications should distinguish between genuine application failures and expected cancellation.
For example:
try
{
await ProcessStreamAsync(cancellationToken);
}
catch (OperationCanceledException)
{
// Client or request cancellation.
}Cancellation should generally not be treated as an unexpected server failure.
Otherwise, logs can become filled with errors caused by normal client disconnects.
Authentication Still Happens at the API
A gateway may handle authentication or token validation, but the API should understand what security guarantees the gateway provides.
For example:
Client
|
| Authorization
v
Gateway
|
| Forward request
v
ASP.NET Core APIDecide explicitly whether:
The gateway validates the token.
The API validates the token.
Both validate the token.
The gateway forwards identity information.
The API trusts specific gateway headers.
Do not automatically trust headers such as:
X-User-Id
X-User-Role
X-Authenticated-Userunless the network architecture guarantees that clients cannot directly inject or modify them.
Join the conversation! Your thoughts help the community grow.