Working with JSON data is essential when building web applications and APIs in C#. HttpClient
provides robust support for sending and receiving JSON data. This guide covers modern approaches using both System.Text.Json
and Newtonsoft.Json
.
Sending JSON Data
Modern Approach with System.Text.Json (.NET Core 3.0+)
The recommended approach for modern .NET applications uses System.Text.Json
for serialization:
using System;
using System.Net.Http;
using System.Net.Http.Json; // For JSON extension methods
using System.Text.Json;
using System.Threading.Tasks;
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
class Program
{
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main()
{
var user = new User
{
Name = "John Doe",
Age = 30,
Email = "john@example.com"
};
try
{
// Using PostAsJsonAsync (recommended for .NET 5+)
var response = await httpClient.PostAsJsonAsync("https://api.example.com/users", user);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {responseContent}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP Error: {ex.Message}");
}
catch (JsonException ex)
{
Console.WriteLine($"JSON Error: {ex.Message}");
}
}
}
Manual Serialization Approach
For more control over serialization options:
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main()
{
var user = new User
{
Name = "Jane Smith",
Age = 25,
Email = "jane@example.com"
};
// Configure serialization options
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
try
{
// Manual serialization
var jsonString = JsonSerializer.Serialize(user, options);
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://api.example.com/users", content);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Created user: {responseBody}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Receiving JSON Data
Using GetFromJsonAsync (.NET 5+)
The simplest approach for deserializing JSON responses:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
public class ApiResponse<T>
{
public bool Success { get; set; }
public T Data { get; set; }
public string Message { get; set; }
}
class Program
{
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main()
{
try
{
// Direct deserialization
var user = await httpClient.GetFromJsonAsync<User>("https://api.example.com/users/1");
Console.WriteLine($"User: {user.Name}, Age: {user.Age}");
// Getting a collection
var users = await httpClient.GetFromJsonAsync<List<User>>("https://api.example.com/users");
foreach (var u in users)
{
Console.WriteLine($"User: {u.Name}");
}
// Getting wrapped response
var response = await httpClient.GetFromJsonAsync<ApiResponse<User>>("https://api.example.com/users/1");
if (response.Success)
{
Console.WriteLine($"Retrieved: {response.Data.Name}");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP Error: {ex.Message}");
}
catch (JsonException ex)
{
Console.WriteLine($"JSON Parsing Error: {ex.Message}");
}
}
}
Manual Deserialization with Error Handling
For more control over error handling and response processing:
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main()
{
try
{
var response = await httpClient.GetAsync("https://api.example.com/users/1");
if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
var user = JsonSerializer.Deserialize<User>(jsonString, options);
Console.WriteLine($"Name: {user.Name}, Age: {user.Age}");
}
else
{
Console.WriteLine($"Error: {response.StatusCode} - {response.ReasonPhrase}");
if (response.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("User not found");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Using Newtonsoft.Json (Legacy/Alternative)
If you prefer or need to use Newtonsoft.Json:
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
class Program
{
private static readonly HttpClient httpClient = new HttpClient();
static async Task Main()
{
var user = new User { Name = "Bob Wilson", Age = 35 };
try
{
// Sending JSON
var jsonString = JsonConvert.SerializeObject(user);
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://api.example.com/users", content);
response.EnsureSuccessStatusCode();
// Receiving JSON
var responseBody = await response.Content.ReadAsStringAsync();
var createdUser = JsonConvert.DeserializeObject<User>(responseBody);
Console.WriteLine($"Created: {createdUser.Name}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Advanced Scenarios
Handling Different Content Types
public static async Task<T> GetJsonAsync<T>(this HttpClient client, string url)
{
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var contentType = response.Content.Headers.ContentType?.MediaType;
if (contentType != "application/json")
{
throw new InvalidOperationException($"Expected JSON content, got {contentType}");
}
var jsonString = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(jsonString);
}
Custom JSON Options
public static class JsonConfig
{
public static readonly JsonSerializerOptions DefaultOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
}
// Usage
var user = await httpClient.GetFromJsonAsync<User>("https://api.example.com/users/1", JsonConfig.DefaultOptions);
Best Practices
Use IHttpClientFactory: For production applications, use dependency injection with
IHttpClientFactory
to manage HttpClient instances properly.Configure JSON Options: Set up consistent JSON serialization options across your application.
Handle Errors Gracefully: Always handle HTTP errors, JSON parsing errors, and network timeouts.
Use Strongly Typed Models: Define classes that match your JSON structure for better type safety.
Set Timeouts: Configure appropriate timeouts for your HTTP requests.
// Example with IHttpClientFactory and proper configuration
public class UserService
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
}
public async Task<User> GetUserAsync(int id)
{
try
{
return await _httpClient.GetFromJsonAsync<User>($"users/{id}", _jsonOptions);
}
catch (HttpRequestException ex)
{
throw new ServiceException($"Failed to retrieve user {id}", ex);
}
}
public async Task<User> CreateUserAsync(User user)
{
try
{
var response = await _httpClient.PostAsJsonAsync("users", user, _jsonOptions);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<User>(_jsonOptions);
}
catch (HttpRequestException ex)
{
throw new ServiceException("Failed to create user", ex);
}
}
}
This comprehensive approach ensures robust JSON handling with proper error management and follows modern .NET best practices.