Make HTTP requests with the HttpClient class

In this article, you'll learn how to make HTTP requests and handle responses with the HttpClient class.

All of the example HTTP requests target one of the following URLs:

HTTP endpoints commonly return JavaScript Object Notation (JSON) data, but not always. For convenience, the optional System.Net.Http.Json NuGet package provides several extension methods for HttpClient and HttpContent that perform automatic serialization and deserialization using System.Text.Json . The examples that follow call attention to places where these extensions are available.

All of the source code from this article is available in the GitHub: .NET Docs repository.

Create an HttpClient

Most of the following examples reuse the same HttpClient instance, and therefore only need to be configured once. To create an HttpClient , use the HttpClient class constructor. For more information, see Guidelines for using HttpClient.

// HttpClient lifecycle management best practices: // https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use private static HttpClient sharedClient = new() < BaseAddress = new Uri("https://jsonplaceholder.typicode.com"), >; 

The preceding code:

This HttpClient instance uses the base address when making subsequent requests. To apply other configuration, consider:

Alternatively, you can create HttpClient instances using a factory-pattern approach that allows you to configure any number of clients and consume them as dependency injection services. For more information, see HTTP client factory with .NET.

Make an HTTP request

To make an HTTP request, you call any of the following APIs:

HTTP method API
GET HttpClient.GetAsync
GET HttpClient.GetByteArrayAsync
GET HttpClient.GetStreamAsync
GET HttpClient.GetStringAsync
POST HttpClient.PostAsync
PUT HttpClient.PutAsync
PATCH HttpClient.PatchAsync
DELETE HttpClient.DeleteAsync
† USER SPECIFIED HttpClient.SendAsync

† A USER SPECIFIED request indicates that the SendAsync method accepts any valid HttpMethod.

Making HTTP requests is considered network I/O-bound work. While there is a synchronous HttpClient.Send method, it is recommended to use the asynchronous APIs instead, unless you have good reason not to.

While targeting Android devices (such as with .NET MAUI development), you must add android:usesCleartextTraffic="true" to in AndroidManifest.xml. This enables clear-text traffic, such as HTTP requests, which is otherwise disabled by default due to Android security policies. Consider the following example XML settings:

HTTP content

The HttpContent type is used to represent an HTTP entity body and corresponding content headers. For HTTP methods (or request methods) that require a body, POST , PUT , and PATCH , you use the HttpContent class to specify the body of the request. Most examples show how to prepare the StringContent subclass with a JSON payload, but other subclasses exist for different content (MIME) types.

The HttpContent class is also used to represent the response body of the HttpResponseMessage, accessible on the HttpResponseMessage.Content property.

HTTP Get

A GET request shouldn't send a body and is used (as the method name indicates) to retrieve (or get) data from a resource. To make an HTTP GET request, given an HttpClient and a URI, use the HttpClient.GetAsync method:

static async Task GetAsync(HttpClient httpClient) < using HttpResponseMessage response = await httpClient.GetAsync("todos/3"); response.EnsureSuccessStatusCode() .WriteRequestToConsole(); var jsonResponse = await response.Content.ReadAsStringAsync(); Console.WriteLine($"\n"); // Expected output: // GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1 // < // "userId": 1, // "id": 3, // "title": "fugiat veniam minus", // "completed": false // >> 

The preceding code:

The WriteRequestToConsole is a custom extension method that isn't part of the framework, but if you're curious about how it's implemented, consider the following C# code:

static class HttpResponseMessageExtensions < internal static void WriteRequestToConsole(this HttpResponseMessage response) < if (response is null) < return; >var request = response.RequestMessage; Console.Write($" "); Console.Write($" "); Console.WriteLine($"HTTP/"); > > 

This functionality is used to write the request details to the console in the following form:

As an example, the GET request to https://jsonplaceholder.typicode.com/todos/3 outputs the following message:

GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1 

HTTP Get from JSON

The https://jsonplaceholder.typicode.com/todos endpoint returns a JSON array of "todo" objects. Their JSON structure resembles the following:

The C# Todo object is defined as follows:

public record class Todo( int? UserId = null, int? string? Title = null, bool? Completed = null); 

It's a record class type, with optional Id , Title , Completed , and UserId properties. For more information on the record type, see Introduction to record types in C#. To automatically deserialize GET requests into strongly-typed C# object, use the GetFromJsonAsync extension method that's part of the System.Net.Http.Json NuGet package.

static async Task GetFromJsonAsync(HttpClient httpClient) < var todos = await httpClient.GetFromJsonAsync>( "todos?userId=1&completed=false"); Console.WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1"); todos?.ForEach(Console.WriteLine); Console.WriteLine(); // Expected output: // GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1 // Todo < UserId = 1, Title = delectus aut autem, Completed = False >// Todo < UserId = 1, Title = quis ut nam facilis et officia qui, Completed = False >// Todo < UserId = 1, Title = fugiat veniam minus, Completed = False >// Todo < UserId = 1, Title = laboriosam mollitia et enim quasi adipisci quia provident illum, Completed = False >// Todo < UserId = 1, Title = qui ullam ratione quibusdam voluptatem quia omnis, Completed = False >// Todo < UserId = 1, Title = illo expedita consequatur quia in, Completed = False >// Todo < UserId = 1, Title = molestiae perspiciatis ipsa, Completed = False >// Todo < UserId = 1, Title = et doloremque nulla, Completed = False >// Todo < UserId = 1, Title = dolorum est consequatur ea mollitia in culpa, Completed = False >> 

In the preceding code: