To send a multipart/form-data
request using HttpClient
in C#, you need to use the MultipartFormDataContent
class. This is commonly used for file uploads and form submissions that contain both text data and binary content. This guide provides comprehensive examples for various scenarios.
Quick Example
using var httpClient = new HttpClient();
using var multipartContent = new MultipartFormDataContent();
// Add text fields
multipartContent.Add(new StringContent("John Doe"), "username");
multipartContent.Add(new StringContent("user@example.com"), "email");
// Add file from byte array
var fileBytes = File.ReadAllBytes("document.pdf");
multipartContent.Add(new ByteArrayContent(fileBytes), "file", "document.pdf");
var response = await httpClient.PostAsync("https://api.example.com/upload", multipartContent);
Step-by-Step Implementation
Step 1: Set up HttpClient
Use HttpClientFactory
or a singleton HttpClient
instance to avoid socket exhaustion:
using System.Net.Http;
// Option 1: Using HttpClientFactory (recommended for dependency injection)
public class FileUploadService
{
private readonly HttpClient _httpClient;
public FileUploadService(HttpClient httpClient)
{
_httpClient = httpClient;
}
}
// Option 2: Static instance for simple console applications
private static readonly HttpClient client = new HttpClient();
Step 2: Create MultipartFormDataContent
Build your multipart content by adding different types of data:
using var multipartContent = new MultipartFormDataContent();
// Text fields
multipartContent.Add(new StringContent("John Doe"), "name");
multipartContent.Add(new StringContent("Description text"), "description");
// Text field with specific encoding
multipartContent.Add(new StringContent("Special chars: àáâã", Encoding.UTF8), "title");
// JSON data as string
var jsonData = JsonSerializer.Serialize(new { id = 123, active = true });
multipartContent.Add(new StringContent(jsonData, Encoding.UTF8, "application/json"), "metadata");
Step 3: Add Files
There are several ways to add files to your multipart request:
From File Path (using FileStream)
var filePath = "path/to/document.pdf";
using var fileStream = File.OpenRead(filePath);
using var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
multipartContent.Add(streamContent, "document", Path.GetFileName(filePath));
From Byte Array
var fileBytes = File.ReadAllBytes("image.jpg");
var byteArrayContent = new ByteArrayContent(fileBytes);
byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
multipartContent.Add(byteArrayContent, "image", "image.jpg");
From Memory Stream
using var memoryStream = new MemoryStream();
// ... populate memory stream with data
memoryStream.Position = 0;
using var streamContent = new StreamContent(memoryStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
multipartContent.Add(streamContent, "textfile", "data.txt");
Step 4: Send the Request
try
{
var requestUri = "https://api.example.com/upload";
using var response = await httpClient.PostAsync(requestUri, multipartContent);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Upload successful: {responseContent}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP error: {ex.Message}");
}
catch (TaskCanceledException ex)
{
Console.WriteLine($"Request timeout: {ex.Message}");
}
Complete Examples
File Upload with Form Data
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
public class FileUploadExample
{
private static readonly HttpClient client = new HttpClient();
public static async Task Main(string[] args)
{
await UploadFileWithFormData();
}
private static async Task UploadFileWithFormData()
{
try
{
using var multipartContent = new MultipartFormDataContent();
// Add form fields
multipartContent.Add(new StringContent("John Doe"), "uploaderName");
multipartContent.Add(new StringContent("Important document"), "description");
multipartContent.Add(new StringContent("documents"), "category");
// Add file
var filePath = "document.pdf";
if (File.Exists(filePath))
{
var fileBytes = await File.ReadAllBytesAsync(filePath);
var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
multipartContent.Add(fileContent, "file", Path.GetFileName(filePath));
// Send request
var response = await client.PostAsync("https://api.example.com/documents", multipartContent);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Upload successful: {result}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Multiple File Upload
public async Task UploadMultipleFiles(string[] filePaths)
{
using var multipartContent = new MultipartFormDataContent();
// Add text field
multipartContent.Add(new StringContent("Batch upload"), "operation");
// Add multiple files
foreach (var filePath in filePaths)
{
if (File.Exists(filePath))
{
var fileBytes = await File.ReadAllBytesAsync(filePath);
var fileContent = new ByteArrayContent(fileBytes);
// Set appropriate content type based on file extension
var contentType = GetContentType(Path.GetExtension(filePath));
fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
// Use "files[]" for multiple files or unique names
multipartContent.Add(fileContent, "files[]", Path.GetFileName(filePath));
}
}
var response = await client.PostAsync("https://api.example.com/batch-upload", multipartContent);
response.EnsureSuccessStatusCode();
}
private static string GetContentType(string extension)
{
return extension.ToLower() switch
{
".pdf" => "application/pdf",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".txt" => "text/plain",
".json" => "application/json",
_ => "application/octet-stream"
};
}
With Authentication Headers
public async Task UploadWithAuth(string accessToken, string filePath)
{
// Set authentication header
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
using var multipartContent = new MultipartFormDataContent();
// Add file
var fileBytes = await File.ReadAllBytesAsync(filePath);
var fileContent = new ByteArrayContent(fileBytes);
multipartContent.Add(fileContent, "file", Path.GetFileName(filePath));
var response = await client.PostAsync("https://api.example.com/secure-upload", multipartContent);
response.EnsureSuccessStatusCode();
}
Advanced Scenarios
Custom Boundary String
var boundary = "----CustomBoundary" + DateTime.Now.Ticks.ToString("x");
using var multipartContent = new MultipartFormDataContent(boundary);
Progress Tracking for Large Files
public class ProgressStreamContent : StreamContent
{
private readonly IProgress<long> _progress;
public ProgressStreamContent(Stream content, IProgress<long> progress)
: base(content)
{
_progress = progress;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
// Implement progress tracking during upload
return base.SerializeToStreamAsync(stream, context);
}
}
Error Handling Best Practices
public async Task<bool> SafeUpload(string filePath)
{
try
{
using var multipartContent = new MultipartFormDataContent();
// Validate file exists and size
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
Console.WriteLine("File not found");
return false;
}
if (fileInfo.Length > 10 * 1024 * 1024) // 10MB limit
{
Console.WriteLine("File too large");
return false;
}
var fileBytes = await File.ReadAllBytesAsync(filePath);
var fileContent = new ByteArrayContent(fileBytes);
multipartContent.Add(fileContent, "file", fileInfo.Name);
using var response = await client.PostAsync("https://api.example.com/upload", multipartContent);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Upload successful");
return true;
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Upload failed: {response.StatusCode} - {errorContent}");
return false;
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Network error: {ex.Message}");
return false;
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"File access error: {ex.Message}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
return false;
}
}
Important Notes
- Resource Management: Always use
using
statements or manually dispose ofStreamContent
,ByteArrayContent
, andMultipartFormDataContent
- Content-Type: Set appropriate
ContentType
headers for files to ensure proper handling by the server - File Size: Consider implementing file size limits and progress tracking for large uploads
- Security: Validate file types and implement virus scanning for production applications
- Performance: Use
ByteArrayContent
for small files andStreamContent
for large files to optimize memory usage - Authentication: Add authentication headers before making the request if required by the API