Fazer solicitações HTTP com a classe HttpClient

Neste artigo, você aprenderá a fazer solicitações HTTP e lidar com respostas com a classe HttpClient.

Importante

Todas as solicitações HTTP de exemplo visam uma das seguintes URLs:

Os pontos de extremidade HTTP normalmente retornam dados JSON (JavaScript Object Notation), mas nem sempre. Para conveniência, o pacote NuGet opcional System.Net.Http.Json fornece vários métodos de extensão para HttpClient e HttpContent que executam serialização e desserialização automáticas usando System.Text.Json. Os exemplos que seguem chamam a atenção para locais em que essas extensões estão disponíveis.

Dica

Todo o código-fonte deste artigo está disponível no repositório GitHub: .NET Docs.

Criar uma HttpClient

A maioria dos exemplos a seguir reutiliza a mesma instância HttpClient e, portanto, só é preciso configurar uma vez. Para criar um HttpClient, use o construtor de classe HttpClient. Para obter mais informações, confira Diretrizes para usar 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"),
};

O código anterior:

  • Cria uma nova instância HttpClient como uma variável static. De acordo com as diretrizes, é recomendável reutilizar instâncias HttpClient durante o ciclo de vida do aplicativo.
  • Define o HttpClient.BaseAddress como "https://jsonplaceholder.typicode.com".

Essa instância HttpClient usa o endereço base ao fazer solicitações subsequentes. Para aplicar outra configuração, considere:

Dica

Como alternativa, você pode criar instâncias HttpClient usando uma abordagem de padrão de fábrica que permite configurar qualquer número de clientes e consumi-las como serviços de injeção de dependência. Para obter mais informações, consulte fábrica de clientes HTTP com o .NET.

Fazer uma solicitação HTTP

Para fazer uma solicitação HTTP, você chama qualquer uma das seguintes APIs:

Método HTTP 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

Uma solicitação USER SPECIFIED indica que o método SendAsync aceita qualquer HttpMethod válido.

Aviso

Fazer solicitações HTTP é considerado um trabalho associado a E/S de rede. Embora haja um método síncrono HttpClient.Send, é recomendável usar as APIs assíncronas, a menos que você tenha uma boa razão para não fazer isso.

Conteúdo HTTP

O tipo HttpContent é usado para representar um corpo de entidade HTTP e cabeçalhos de conteúdo correspondentes. Para métodos HTTP (ou métodos de solicitação) que exigem um corpo, POST, PUT e PATCH, você usa a classe HttpContent para especificar o corpo da solicitação. A maioria dos exemplos mostra como preparar a subclasse StringContent com um conteúdo JSON, mas existem outras subclasses para diferentes tipos de conteúdo (MIME).

A classe HttpContent também é usada para representar o corpo da resposta do HttpResponseMessage, acessível na propriedade HttpResponseMessage.Content.

HTTP Get

Uma solicitação GET não deve enviar um corpo e é usada (como o nome do método indica) para recuperar (ou obter) dados de um recurso. Para fazer uma solicitação HTTP GET, considerando um HttpClient e um URI, use o método HttpClient.GetAsync:

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($"{jsonResponse}\n");

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 3,
    //     "title": "fugiat veniam minus",
    //     "completed": false
    //   }
}

O código anterior:

  • Faz uma solicitação GET para "https://jsonplaceholder.typicode.com/todos/3".
  • Garante que a resposta seja bem-sucedida.
  • Grava os detalhes da solicitação no console.
  • Lê o corpo de resposta como uma cadeia.
  • Grava o corpo da resposta JSON no console.

O WriteRequestToConsole é um método de extensão personalizado que não faz parte da estrutura, mas, se você tiver curiosidade sobre como ele é implementado, considere o seguinte código C#:

static class HttpResponseMessageExtensions
{
    internal static void WriteRequestToConsole(this HttpResponseMessage response)
    {
        if (response is null)
        {
            return;
        }

        var request = response.RequestMessage;
        Console.Write($"{request?.Method} ");
        Console.Write($"{request?.RequestUri} ");
        Console.WriteLine($"HTTP/{request?.Version}");        
    }
}

Essa funcionalidade é usada para gravar os detalhes da solicitação no console no seguinte formulário:

<HTTP Request Method> <Request URI> <HTTP/Version>

Por exemplo, a solicitação GET para https://jsonplaceholder.typicode.com/todos/3 gera a seguinte mensagem:

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

HTTP Get do JSON

O ponto de extremidade https://jsonplaceholder.typicode.com/todos retorna uma matriz JSON de objetos "todo". Sua estrutura JSON é semelhante à seguinte:

[
  {
    "userId": 1,
    "id": 1,
    "title": "example title",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "another example title",
    "completed": true
  },
]

O objeto Todo do C# é definido da seguinte maneira:

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

É um tipo record class, com propriedades opcionais Id, Title, Completed e UserId. Para obter mais informações sobre o tipo record, confira Introdução aos tipos de registro em C#. Para desserializar automaticamente solicitações GET em um objeto C# fortemente tipado, use o método de extensão GetFromJsonAsync que faz parte do pacote NuGet System.Net.Http.Json.

static async Task GetFromJsonAsync(HttpClient httpClient)
{
    var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
        "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, Id = 1, Title = delectus aut autem, Completed = False }
    //   Todo { UserId = 1, Id = 2, Title = quis ut nam facilis et officia qui, Completed = False }
    //   Todo { UserId = 1, Id = 3, Title = fugiat veniam minus, Completed = False }
    //   Todo { UserId = 1, Id = 5, Title = laboriosam mollitia et enim quasi adipisci quia provident illum, Completed = False }
    //   Todo { UserId = 1, Id = 6, Title = qui ullam ratione quibusdam voluptatem quia omnis, Completed = False }
    //   Todo { UserId = 1, Id = 7, Title = illo expedita consequatur quia in, Completed = False }
    //   Todo { UserId = 1, Id = 9, Title = molestiae perspiciatis ipsa, Completed = False }
    //   Todo { UserId = 1, Id = 13, Title = et doloremque nulla, Completed = False }
    //   Todo { UserId = 1, Id = 18, Title = dolorum est consequatur ea mollitia in culpa, Completed = False }
}

No código anterior:

  • É feita uma solicitação GET para "https://jsonplaceholder.typicode.com/todos?userId=1&completed=false".
    • A cadeia de caracteres de consulta representa os critérios de filtragem da solicitação.
  • A resposta é desserializada automaticamente em um List<Todo> quando bem-sucedida.
  • Os detalhes da solicitação são gravados no console, juntamente com cada objeto Todo.

HTTP Post

Uma solicitação POST envia dados ao servidor para processamento. O cabeçalho da solicitação Content-Type significa qual tipo MIME o corpo está enviando. Para fazer uma solicitação HTTP POST, considerando um HttpClient e um Uri, use o método HttpClient.PostAsync:

static async Task PostAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            userId = 77,
            id = 1,
            title = "write code sample",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PostAsync(
        "todos",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   {
    //     "userId": 77,
    //     "id": 201,
    //     "title": "write code sample",
    //     "completed": false
    //   }
}

O código anterior:

  • Prepara uma instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faz uma solicitação POST para "https://jsonplaceholder.typicode.com/todos".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.
  • Grava o corpo da resposta como uma cadeia de caracteres no console.

HTTP Post como JSON

Para serializar automaticamente argumentos de solicitação POST e desserializar respostas em objetos C# fortemente tipados, use o método de extensão PostAsJsonAsync que faz parte do pacote NuGet System.Net.Http.Json.

static async Task PostAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PostAsJsonAsync(
        "todos", 
        new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   Todo { UserId = 9, Id = 201, Title = Show extensions, Completed = False }
}

O código anterior:

  • Serializa a instância Todo como JSON e faz uma solicitação POST para "https://jsonplaceholder.typicode.com/todos".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.
  • Desserializa o corpo da resposta em uma instância Todo e grava o Todo no console.

HTTP Put

O método de solicitação PUT substitui um recurso existente ou cria um novo usando o conteúdo do corpo da solicitação. Para fazer uma solicitação HTTP PUT, considerando um HttpClient e um URI, use o método HttpClient.PutAsync:

static async Task PutAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new 
        {
            userId = 1,
            id = 1,
            title = "foo bar",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PutAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "foo bar",
    //     "completed": false
    //   }
}

O código anterior:

  • Prepara uma instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faz uma solicitação PUT para "https://jsonplaceholder.typicode.com/todos/1".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação e o corpo da resposta JSON no console.

HTTP Put como JSON

Para serializar automaticamente argumentos de solicitação PUT e desserializar respostas em objetos C# fortemente tipados, use o PutAsJsonAsync método de extensão que faz parte do pacote NuGet System.Net.Http.Json.

static async Task PutAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PutAsJsonAsync(
        "todos/5",
        new Todo(Title: "partially update todo", Completed: true));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/5 HTTP/1.1
    //   Todo { UserId = , Id = 5, Title = partially update todo, Completed = True }
}

O código anterior:

  • Serializa a instância Todo como JSON e faz uma solicitação PUT para "https://jsonplaceholder.typicode.com/todos/5".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.
  • Desserializa o corpo da resposta em uma instância Todo e grava o Todo no console.

HTTP Patch

A solicitação PATCH é uma atualização parcial de um recurso existente. Ele não cria um novo recurso e não se destina a substituir um recurso existente. Em vez disso, ela atualiza um recurso apenas parcialmente. Para fazer uma solicitação HTTP PATCH, considerando um HttpClient e um URI, use o método HttpClient.PatchAsync:

static async Task PatchAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            completed = true
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PatchAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   PATCH https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "delectus aut autem",
    //     "completed": true
    //   }
}

O código anterior:

  • Prepara uma instância StringContent com o corpo JSON da solicitação (tipo MIME de "application/json").
  • Faz uma solicitação PATCH para "https://jsonplaceholder.typicode.com/todos/1".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação e o corpo da resposta JSON no console.

Não existem métodos de extensão para solicitações PATCH no pacote NuGet System.Net.Http.Json.

HTTP Delete

Uma solicitação DELETE exclui um recurso existente. Uma solicitação DELETE é idempotente, mas não segura, o que significa que várias solicitações DELETE para os mesmos recursos produzem o mesmo resultado, mas a solicitação afeta o estado do recurso. Para fazer uma solicitação HTTP DELETE, considerando um HttpClient e um URI, use o método HttpClient.DeleteAsync:

static async Task DeleteAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   DELETE https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {}
}

O código anterior:

  • Faz uma solicitação DELETE para "https://jsonplaceholder.typicode.com/todos/1".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.

Dica

A resposta a uma solicitação DELETE (assim como uma solicitação PUT) pode ou não incluir um corpo.

HTTP Head

A solicitação HEAD é semelhante a uma solicitação GET. Em vez de retornar o recurso, ela retorna apenas os cabeçalhos associados ao recurso. Uma resposta à solicitação HEAD não retorna um corpo. Para fazer uma solicitação HTTP HEAD, considerando um HttpClient e um URI, use o método HttpClient.SendAsync com o HttpMethod definido como HttpMethod.Head:

static async Task HeadAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Head, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output:
    //   HEAD https://www.example.com/ HTTP/1.1
    //   Accept-Ranges: bytes
    //   Age: 550374
    //   Cache-Control: max-age=604800
    //   Date: Wed, 10 Aug 2022 17:24:55 GMT
    //   ETag: "3147526947"
    //   Server: ECS, (cha / 80E2)
    //   X-Cache: HIT
}

O código anterior:

  • Faz uma solicitação HEAD para "https://www.example.com/".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.
  • Itera em todos os cabeçalhos de resposta, gravando cada um no console.

HTTP Options

A solicitação OPTIONS é usada para identificar a quais métodos HTTP um servidor ou ponto de extremidade dá suporte. Para fazer uma solicitação HTTP OPTIONS, considerando um HttpClient e um URI, use o método HttpClient.SendAsync com o HttpMethod definido como HttpMethod.Options:

static async Task OptionsAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Options, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Content.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output
    //   OPTIONS https://www.example.com/ HTTP/1.1
    //   Allow: OPTIONS, GET, HEAD, POST
    //   Content-Type: text/html; charset=utf-8
    //   Expires: Wed, 17 Aug 2022 17:28:42 GMT
    //   Content-Length: 0
}

O código anterior:

  • Envia uma solicitação HTTP OPTIONS para "https://www.example.com/".
  • Garante que a resposta seja bem-sucedida e grava os detalhes da solicitação no console.
  • Itera em todos os cabeçalhos de conteúdo de resposta, gravando cada um no console.

HTTP Trace

A solicitação TRACE pode ser útil para depuração, pois fornece loopback no nível do aplicativo da mensagem de solicitação. Para fazer uma solicitação HTTP TRACE, crie um HttpRequestMessage usando o HttpMethod.Trace:

using HttpRequestMessage request = new(
    HttpMethod.Trace, 
    "{ValidRequestUri}");

Cuidado

Não há suporte para o método HTTP TRACE em todos os servidores HTTP. Ele pode expor uma vulnerabilidade de segurança se usado de forma imprudente. Para obter mais informações, confira o texto sobre Rastreamento entre sites da OWASP (Open Web Application Security Project).

Lidar com uma resposta HTTP

Sempre que você estiver lidando com uma resposta HTTP, você interage com o tipo HttpResponseMessage. Vários membros são usados ao avaliar a validade de uma resposta. O código de status HTTP está disponível por meio da propriedade HttpResponseMessage.StatusCode. Imagine que você enviou uma solicitação dada uma instância de cliente:

using HttpResponseMessage response = await httpClient.SendAsync(request);

Para garantir que response seja OK (código de status HTTP 200), você pode avaliá-la conforme mostrado no exemplo a seguir:

if (response is { StatusCode: HttpStatusCode.OK })
{
    // Omitted for brevity...
}

Há outros códigos de status HTTP que representam uma resposta bem-sucedida, como CREATED (código de status HTTP 201), ACCEPTED (código de status HTTP 202), NO CONTENT (código de status HTTP 204) e RESET CONTENT (código de status HTTP 205). Você também pode usar a propriedade HttpResponseMessage.IsSuccessStatusCode para avaliar esses códigos, o que garante que o código de status de resposta esteja dentro do intervalo de 200 a 299:

if (response.IsSuccessStatusCode)
{
    // Omitted for brevity...
}

Se você precisar que a estrutura gere HttpRequestException, poderá chamar o método HttpResponseMessage.EnsureSuccessStatusCode():

response.EnsureSuccessStatusCode();

Esse código gera um HttpRequestException, se o código de status de resposta não estiver dentro do intervalo 200-299.

Respostas de conteúdo válidas para HTTP

Com uma resposta válida, você pode acessar o corpo da resposta usando a propriedade Content. O corpo está disponível como uma instância HttpContent, que você pode usar para acessar o corpo como um fluxo, matriz de bytes ou cadeia de caracteres:

await using Stream responseStream =
    await response.Content.ReadAsStreamAsync();

No código anterior, responseStream pode ser usado para ler o corpo da resposta.

byte[] responseByteArray = await response.Content.ReadAsByteArrayAsync();

No código anterior, responseByteArray pode ser usado para ler o corpo da resposta.

string responseString = await response.Content.ReadAsStringAsync();

No código anterior, responseString pode ser usado para ler o corpo da resposta.

Por fim, quando você sabe que um ponto de extremidade HTTP retorna JSON, você pode desserializar o corpo da resposta em qualquer objeto C# válido usando o pacote NuGet System.Net.Http.Json:

T? result = await response.Content.ReadFromJsonAsync<T>();

No código anterior, result é o corpo da resposta desserializado como o tipo T.

Tratamento de erros HTTP

Quando uma solicitação HTTP falha, o HttpRequestException é gerado. Capturar essa exceção sozinha pode não ser suficiente, pois há outras exceções potenciais geradas que talvez você queira considerar tratar. Por exemplo, o código de chamada pode ter usado um token de cancelamento que foi cancelado antes da solicitação ser concluída. Nesse cenário, você capturaria TaskCanceledException:

using var cts = new CancellationTokenSource();
try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100", cts.Token);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
    // When the token has been canceled, it is not a timeout.
    Console.WriteLine($"Canceled: {ex.Message}");
}

Da mesma forma, ao fazer uma solicitação HTTP, se o servidor não responder antes que o HttpClient.Timeout seja excedido, a mesma exceção será gerada. No entanto, nesse cenário, você pode distinguir que o tempo limite ocorreu avaliando Exception.InnerException ao capturar TaskCanceledException:

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100");
}
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException tex)
{
    Console.WriteLine($"Timed out: {ex.Message}, {tex.Message}");
}

No código anterior, quando a exceção interna é TimeoutException, o tempo limite ocorreu, e a solicitação não foi cancelada pelo token de cancelamento.

Para avaliar o código de status HTTP ao capturar um HttpRequestException, você pode avaliar a propriedade HttpRequestException.StatusCode:

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/doesNotExist");

    response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

No código anterior, o método EnsureSuccessStatusCode() é chamado para gerar uma exceção caso a resposta não seja bem-sucedida. A propriedade HttpRequestException.StatusCode é avaliada em seguida para determinar se a resposta foi um 404 (código de status HTTP 404). Há vários métodos auxiliares em HttpClient que implicitamente chamam EnsureSuccessStatusCode em seu nome. Considere as seguintes APIs:

Dica

Todos os métodos HttpClient usados para fazer solicitações HTTP que não retornam um HttpResponseMessage, implicitamente chamam EnsureSuccessStatusCode em seu nome.

Ao chamar esses métodos, você pode tratar de HttpRequestException e avaliar a propriedade HttpRequestException.StatusCode para determinar o código de status HTTP da resposta:

try
{
    // These extension methods will throw HttpRequestException
    // with StatusCode set when the HTTP request status code isn't 2xx:
    //
    //   GetByteArrayAsync
    //   GetStreamAsync
    //   GetStringAsync

    using var stream = await httpClient.GetStreamAsync(
        "https://localhost:5001/doesNotExists");
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

Pode haver cenários em que você precisa lançar HttpRequestException no seu código. O construtor HttpRequestException() é público e você pode usá-lo para gerar uma exceção com uma mensagem personalizada:

try
{
    using var response = await httpClient.GetAsync(
        "https://localhost:5001/doesNotExists");

    // Throw for anything higher than 400.
    if (response is { StatusCode: >= HttpStatusCode.BadRequest })
    {
        throw new HttpRequestException(
            "Something went wrong", inner: null, response.StatusCode);
    }
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    Console.WriteLine($"Not found: {ex.Message}");
}

Proxy HTTP

Um proxy HTTP pode ser configurado de duas maneiras. Um padrão é especificado na propriedade HttpClient.DefaultProxy. Como alternativa, você pode especificar um proxy na propriedade HttpClientHandler.Proxy.

Proxy padrão global

O HttpClient.DefaultProxy é uma propriedade estática que determina o proxy padrão que todas as instâncias HttpClient usam se nenhum proxy for definido explicitamente no HttpClientHandler transmitido pelo seu construtor.

A instância padrão retornada por essa propriedade é inicializada seguindo um conjunto diferente de regras, dependendo da plataforma:

  • Para Windows: lê a configuração de proxy a partir de variáveis de ambiente ou, se elas não estiverem definidas, a partir das configurações de proxy do usuário.
  • Para macOS: lê a configuração de proxy a partir de variáveis de ambiente ou, se elas não estiverem definidas, a partir das configurações de proxy do sistema.
  • Para Linux: lê a configuração de proxy a partir de variáveis de ambiente ou, se elas não estiverem definidas, essa propriedade inicializa uma instância não configurada que ignora todos os endereços.

As variáveis de ambiente usadas para inicialização DefaultProxy em plataformas baseadas em Windows e Unix são:

  • HTTP_PROXY: o servidor proxy usado em solicitações HTTP.
  • HTTPS_PROXY: o servidor proxy usado em solicitações HTTPS.
  • ALL_PROXY: o servidor proxy usado em solicitações HTTP e/ou HTTPS, caso HTTP_PROXY e/ou HTTPS_PROXY não estejam definidos.
  • NO_PROXY: uma lista separada por vírgulas de nomes de host que devem ser excluídos do proxy. Não há suporte para asteriscos para curingas; use um ponto à esquerda caso você queira corresponder a um subdomínio. Exemplos: NO_PROXY=.example.com (com ponto à esquerda) corresponderá a www.example.com, mas não corresponderá a example.com. NO_PROXY=example.com (sem ponto à esquerda) não corresponderá a www.example.com. Esse comportamento pode ser revisitado no futuro para corresponder melhor a outros ecossistemas.

Em sistemas nos quais as variáveis de ambiente diferenciam maiúsculas de minúsculas, os nomes de variáveis podem apenas em minúsculas ou apenas em maiúsculas. Os nomes em minúsculas são verificados primeiro.

O servidor proxy pode ser um nome de host ou endereço IP, opcionalmente seguido por dois-pontos e um número de porta, ou pode ser uma URL http, incluindo opcionalmente um nome de usuário e senha para autenticação de proxy. A URL deve ser iniciada com http, e não https, e não pode incluir nenhum texto após o nome do host, IP ou porta.

Proxy por cliente

A propriedade HttpClientHandler.Proxy identifica o objeto WebProxy a ser usado para processar solicitações para recursos da Internet. Para especificar que nenhum proxy deve ser usado, defina a propriedade Proxy como a instância de proxy retornada pelo método GlobalProxySelection.GetEmptyWebProxy().

O computador local ou o arquivo de configuração do aplicativo pode especificar que um proxy padrão seja usado. Se a propriedade Proxy for especificada, as configurações de proxy da propriedade Proxy substituirão o computador local ou o arquivo de configuração de aplicativo, e o manipulador usará as configurações de proxy especificadas. Se nenhum proxy for especificado em um arquivo de configuração e a propriedade Proxy não for especificada, o manipulador usará as configurações de proxy herdadas do computador local. Se não houver configurações de proxy, a solicitação será enviada diretamente ao servidor.

A classe HttpClientHandler analisa uma lista de bypass de proxy com caracteres curinga herdados das configurações do computador local. Por exemplo, a classe HttpClientHandler analisa uma lista de bypass de "nt*" a partir de navegadores como uma expressão regular de "nt.*". Portanto, uma URL de http://nt.com ignoraria o proxy usando a classe HttpClientHandler.

A classe HttpClientHandler dá suporte ao bypass de proxy local. A classe considera um destino como local se qualquer uma das seguintes condições for atendida:

  1. O destino contém um nome simples (nenhum ponto na URL).
  2. O destino contém um endereço de loopback (Loopback ou IPv6Loopback) ou o destino contém um IPAddress atribuído ao computador local.
  3. O sufixo de domínio do destino corresponde ao sufixo de domínio do computador local (DomainName).

Para obter mais informações sobre como configurar um proxy, confira:

Confira também