Search code examples
c#asp.net-core.net-coredotnet-httpclient

Write stream directly to HttpClient


Example flow:

  1. Load file from file system
  2. Modify file by embedding custom id
  3. Send modified file to external system with http.

Current implementation:

public async Task EmbedIdAndSendAsync(string filePath, string id, HttpClient httpClient)
{
    using (Stream stream = File.OpenRead(filePath))
    using (PdfDocument pdf = new PdfDocument(stream))
    using (MemoryStream modifiedStream = new MemoryStream())
    {
        pdf.EmbedId(id);

        pdf.WriteTo(modifiedStream);

        modifiedStream.Position = 0;

        await httpClient.PostAsync(
            "files/upload",
            new MultipartFormDataContent {
                { new StringContent(id), "file[id]" },
                { new StreamContent(modifiedStream), "file[stream]", Path.GetFileName(filePath) }
            }
        );
    }
}

How would I write file modifications directly to HttpClient's network stream without having to use an intermediary memory stream?


Solution

  • The answer is you have to define your own HttpContent.

    In this way you kinda invert the task and HttpClient will call your code when needed and only if needed (because maybe there's no network connection or any other error)

    sealed class PdfContent : HttpContent
    {
        string filePath;
        string id;
    
        public PdfContent(string filePath, string id)
        {
            this.filePath = filePath;
            this.id = id;
            // Put something here to define content or just do it in the outer code
            Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
        }
    
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
        {
            // Here you can write to a stream directly
            using Stream fileStream = File.OpenRead(filePath);
            using PdfDocument pdf = new PdfDocument(fileStream);
            pdf.EmbedId(id);
            // Use async + cancellationToken if there's one
            pdf.WriteTo(stream);
            return Task.CompletedTask;
        }
    
        // This is just a technical compatibility override to support cancellation
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
        {
            return SerializeToStreamAsync(stream, context, CancellationToken.None);
        }
    
        protected override bool TryComputeLength(out long length)
        {
            // Return something if you know length or zero if not
            length = 0;
            return false;
        }
    }
    

    Now just post it.

        await httpClient.PostAsync(
            "files/upload",
            new MultipartFormDataContent {
                { new StringContent(id), "file[id]" },
                { new PdfContent(filePath, id), "file[stream]" }
            }
        );