Skip to the content.

How to do a curl -F multipart upload in C#

To do a curl -F multipart upload in C#, pass -F 'field=@path/to/file' (plus any -F 'name=value' text fields) to Curl.ExecuteAsync, or call CurlRequestBuilder.Post(url).WithMultipartForm(fields, files). The request is sent as multipart/form-data, like the curl CLI.

dotnet add package CurlDotNet

The curl command

using CurlDotNet;

var result = await Curl.ExecuteAsync("""
    curl -F 'file=@report.txt' \
         -F 'description=Quarterly report' \
         https://httpbin.org/post
    """);

Console.WriteLine(result.StatusCode);  // 200
Console.WriteLine(result.Body);        // httpbin echoes "files" and "form"

@ means “read this file”. Relative paths resolve against the process’s current directory (Environment.CurrentDirectory), so use absolute paths in services. A missing file throws CurlReadErrorException (curl exit code 26) before anything is sent.

The request body follows curl’s format: curl’s boundary style, quoted field names, no Content-Type on text fields, and for files a filename= plus a content type guessed from the extension.

Files and fields with the fluent builder

WithMultipartForm takes two dictionaries: text fields, and field name to file path.

using CurlDotNet.Core;

var result = await CurlRequestBuilder
    .Post("https://httpbin.org/post")
    .WithMultipartForm(
        fields: new Dictionary<string, string> { ["description"] = "Quarterly report" },
        files:  new Dictionary<string, string> { ["file"] = "report.txt" })
    .ExecuteAsync();

Console.WriteLine(result.StatusCode);

For a single file with no other fields, .WithFile("file", "report.txt") is shorter. WithFormData builds a URL-encoded body, not multipart fields, so combining it with WithFile drops the text fields; use WithMultipartForm whenever you need both.

Upload several files

Repeat -F, or add more entries to the files dictionary:

using CurlDotNet;

var result = await Curl.ExecuteAsync(
    "curl -F 'invoice=@invoice.pdf' -F 'receipt=@receipt.png' https://httpbin.org/post");

Console.WriteLine(result.StatusCode);

Raw body upload instead of multipart

Some APIs (S3 pre-signed URLs, many blob stores) want the file as the raw request body rather than a form. Use -T / --upload-file, which sends a PUT with the file’s bytes (when the URL ends in /, the file name is appended, as in curl):

using CurlDotNet;

var result = await Curl.ExecuteAsync(
    "curl -T photo.jpg -H 'Content-Type: image/jpeg' https://httpbin.org/put");

Console.WriteLine(result.StatusCode);

-X PUT --data-binary @photo.jpg sends the same bytes. Use --data-binary rather than -d @file: -d strips carriage returns, newlines and NUL bytes from the file, as curl does.

With the builder, read the bytes yourself and call .WithBinaryData(File.ReadAllBytes("photo.jpg")).

Field modifiers

curl’s -F syntax is supported in full: