Condividi tramite


Effettuare richieste HTTP con la classe HttpClient

Questo articolo illustra come effettuare richieste HTTP e gestire le risposte con la classe HttpClient.

Importante

Tutte le richieste HTTP di esempio in questo articolo sono destinate a uno degli URL seguenti:

Gli endpoint HTTP restituiscono in genere dati JavaScript Object Notation (JSON), ma non sempre. Per praticità, l'facoltativo System.Net.Http.Json pacchetto NuGet fornisce diversi metodi di estensione per HttpClient e oggetti HttpContent che eseguono la serializzazione e la deserializzazione automatica usando il pacchetto 📦 System.Text.Json NuGet. Gli esempi in questo articolo richiamano l'attenzione sui luoghi in cui sono disponibili queste estensioni.

Suggerimento

Tutto il codice sorgente a cui si fa riferimento in questo articolo è disponibile nel repository GitHub: .NET Docs.

Creare un oggetto HttpClient

La maggior parte degli esempi in questo articolo riutilizza la stessa istanza di HttpClient, in modo da poter configurare l'istanza una sola volta e usarla per gli esempi rimanenti. Per creare un oggetto HttpClient, usare il costruttore della classe HttpClient. Per ricevere ulteriori informazioni, consultare l'articolo Linee guida per l'uso di 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"),
};

Il codice completa le attività seguenti:

  • Istanziare una nuova istanza di HttpClient come variabile static. In base alle linee guida , l'approccio consigliato consiste nel riutilizzare HttpClient istanze durante il ciclo di vita dell'applicazione.
  • Impostare la proprietà HttpClient.BaseAddress su "https://jsonplaceholder.typicode.com".

Questa istanza di HttpClient usa l'indirizzo di base per effettuare richieste successive. Per applicare altre configurazioni, prendere in considerazione le API seguenti:

Suggerimento

In alternativa, è possibile creare istanze HttpClient utilizzando un approccio basato su pattern factory che consente di configurare un numero qualsiasi di client e di utilizzarli come servizi di iniezione delle dipendenze. Per ulteriori informazioni, vedere IHttpClientFactory con .NET.

Effettuare una richiesta HTTP

Per effettuare una richiesta HTTP, chiamare uno dei metodi API seguenti:

Metodo HTTP API (Interfaccia di Programmazione delle Applicazioni)
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

Una richiesta di USER SPECIFIED indica che il metodo SendAsync accetta qualsiasi oggetto HttpMethod valido.

Avviso

L'esecuzione di richieste HTTP è considerata un lavoro associato a I/O di rete. Esiste un metodo HttpClient.Send sincrono, ma è consigliabile usare invece le API asincrone, a meno che non si abbia un buon motivo.

Nota

Durante lo sviluppo per dispositivi Android, ad esempio con .NET MAUI, è necessario aggiungere la definizione di android:usesCleartextTraffic="true" alla sezione <application></application> nel file AndroidManifest.xml. Questa impostazione abilita il traffico non crittografato, ad esempio le richieste HTTP, che altrimenti è disabilitato per impostazione predefinita a causa dei criteri di sicurezza android. Si considerino le seguenti impostazioni XML di esempio:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:usesCleartextTraffic="true"></application>
  <!-- omitted for brevity -->
</manifest>

Per altre informazioni, vedere Abilitare il traffico di rete non crittografato per il dominio localhost.

Informazioni sul contenuto HTTP

Il tipo HttpContent viene usato per rappresentare un corpo dell'entità HTTP e le intestazioni di contenuto corrispondenti. Per i metodi HTTP (o metodi di richiesta) che richiedono un corpo (POST, PUT, PATCH), usare la classe HttpContent per specificare il corpo della richiesta. La maggior parte degli esempi illustra come preparare la sottoclasse StringContent con un payload JSON, ma esistono altre sottoclassi per diversi tipi di contenuto (MIME).

La classe HttpContent viene usata anche per rappresentare il corpo della risposta della classe HttpResponseMessage, accessibile nella proprietà HttpResponseMessage.Content.

Usare una richiesta HTTP GET

