Can find numerous examples on how return an IAsyncEnumerable<T>
from a Web API and consume it in C#, but I can't seem to find any examples on how to send (post) an IAsyncEnumerable<T>
to a Web API method.
Is that not possible?
If you are using HttpClient
it's not straight-forward to pass an asynchronous stream to it, as none of the existing HttpContent
classes support that. The following custom content class can do this:
// only pass the getLength lambda if you are 100% sure the size
public class PullingStreamContent(Func<Stream, CancellationToken, Task> streamWriter, Func<long?>? getLength = null)
: HttpContent
{
private readonly Func<Stream, CancellationToken, Task> _streamWriter = streamWriter;
private readonly Func<long?>? _getLength = getLength;
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) =>
_streamWriter(stream, default);
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) =>
_streamWriter(stream, cancellationToken);
protected override bool TryComputeLength(out long length)
{
var l = _getLength?.Invoke();
length = l.GetValueOrDefault();
return l.HasValue;
}
}
Then you can pass an IAsyncEnumerable
like this:
private async Task SendMyAsync(CancellationToken cancellationToken)
{
using var content = new PullingStreamContent(async (outputStream, ct) =>
{
IAsyncEnumerable<SomeClass> enumerable = GetSomeAsyncEnumerable(cancellationToken);
// must be directly of type IAsyncEnumerable not a derived type
await _jsonSerializer.SerializeAsync(outputStream, enumerable, cancellationToken: ct);
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "https://someUrl");
request.Content = upstreamContent;
using var response = await _httpClient.SendAsync(upstreamRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
using var responseStream = await response.ReadAsStreamAsync(cancellationToken);
// do stuff with response
}