Search code examples
c#asp.net-core-webapiiasyncenumerable

POST IAsyncEnumerable to ASP.NET Core Web API


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?


Solution

  • 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
    }