C# HttpClient: The Complete Guide

The C# HttpClient class is a powerful tool that make sending HTTP requests a breeze.

In this guide, you will learn everything you need to know about using HttpClient. From the basics of making requests to advanced topics such as handling timeouts, authentication, and dependency injection.

By following the best practices outlined in this guide, you will be able to create robust and efficient C# applications that interact with web services.

C# http client graphic

HttpClient Fundamentals

In this chapter, we will cover the basics of the HttpClient class in C#. You will learn how to create and configure an HttpClient instance, send different types of requests (GET, POST, PUT, DELETE) and handle HTTP responses.

What is HttpClient?

HttpClient is a high-level class in the C# programming language that makes it easy to make HTTP calls. It is part of the System.Net.Http namespace and it is available in .Net Framework and .Net Core.

HttpClient provides a modern, flexible, and easy-to-use API for interacting with web services in C#. It is recommended for most uses, as it is the most recent, efficient and flexible class for HTTP request and response manipulation.

The HttpClient class also provides methods to read and write the headers and content of HTTP requests and responses, making it easy to extract and parse the data that you need from the responses. Additionally, it also has support for asynchronous programming out-of-the-box and it allows you to configure options such as proxy settings, cookies, and credentials using the HttpClientHandler class.

History of HttpClient

The HttpClient class was first introduced in .NET Framework 4.5, which was released in 2012. This class was introduced as an evolution over the WebClient class, which had been in the framework since .NET 2.0.

With the introduction of HttpClient, Microsoft added more modern capabilities to handle HTTP requests and responses, such as support for asynchronous programming, the ability to send and receive streams, support for modern web standards, and more. This change enabled C# developers to have a more flexible and powerful tool to interact with web services using the HTTP protocol.

It is also worth noting that HttpClient is also part of the ASP.NET Core framework which is a cross-platform, open-source web development framework that was first released in 2016.

How to use HttpClient?

Here is a step-by-step guide on how to use the HttpClient class in C#:

  1. Import the namespaces:
C#
using System.Net.Http;
  1. Create an instance of the HttpClient class:
C#
public static var client = new HttpClient();
  1. Set the request URI:
C#
var uri = "https://josipmisko.com/api/books/5";
  1. Request the resource identified by the URI:
C#
var response = await client.GetAsync(uri);
  1. Check the response status code:
C#
if (response.IsSuccessStatusCode)
{
    // continue
}
else
{
    // handle error
}
  1. Read the response content:
C#
string responseContent = await response.Content.ReadAsStringAsync();
  1. Properly dispose of the HttpClient instance:

It is recommended to reuse the HttpClient instance throughout the lifetime of the application. However, if it's no longer needed we need to dispose the resource by calling the Dispose method:

C#
client.Dispose();

HttpClient Dependency Injection

IHttpClientFactory is a factory abstraction introduced in .NET Core 2.1. to create and manage HttpClient instances with dependency injection.

It is a powerful and flexible tool that you should use to configure, create and manage the lifetime of HttpClient instances. This chapter will show you how.

How to use IHttpClientFactory?

Here's how to use IHttpClientFactory to inject the HttpClient:

  1. In Startup.cs, add the service to the container:
C#
services.AddHttpClient();
  1. Once the HttpClient or IHttpClientFactory is registered as a singleton service, you can inject it into other classes that need it:
C#
public class MyController : Controller
{
    private readonly IHttpClientFactory _clientFactory;

    public MyController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
    public async Task<IActionResult> Get()
    {
        using var client = _clientFactory.CreateClient();
    }
}
  1. Call CreateClient to get an instance of the HttpClient:
C#
using HttpClient client = _httpClientFactory.CreateClient();

Named Clients

Named clients are a feature of IHttpClientFactory that allows you to create HttpClient instances with different configurations, and assign them unique names. They are useful when you want to preserve settings when talking to a web service.

Here's an example of how to use named clients:

  1. Configure the named clients in your Startup class:
C#
services.AddHttpClient("instagram", c =>
{
    c.BaseAddress = new Uri("https://instagram.com/");
    c.Timeout = TimeSpan.FromSeconds(10);
});
  1. Inject IHttpClientFactory into your service or controller class:
C#
public class MyController : Controller
{
    private readonly IHttpClientFactory _clientFactory;

    public MyController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
}
  1. Use the named clients in your service:
C#
// ...
public async Task<IActionResult> Get()
{
    using var client = _clientFactory.CreateClient("instagram");
}

Typed Clients

A typed client is a way to encapsulate the creation and configuration of an HttpClient instance into a strongly-typed class. This allows you to use a more specific, and self-describing class when making requests.

Here's an example of how to create and use a typed client:

  1. Create a new class that represents your typed client, and use the HttpClient class as a base:
C#
public class WeatherClient
{
    private readonly HttpClient _client;

    public WeatherClient(HttpClient client)
    {
        _client = client;
        _client.BaseAddress = new Uri("https://weather-api.com/");
        _client.Timeout = TimeSpan.FromSeconds(10);
    }

    public async Task<Weather> GetCurrentWeather()
    {
        var response = await _client.GetAsync("current");
        return JsonConvert.DeserializeObject<Weather>(await response.Content.ReadAsStringAsync());
    }
}
  1. Register the typed client in the Startup class, in the ConfigureServices method:
C#
services.AddHttpClient<WeatherClient>();
  1. Inject the typed client into your service or controller class:
C#
public class MyService
{
    private readonly WeatherClient _weatherClient;

    public MyService(WeatherClient weatherClient)
    {
        _weatherClient = weatherClient;
    }
}
  1. Use the typed client:
C#
public async Task<Weather> GetWeather()
{
    return await _weatherClient.GetCurrentWeather();
}

HttpClient Best Practices

Using HttpClient instances properly is essential for performance, stability, scalability, security, and maintainability.

In this chapter, we'll discuss how to create, reuse, and dispose of HttpClient instances correctly and how to take advantage of IHttpClientFactory for managing the lifetime of HttpClient.

Reuse the HttpClient instance

HttpClient is designed to be reused. Reusing an instance of HttpClient improves performance, reduces resource usage, and helps your application scale more efficiently.

There are several reasons why you should reuse an instance of HttpClient when making multiple requests:

  1. Socket exhaustion: Each time you create a new instance of HttpClient, it creates a new socket to handle the request. Creating too many sockets can cause socket exhaustion, which can lead to performance issues. By reusing an instance of HttpClient, you reduce the number of sockets needed to run your app.

  2. Connection pooling: HttpClient uses a connection pool to manage the underlying connections to the server. When a new instance of HttpClient is created, it creates a new connection pool.

  3. DNS resolution: When HttpClient instance is first used to connect to a server, it performs a DNS resolution to determine the IP address of the server. This can be a time-consuming process. When you reuse the HttpClient instance, it will reuse the previously resolved IP address.

  4. Thread-safety: HttpClient is thread-safe. By reusing the same instance of HttpClient, you can avoid the overhead of creating and disposing of the object for each request and have the same instance handle multiple requests.

"Socket exhaustion" is a situation where a program exhausts all available sockets, preventing the program from opening new connections. This happens if we create too many connections and do not properly close them, or if we create connections too quickly.

Use Dependency Injection

The best way to reuse an HttpClient instance is to use dependency injection.

Microsoft offers the HttpClientFactory class that takes care of managing HttpClient instances for you. Here is an example of how you can register HttpClientFactory as a singleton service:

  1. In Startup.cs, add the service to the container:
C#
services.AddHttpClient();
  1. Once the HttpClient or IHttpClientFactory is registered as a singleton service, you can inject it into other classes that need it:

HttpClient Examples

HTTP GET Request Example

Here's an example of how to use the GetAsync method to make a GET request:

C#
var response = await httpClient.GetAsync("https://example.com/api/httpclient");
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();

It's a good practice to check that the response from the GET request is successful before reading the body.

HTTP POST Request Example

The HttpClient class provides several methods for making POST requests, including PostAsync, PostJsonAsync, and PostAsXmlAsync.

Here's an example of how to use PostAsync:

C#
var content = new StringContent("{\"name\":\"John Doe\",\"age\":30}", Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://example.com/api/values", content);
var responseContent = await response.Content.ReadAsStringAsync();

HTTP DELETE Request Example

The easiest way to make a DELETE request is to use the DeleteAsync method against the specified URI.

C#
var response = await httpClient.DeleteAsync("https://example.com");
var responseContent = await response.Content.ReadAsStringAsync();

HTTP PUT Request Example

C#
var content = new StringContent("{\"name\":\"John Doe\",\"age\":30}", Encoding.UTF8, "application/json");
var response = await httpClient.PutAsync("https://myapi.com/httpclient", content);

HttpClient Headers

Here's an example of how to set headers for an HttpClient request:

C#
httpClient.DefaultRequestHeaders.Add("MyCustomheader", "Custom");
httpClient.DefaultRequestHeaders.Add("User-Agent", "API");
var response = await httpClient.GetAsync("https://josipmisko.com/api/values");

HttpClient Authorization Header

Here's an example of how to set Bearer authorization headers for an HttpClient request:

C#
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", " TOKEN ");

Here's an example of how to use basic authentication:

C#

var byteArray = Encoding.ASCII.GetBytes("username:password");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var response = await httpClient.GetAsync("https://josipmisko.com");

Timeouts and Cancellation

There are a couple of ways to set a timeout when using the HttpClient class in C#:

  1. Using the Timeout property: You can set the Timeout property of the HttpClient class to a TimeSpan value that represents the timeout duration. The Timeout property is used to set the time-out value for both the request and the response.
C#
static HttpClient client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(30); // 30 seconds timeout
  1. Using the HttpClientHandler: You can set the Timeout property of the HttpClientHandler class to a TimeSpan value that represents the timeout duration.
C#
var handler = new HttpClientHandler();
handler.Timeout = TimeSpan.FromSeconds(30); // 30 seconds timeout
static HttpClient client = new HttpClient(handler);
  1. Using the CancellationToken: Pass a CancellationToken object that can be used to cancel the request after a specified time to the SendAsync method.
C#
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
var response = await client.SendAsync(request, cts.Token);

Proxy Server

Proxy servers are managed by the underlying HttpClientHandler. Here's an example how to configure a proxy server using dependency injection:

  1. In your Startup, in the ConfigureServices method, add a named client:
C#
services.AddHttpClient("MyProxyClient")
        .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() {
            Proxy = httpProxy // Configure your proxy here.
        });
  1. Use the named client:
C#
var client = httpClientFactory.CreateClient("MyProxyClient");

Process JSON with HttpClient

If you are using HttpClient to communicate with REST APIs, it's most likely you'll have to deal with JSON.

In this chapter, we'll explore extension methods offered in the .NET framework that make it easy to use HttpClient and JSON.

Sending JSON request body

C#
var httpClient = _httpClientFactory.CreateClient();
var createShortUrlResponse = await httpClient.PostAsJsonAsync($"{_appSettings.RepairIntelligenceApiBaseUrl}/shortenurl", new CreateShortenUrl
{
    Name = "Josip",
    LastName = "Misko",
});
createShortUrlResponse.EnsureSuccessStatusCode();

Reading JSON request body

To read JSON response body, you can use the ReadAsAsync method to read the response content as a JSON object:

C#
var response = await client.SendAsync(request);
MyDataTransferObject data = await response.Content.ReadAsAsync<MyDataTransferObject>();

Note: Under the hood, GetFromJsonAsync calls EnsureSuccessStatusCode, so it is expecting a status code in the 200-299 range. So, use it only when you're expecting a successful response. Otherwise, use ReadFromJsonAsync .

HttpClient FAQ

Should I use named or typed clients?

When using the named client approach, services inject IHttpClientFactory and call CreateClient every time they need an HttpClient instance. However, with typed clients, these objects are usually injected into services as transient objects - but that can be a problem if they're used in singleton services. Instead of creating indefinite instances like this, it's best to use IHttpClientFactory with SocketsHttpHandler and PooledConnectionLifetime or choose the named client approach and recreate HttpClients when necessary.

Published on