Skip to content

Stream requests ​

A stream request returns its results one at a time as an IAsyncEnumerable<T>: rows from a paged query, lines of a log, tokens from a language model.

csharp
public sealed record TailLog(string Source) : IStreamRequest<string>;

public sealed class TailLogHandler(ILogReader reader) : IStreamRequestHandler<TailLog, string>
{
    public async IAsyncEnumerable<string> Handle(
        TailLog request,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await foreach (var line in reader.ReadAsync(request.Source, cancellationToken))
            yield return line;
    }
}

Consume it with CreateStream:

csharp
await foreach (var line in sender.CreateStream(new TailLog("api"), cancellationToken))
    Console.WriteLine(line);

CreateStream(object) accepts a request whose type is only known at runtime and yields the items boxed as object?. The object must implement IStreamRequest<T>, otherwise an ArgumentException is thrown.

Stream behaviors ​

Stream requests have their own pipeline, separate from the request pipeline:

csharp
public sealed class StreamLogging<TRequest, TResponse>(ILogger<StreamLogging<TRequest, TResponse>> logger)
    : IStreamPipelineBehavior<TRequest, TResponse>
    where TRequest : IStreamRequest<TResponse>
{
    public async IAsyncEnumerable<TResponse> Handle(
        TRequest request,
        StreamHandlerDelegate<TResponse> next,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var count = 0;
        await foreach (var item in next(cancellationToken).WithCancellation(cancellationToken))
        {
            count++;
            yield return item;
        }
        logger.LogInformation("{Request} streamed {Count} items", typeof(TRequest).Name, count);
    }
}
csharp
cfg.AddOpenStreamBehavior(typeof(StreamLogging<,>));

Like request behaviors, stream behaviors run in registration order: the first registered is the outermost.