HTTP Client Best Practices
Properly call external APIs from your ASP.NET Core application on Plesk.
Using IHttpClientFactory (Recommended)
// Program.cs - Register named client
builder.Services.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp");
});
// Or typed client
builder.Services.AddHttpClient<GitHubService>(client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
});
Using Named Client
public class MyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<GitHubUser?> GetUserAsync(string username)
{
var client = _clientFactory.CreateClient("GitHub");
var response = await client.GetAsync($"users/{username}");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<GitHubUser>();
}
return null;
}
}
Typed Client Pattern
public class GitHubService
{
private readonly HttpClient _client;
public GitHubService(HttpClient client)
{
_client = client;
}
public async Task<IEnumerable<Repository>?> GetRepositoriesAsync(string user)
{
return await _client.GetFromJsonAsync<IEnumerable<Repository>>(
$"users/{user}/repos");
}
}
// Register
builder.Services.AddHttpClient<GitHubService>(client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
});
POST Requests with JSON
public async Task<Order?> CreateOrderAsync(CreateOrderDto dto)
{
var response = await _client.PostAsJsonAsync("orders", dto);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>();
}
Adding Resilience with Polly
// Install: dotnet add package Microsoft.Extensions.Http.Polly
builder.Services.AddHttpClient<MyApiService>()
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
.AddTransientHttpErrorPolicy(policy =>
policy.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Authentication Headers
// API Key
builder.Services.AddHttpClient("PaymentApi", client =>
{
client.BaseAddress = new Uri("https://api.payment.com/");
client.DefaultRequestHeaders.Add("X-Api-Key", "your-api-key");
});
// Bearer Token
builder.Services.AddHttpClient("SecureApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
})
.AddHttpMessageHandler<AuthTokenHandler>();
// Token handler
public class AuthTokenHandler : DelegatingHandler
{
private readonly ITokenService _tokenService;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await _tokenService.GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
Error Handling
public async Task<Result<User>> GetUserAsync(int id)
{
try
{
var response = await _client.GetAsync($"users/{id}");
if (response.IsSuccessStatusCode)
{
var user = await response.Content.ReadFromJsonAsync<User>();
return Result<User>.Success(user!);
}
return response.StatusCode switch
{
HttpStatusCode.NotFound => Result<User>.Failure("User not found"),
HttpStatusCode.Unauthorized => Result<User>.Failure("Unauthorized"),
_ => Result<User>.Failure($"Error: {response.StatusCode}")
};
}
catch (HttpRequestException ex)
{
return Result<User>.Failure($"Network error: {ex.Message}");
}
}
Best Practices
- Always use IHttpClientFactory, never new HttpClient()
- Configure timeouts appropriately
- Implement retry policies for transient failures
- Use circuit breakers for failing services
- Log HTTP requests in development
- Handle all error responses gracefully