Una richiesta di GET non deve inviare un corpo. Questa richiesta viene usata (come indica il nome del metodo) per recuperare (o ottenere) dati da una risorsa. Per effettuare una richiesta HTTP GET in base a un'istanza di HttpClient e a un oggetto Uri, usare il metodo 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
    //   }
}

Il codice completa le attività seguenti:

  • Effettua una richiesta GET verso l'endpoint "https://jsonplaceholder.typicode.com/todos/3".
  • Verificare che la risposta sia riuscita.
  • Scrivere i dettagli della richiesta nella console.
  • Leggere il corpo della risposta come stringa.
  • Scrivere il corpo della risposta JSON nella console.

Il metodo WriteRequestToConsole è un'estensione personalizzata che non fa parte del framework. Per informazioni sull'implementazione, prendere in considerazione il codice C# seguente:

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}");        
    }
}

Questa funzionalità viene usata per scrivere i dettagli della richiesta nella console nel seguente formato:

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

Ad esempio, la richiesta di GET all'endpoint "https://jsonplaceholder.typicode.com/todos/3" restituisce il messaggio seguente:

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

Creare la richiesta HTTP GET da JSON

L'endpoint https://jsonplaceholder.typicode.com/todos restituisce una matrice JSON di oggetti Todo. La struttura JSON è simile al formato seguente:

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

L'oggetto C# Todo viene definito come segue:

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

Si tratta di un tipo record class, con proprietà facoltative Id, Title,Completed e UserId. Per ricevere ulteriori informazioni sul tipo record, consultare l'articolo Introduzione ai tipi di record in C#. Per deserializzare automaticamente le richieste di GET in un oggetto C# fortemente tipizzato, usare il metodo di estensione GetFromJsonAsync che fa parte del pacchetto 📦 System.Net.Http.Json NuGet.

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 }
}

Il codice completa le attività seguenti:

  • Effettuare una richiesta di GET per "https://jsonplaceholder.typicode.com/todos?userId=1&completed=false".

    La stringa di query rappresenta i criteri di filtro per la richiesta. Quando il comando ha esito positivo, la risposta viene deserializzata automaticamente in un oggetto List<Todo>.

  • Scrivere i dettagli della richiesta nella console, insieme a ogni oggetto Todo.

Usare una richiesta HTTP POST

Una richiesta POST invia dati al server per l'elaborazione. L'intestazione Content-Type della richiesta indica il tipo MIME inviato dal corpo. Per effettuare una richiesta HTTP POST in base a un'istanza di HttpClient e a un oggetto Uri, usare il metodo 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
    //   }
}

Il codice completa le attività seguenti:

  • Preparare un'istanza di StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettuare una richiesta di POST all'endpoint "https://jsonplaceholder.typicode.com/todos".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.
  • Scrivere il corpo della risposta come stringa nella console.

Creare la richiesta HTTP POST come JSON

Per serializzare automaticamente gli argomenti della richiesta POST e deserializzare le risposte in oggetti C# fortemente tipizzati, usare il metodo di estensione PostAsJsonAsync che fa parte del pacchetto 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 }
}

Il codice completa le attività seguenti:

  • Serializzare l'istanza di Todo come JSON ed effettuare una richiesta di POST all'endpoint "https://jsonplaceholder.typicode.com/todos".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.
  • Deserializzare il corpo della risposta in un'istanza di Todo e scrivere l'oggetto Todo nella console.

Usare una richiesta HTTP PUT

Il metodo di richiesta PUT sostituisce una risorsa esistente o ne crea uno nuovo usando il payload del corpo della richiesta. Per effettuare una richiesta HTTP PUT in base a un'istanza di HttpClient e a un oggetto Uri, usare il metodo 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
    //   }
}

Il codice completa le attività seguenti:

  • Preparare un'istanza di StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettuare una richiesta di PUT all'endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta con il corpo della risposta JSON nella console.

Creare la richiesta HTTP PUT come JSON

Per serializzare automaticamente gli argomenti della richiesta PUT e deserializzare le risposte in oggetti C# fortemente tipizzati, usare il metodo di estensione PutAsJsonAsync che fa parte del pacchetto 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 }
}

Il codice completa le attività seguenti:

  • Serializzare l'istanza di Todo come JSON ed effettuare una richiesta di PUT all'endpoint "https://jsonplaceholder.typicode.com/todos/5".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.
  • Deserializzare il corpo della risposta in un'istanza di Todo e scrivere gli oggetti Todo sulla console.

