To attach a file to an HttpClient request in C#, you'll use MultipartFormDataContent
to create a multipart/form-data request. This is the standard approach for file uploads in web applications.
Basic File Upload
Here's a complete example of uploading a file:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
public class FileUploadExample
{
private static readonly HttpClient httpClient = new HttpClient();
public static async Task UploadFileAsync()
{
string url = "https://api.example.com/upload";
string filePath = @"C:\Documents\document.pdf";
try
{
using var multipartContent = new MultipartFormDataContent();
// Add the file
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var fileContent = new StreamContent(fileStream);
// Set content type (optional but recommended)
fileContent.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/pdf");
multipartContent.Add(fileContent, "file", Path.GetFileName(filePath));
// Send the request
var response = await httpClient.PostAsync(url, multipartContent);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Upload successful: {result}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Multiple Files Upload
You can upload multiple files in a single request:
public static async Task UploadMultipleFilesAsync()
{
string url = "https://api.example.com/upload-multiple";
string[] filePaths = {
@"C:\Documents\file1.txt",
@"C:\Documents\file2.jpg"
};
using var multipartContent = new MultipartFormDataContent();
foreach (string filePath in filePaths)
{
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
var fileContent = new StreamContent(fileStream);
// Set appropriate content type
string contentType = GetContentType(Path.GetExtension(filePath));
fileContent.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse(contentType);
multipartContent.Add(fileContent, "files", Path.GetFileName(filePath));
}
var response = await httpClient.PostAsync(url, multipartContent);
response.EnsureSuccessStatusCode();
}
private static string GetContentType(string extension)
{
return extension.ToLower() switch
{
".txt" => "text/plain",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".pdf" => "application/pdf",
_ => "application/octet-stream"
};
}
Upload with Additional Form Data
Often you need to send additional form fields along with files:
public static async Task UploadFileWithFormDataAsync()
{
string url = "https://api.example.com/upload";
string filePath = @"C:\Documents\image.jpg";
using var multipartContent = new MultipartFormDataContent();
// Add text fields
multipartContent.Add(new StringContent("John Doe"), "userName");
multipartContent.Add(new StringContent("Profile Picture"), "description");
multipartContent.Add(new StringContent("public"), "visibility");
// Add the file
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse("image/jpeg");
multipartContent.Add(fileContent, "avatar", Path.GetFileName(filePath));
var response = await httpClient.PostAsync(url, multipartContent);
response.EnsureSuccessStatusCode();
}
Upload from Byte Array
If you have file data as a byte array instead of a file path:
public static async Task UploadFromByteArrayAsync(byte[] fileData, string fileName)
{
string url = "https://api.example.com/upload";
using var multipartContent = new MultipartFormDataContent();
using var byteContent = new ByteArrayContent(fileData);
byteContent.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/octet-stream");
multipartContent.Add(byteContent, "file", fileName);
var response = await httpClient.PostAsync(url, multipartContent);
response.EnsureSuccessStatusCode();
}
Progress Tracking
For large files, you might want to track upload progress:
public class ProgressStreamContent : StreamContent
{
private readonly Stream _stream;
private readonly IProgress<long> _progress;
public ProgressStreamContent(Stream stream, IProgress<long> progress) : base(stream)
{
_stream = stream;
_progress = progress;
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
const int bufferSize = 4096;
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await _stream.ReadAsync(buffer, 0, bufferSize)) > 0)
{
await stream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
_progress?.Report(totalBytesRead);
}
}
}
// Usage
public static async Task UploadWithProgressAsync()
{
string filePath = @"C:\large-file.zip";
var progress = new Progress<long>(bytesUploaded =>
{
Console.WriteLine($"Uploaded {bytesUploaded} bytes");
});
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var progressContent = new ProgressStreamContent(fileStream, progress);
using var multipartContent = new MultipartFormDataContent();
multipartContent.Add(progressContent, "file", Path.GetFileName(filePath));
var response = await httpClient.PostAsync("https://api.example.com/upload", multipartContent);
response.EnsureSuccessStatusCode();
}
Best Practices
- Use a shared HttpClient instance to avoid socket exhaustion
- Always specify Content-Type for better server compatibility
- Handle exceptions properly including network timeouts
- Dispose resources using
using
statements - Validate file size before upload to prevent memory issues
- Use async/await consistently for better performance
Common Issues
- File locked: Ensure no other process is using the file
- Large files: Consider streaming for files over 85KB to avoid Large Object Heap
- Content-Type: Some servers require specific content types
- Field names: Match the exact field names expected by the server API
This approach works for most file upload scenarios and is compatible with standard web APIs and REST services.