Usare una richiesta HTTP PATCH

La richiesta PATCH è un aggiornamento parziale di una risorsa esistente. Questa richiesta non crea una nuova risorsa e non è progettata per sostituire una risorsa esistente. Questo metodo aggiorna invece solo parzialmente una risorsa. Per effettuare una richiesta HTTP PATCH in base a un'istanza di HttpClient e a un oggetto Uri, usare il metodo 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
    //   }
}

Il codice completa le attività seguenti:

  • Preparare un'istanza di StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettuare una richiesta di PATCH all'endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta con il corpo della risposta JSON nella console.

Non esistono metodi di estensione per le richieste PATCH nel pacchetto NuGet System.Net.Http.Json.

Usare una richiesta HTTP DELETE

Una richiesta di DELETE rimuove una risorsa esistente e la richiesta è idempotente, ma non sicura. Più richieste di DELETE alle stesse risorse producono lo stesso risultato, ma la richiesta influisce sullo stato della risorsa. Per effettuare una richiesta HTTP DELETE in base a un'istanza di HttpClient e a un oggetto Uri, usare il metodo 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
    //   {}
}

Il codice completa le attività seguenti:

  • Effettuare una richiesta di DELETE all'endpoint "https://jsonplaceholder.typicode.com/todos/1".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.

Suggerimento

La risposta a una richiesta di DELETE (proprio come una richiesta di PUT) potrebbe includere o meno del contenuto.

Esplorare la richiesta HTTP HEAD

La richiesta HEAD è simile a una richiesta GET. Anziché restituire la risorsa, questa richiesta restituisce solo le intestazioni associate alla risorsa. Una risposta alla richiesta HEAD non restituisce un corpo. Per effettuare una richiesta HTTP HEAD in base a un'istanza di HttpClient e a un oggetto Uri, utilizzare il metodo HttpClient.SendAsync con il tipo HttpMethod impostato su 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
}

Il codice completa le attività seguenti:

  • Effettuare una richiesta di HEAD all'endpoint "https://www.example.com/".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.
  • Scorrere tutte le intestazioni di risposta e scrivere ogni intestazione nella console.

Esplorare la richiesta HTTP OPTIONS

La richiesta OPTIONS viene usata per identificare i metodi HTTP supportati da un server o endpoint. Per effettuare una richiesta HTTP OPTIONS in base a un'istanza di HttpClient e a un oggetto Uri, utilizzare il metodo HttpClient.SendAsync con il tipo HttpMethod impostato su 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
}

Il codice completa le attività seguenti:

  • Inviare una richiesta HTTP OPTIONS all'endpoint "https://www.example.com/".
  • Verificare che la risposta sia riuscita e scrivere i dettagli della richiesta nella console.
  • Iterare su tutte le intestazioni del contenuto della risposta e scrivere ogni intestazione nella console.

Esplorare la richiesta HTTP TRACE

La richiesta TRACE può essere utile per il debug perché fornisce un loopback a livello di applicazione del messaggio di richiesta. Per effettuare una richiesta di TRACE HTTP, creare un HttpRequestMessage usando il tipo di HttpMethod.Trace:

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

Attenzione

Non tutti i server HTTP supportano il metodo HTTP TRACE. Questo metodo può esporre una vulnerabilità di sicurezza se usata senza senso. Per ricevere ulteriori informazioni, consultare l'articolo Open Web Application Security Project (OWASP): Cross Site Tracing (tracciamento tra siti).

Gestire una risposta HTTP

Quando si gestisce una risposta HTTP, si interagisce con il tipo di HttpResponseMessage. Diversi membri vengono usati per valutare la validità di una risposta. Il codice di stato HTTP è disponibile nella proprietà HttpResponseMessage.StatusCode.

Si supponga di inviare una richiesta in base a un'istanza client:

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

Per assicurarsi che il response sia OK (codice di stato HTTP 200), è possibile valutare il valore come illustrato nell'esempio seguente:

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

Esistono altri codici di stato HTTP che rappresentano una risposta corretta, ad esempio CREATED (codice di stato HTTP 201), ACCEPTED (codice di stato HTTP 202), NO CONTENT (codice di stato HTTP 204) e RESET CONTENT (codice di stato HTTP 205). È possibile usare la proprietà HttpResponseMessage.IsSuccessStatusCode per valutare anche questi codici, il che garantisce che il codice di stato della risposta sia compreso nell'intervallo 200-299:

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

Se è necessario che il framework generi l'errore di HttpRequestException, è possibile chiamare il metodo HttpResponseMessage.EnsureSuccessStatusCode():

response.EnsureSuccessStatusCode();

Questo codice genera un errore HttpRequestException se il codice di stato della risposta non rientra nell'intervallo 200-299.

Esplora le risposte valide del contenuto HTTP

Con una risposta valida, è possibile accedere al corpo della risposta usando la proprietà Content. Il corpo è disponibile come istanza di HttpContent, che è possibile usare per accedere al corpo come flusso, matrice di byte o stringa.

Il codice seguente usa l'oggetto responseStream per leggere il corpo della risposta:

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

È possibile usare oggetti diversi per leggere il corpo della risposta. Usare l'oggetto responseByteArray per leggere il corpo della risposta:

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

Usare l'oggetto responseString per leggere il corpo della risposta:

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

Quando si conosce un endpoint HTTP restituisce JSON, è possibile deserializzare il corpo della risposta in qualsiasi oggetto C# valido usando il pacchetto NuGet System.Net.Http.Json :

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

In questo codice, il valore result è il corpo della risposta deserializzato come tipo T.

Usare la gestione degli errori HTTP

Quando una richiesta HTTP ha esito negativo, il sistema genera l'oggetto HttpRequestException. Rilevare soltanto l'eccezione potrebbe non essere sufficiente. Esistono altre potenziali eccezioni generate che potrebbe essere utile gestire. Ad esempio, il codice chiamante potrebbe usare un token di annullamento annullato prima del completamento della richiesta. In questo scenario è possibile rilevare l'errore 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}");
}

Analogamente, quando si effettua una richiesta HTTP, se il server non risponde prima che venga superato il valore HttpClient.Timeout, viene generata la stessa eccezione. In questo scenario è possibile distinguere che il timeout si è verificato valutando la proprietà Exception.InnerException quando si rileva l'errore di 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}");
}

Nel codice, quando l'eccezione interna è di tipo TimeoutException, si verifica un timeout e il token di annullamento non cancella la richiesta.

Per valutare il codice di stato HTTP quando si intercetta l'oggetto HttpRequestException, è possibile valutare la proprietà 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}");
}

Nel codice viene chiamato il metodo EnsureSuccessStatusCode() per generare un'eccezione se la risposta non riesce. La proprietà HttpRequestException.StatusCode viene quindi valutata per determinare se la risposta è un codice 404 (codice di stato HTTP 404). Esistono diversi metodi helper sull'oggetto HttpClient che chiamano in modo implicito il metodo EnsureSuccessStatusCode per conto dell'utente.

Per la gestione degli errori HTTP, prendere in considerazione le API seguenti:

Suggerimento

Tutti i metodi HttpClient usati per effettuare richieste HTTP che non restituiscono un tipo HttpResponseMessage chiamano implicitamente il metodo EnsureSuccessStatusCode per conto dell'utente.

Quando si chiamano questi metodi, è possibile gestire l'oggetto HttpRequestException e valutare la proprietà HttpRequestException.StatusCode per determinare il codice di stato HTTP della risposta:

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}");
}

Potrebbero esserci scenari in cui è necessario generare l'oggetto HttpRequestException nel codice. Il costruttore HttpRequestException() è pubblico ed è possibile usarlo per generare un'eccezione con un messaggio personalizzato:

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}");
}

Configurare un proxy HTTP

Un proxy HTTP può essere configurato in due modi. Nella proprietà HttpClient.DefaultProxy viene specificato un valore predefinito. In alternativa, è possibile specificare un proxy nella proprietà HttpClientHandler.Proxy.

Usare un proxy predefinito globale

La proprietà HttpClient.DefaultProxy è una proprietà statica che determina il proxy predefinito usato da tutte le istanze HttpClient, se non viene impostato alcun proxy in modo esplicito nell'oggetto HttpClientHandler passato attraverso il relativo costruttore.

L'istanza predefinita restituita da questa proprietà inizializza in base a un set diverso di regole a seconda della piattaforma:

  • Windows: Leggere la configurazione del proxy dalle variabili di ambiente, oppure, se le variabili non sono definite, utilizzare le impostazioni del proxy utente.
  • macOS: leggere la configurazione del proxy dalle variabili di ambiente o se le variabili non sono definite, leggere dalle impostazioni del proxy di sistema.
  • Linux: leggere la configurazione del proxy dalle variabili di ambiente o se le variabili non sono definite, inizializzare un'istanza non configurata per ignorare tutti gli indirizzi.

L'inizializzazione della proprietà DefaultProxy nelle piattaforme basate su Windows e Unix usa le variabili di ambiente seguenti:

  • HTTP_PROXY: server proxy usato nelle richieste HTTP.
  • HTTPS_PROXY: server proxy usato nelle richieste HTTPS.
  • ALL_PROXY: il server proxy usato nelle richieste HTTP e/o HTTPS quando le variabili HTTP_PROXY e/o HTTPS_PROXY non sono definite.
  • NO_PROXY: elenco delimitato da virgole di nomi host da escludere dal proxy. Gli asterischi non sono supportati per i caratteri jolly. Usare un punto iniziale (.) quando si vuole trovare una corrispondenza con un sottodominio. Esempi: NO_PROXY=.example.com (con periodo iniziale) corrisponde www.example.com, ma non corrisponde a example.com. NO_PROXY=example.com (senza periodo iniziale) non corrisponde www.example.com. Questo comportamento potrebbe essere rivisitato in futuro per adattarsi meglio ad altri ecosistemi.

Nei sistemi in cui le variabili di ambiente fanno distinzione tra maiuscole e minuscole, i nomi delle variabili possono essere tutti minuscoli o maiuscoli. Per primi vengono controllati i nomi in minuscolo.

Il server proxy può essere un nome host o un indirizzo IP, facoltativamente seguito da due punti e da un numero di porta oppure può essere un URL di http, includendo facoltativamente un nome utente e una password per l'autenticazione proxy. L'URL deve iniziare con http, non httpse non può includere testo dopo il nome host, l'IP o la porta.

Configurare il proxy per client

La proprietà HttpClientHandler.Proxy identifica l'oggetto WebProxy da utilizzare per elaborare le richieste alle risorse Internet. Per specificare che non deve essere usato alcun proxy, impostare la proprietà Proxy sull'istanza proxy restituita dal metodo GlobalProxySelection.GetEmptyWebProxy().

Il file di configurazione del computer locale o dell'applicazione potrebbe specificare che viene usato un proxy predefinito. Se viene specificata la proprietà Proxy, le impostazioni proxy della proprietà Proxy sostituiscono il computer locale o il file di configurazione dell'applicazione e il gestore usa le impostazioni proxy specificate. Se non viene specificato alcun proxy in un file di configurazione e la proprietà Proxy non è specificata, il gestore usa le impostazioni proxy ereditate dal computer locale. Se non sono presenti impostazioni proxy, la richiesta viene inviata direttamente al server.

La classe HttpClientHandler analizza un elenco di bypass proxy con caratteri jolly ereditati dalle impostazioni del computer locale. Ad esempio, la classe HttpClientHandler analizza un elenco di bypass di tipo"nt*" dai browser come espressione regolare di tipo "nt.*". Pertanto, un URL di http://nt.com ignora il proxy usando la classe HttpClientHandler.

La classe HttpClientHandler supporta il bypass proxy locale. La classe considera una destinazione locale se è soddisfatta una delle seguenti condizioni:

  • La destinazione contiene un nome piatto (nessun punto (.) nell'URL).
  • La destinazione contiene un indirizzo di loopback (Loopback o IPv6Loopback) o la destinazione contiene una proprietà IPAddress assegnata al computer locale.
  • Il suffisso di dominio della destinazione corrisponde al suffisso di dominio del computer locale, come definito nella proprietà DomainName.

Per altre informazioni sulla configurazione di un proxy, vedere le API seguenti:

Passaggi successivi