Effettuare richieste HTTP con la classe HttpClient

In questo articolo si apprenderà come effettuare richieste HTTP e gestire le risposte con la classe HttpClient.

Importante

Tutti gli esempi di richieste HTTP hanno come destinazione uno degli URL seguenti:

Gli endpoint HTTP restituiscono in genere dati JavaScript Object Notation (JSON), ma non sempre. Per praticità, il pacchetto opzionale NuGet System.Net.Http.Json fornisce diversi metodi di estensione per HttpClient e HttpContent che eseguono la serializzazione automatica e la deserializzazione usando System.Text.Json. Gli esempi che seguono richiamano l'attenzione su dove è possibile trovare le seguenti estensioni.

Suggerimento

Tutti i codici sorgente presenti in questo articolo sono disponibili nel repository GitHub: .NET Docs.

Creare una chiave HttpClient

La maggior parte degli esempi seguenti riutilizza la stessa istanza HttpClient e pertanto deve essere configurata una sola volta. 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 precedente:

  • Crea una nuova istanza HttpClient come variabile static. Secondo le linee guida, è consigliabile riutilizzare le istanze HttpClient durante il ciclo di vita dell'applicazione.
  • Imposta HttpClient.BaseAddress su "https://jsonplaceholder.typicode.com".

Questa istanza HttpClient usa l'indirizzo di base durante l'esecuzione di richieste successive. Per applicare altre configurazioni, è necessario prendere in considerazione:

Suggerimento

In alternativa, è possibile creare istanze HttpClient usando un approccio basato su modello factory che consente di configurare un numero qualsiasi di client e utilizzarli come servizi di inserimento delle dipendenze. Per ricevere ulteriori informazioni, consultare l'articolo IHttpClientFactory con .NET.

Effettuare una richiesta HTTP

Per effettuare una richiesta HTTP, chiamare una delle seguenti API:

Metodo 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

Richiesta A USER SPECIFIED indica che il metodo SendAsync accetta qualsiasi oggetto valido HttpMethod.

Avviso

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

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 e PATCH si usa 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 anche utilizzata per rappresentare il corpo della risposta dell'oggetto HttpResponseMessage, accessibile nella proprietà HttpResponseMessage.Content.

HTTP Get

Una richiesta GET non deve inviare un corpo e viene usata (come indicato dal nome del metodo) per recuperare (o ottenere) dati da una risorsa. Per effettuare una richiesta HTTP GET, in base a HttpClient e un 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 precedente:

  • Effettua una richiesta GET a "https://jsonplaceholder.typicode.com/todos/3".
  • Assicura che la risposta venga eseguita correttamente.
  • Scrive i dettagli della richiesta nella console.
  • Legge il corpo della risposta come stringa.
  • Scrive il corpo della risposta JSON nella console.

WriteRequestToConsole è un metodo di estensione personalizzato che non fa parte del framework, ma se si vuole comprendere come viene implementato, considerare il seguente codice 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}");        
    }
}

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 GET a https://jsonplaceholder.typicode.com/todos/3 restituisce il messaggio seguente:

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

HTTP Get da JSON

L'endpoint https://jsonplaceholder.typicode.com/todos restituisce una matrice JSON di oggetti "todo". La struttura JSON è simile alla 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 GET in un oggetto C# fortemente tipizzato, usare il metodo di estensione GetFromJsonAsync che fa parte del pacchetto 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 }
}

Nel codice precedente:

  • Viene effettuata una richiesta GET a "https://jsonplaceholder.typicode.com/todos?userId=1&completed=false".
    • La stringa di query rappresenta i criteri di filtro per la richiesta.
  • In caso di esito positivo, la risposta viene automaticamente deserializzata in un oggetto List<Todo>.
  • I dettagli della richiesta vengono scritti nella console, insieme a ogni oggetto Todo.

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 HttpClient e 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 precedente:

  • Prepara un'istanza StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettua una richiesta POST a "https://jsonplaceholder.typicode.com/todos".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.
  • Scrive il corpo della risposta come stringa nella console.

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 precedente:

  • Serializza l'istanza Todo come JSON e effettua una richiesta POST a "https://jsonplaceholder.typicode.com/todos".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.
  • Deserializza il corpo della risposta in un'istanza Todo e scrive Todo nella console.

HTTP Put

Il metodo di richiesta PUT sostituisce una risorsa esistente o ne crea una nuova usando il payload del corpo della richiesta. Per effettuare una richiesta HTTP PUT, in base a HttpClient e a un 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 precedente:

  • Prepara un'istanza StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettua una richiesta PUT a "https://jsonplaceholder.typicode.com/todos/1".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta e il corpo della risposta JSON nella console.

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 precedente:

  • Serializza l'istanza Todo come JSON e effettua una richiesta PUT a "https://jsonplaceholder.typicode.com/todos/5".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.
  • Deserializza il corpo della risposta in un'istanza Todo e scrive Todo nella console.

HTTP Patch

La richiesta PATCH è un aggiornamento parziale di una risorsa esistente. Non crea una nuova risorsa e non è progettata per sostituire una risorsa esistente. Aggiorna invece una risorsa solo parzialmente. Per effettuare una richiesta HTTP PATCH, in base a HttpClient e a un 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 precedente:

  • Prepara un'istanza StringContent con il corpo JSON della richiesta (tipo MIME di "application/json").
  • Effettua una richiesta PATCH a "https://jsonplaceholder.typicode.com/todos/1".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta e il corpo della risposta JSON nella console.

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

Eliminazione HTTP

Una richiesta DELETE elimina una risorsa esistente. Una richiesta DELETE è idempotente ma non sicura, ovvero più richieste 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 HttpClient e a un 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 precedente:

  • Effettua una richiesta DELETE a "https://jsonplaceholder.typicode.com/todos/1".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.

Suggerimento

La risposta a una richiesta DELETE (proprio come una richiesta PUT) può includere o meno un corpo.

Intestazione HTTP

La richiesta HEAD è simile a una richiesta GET. Anziché restituire la risorsa, 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 HttpClient e a un URI, usare il metodo HttpClient.SendAsync con 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 precedente:

  • Effettua una richiesta HEAD a "https://www.example.com/".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.
  • Esegue l'iterazione di tutte le intestazioni della risposta, scrivendole nella console.

Opzioni HTTP

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 HttpClient e a un URI, usare il metodo HttpClient.SendAsync con 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 precedente:

  • Invia una richiesta HTTP OPTIONS a "https://www.example.com/".
  • Assicura che la risposta abbia esito positivo e scrive i dettagli della richiesta nella console.
  • Esegue l'iterazione di tutte le intestazioni del contenuto della risposta, scrivendole nella console.

Traccia HTTP

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 HTTP TRACE, creare un oggetto HttpRequestMessage usando HttpMethod.Trace:

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

Attenzione

Il metodo HTTP TRACE non è supportato da tutti i server HTTP. Può esporre a vulnerabilità della sicurezza se utilizzato in maniera imprudente. Per ricevere ulteriori informazioni, consultare l'articolo Open Web Application Security Project (OWASP): Cross Site Tracing (tracciamento tra siti).

Gestire una risposta HTTP

Ogni volta che si gestisce una risposta HTTP, si interagisce con il tipo HttpResponseMessage. Quando si valuta la validità di una risposta, vengono usati diversi membri. Il codice di stato HTTP è disponibile tramite la proprietà HttpResponseMessage.StatusCode. Si supponga di aver inviato una richiesta in base a un'istanza client:

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

Per assicurarsi che response sia OK (codice di stato HTTP 200), è possibile eseguire una valutazione 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 HttpRequestException, è possibile chiamare il metodo HttpResponseMessage.EnsureSuccessStatusCode():

response.EnsureSuccessStatusCode();

Questo codice genera un'eccezione HttpRequestException se il codice di stato della risposta non è compreso nell'intervallo 200-299.

Risposte di contenuto valide HTTP

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

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

Nel codice precedente, responseStream può essere usato per leggere il corpo della risposta.

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

Nel codice precedente, responseByteArray può essere usato per leggere il corpo della risposta.

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

Nel codice precedente, responseString può essere usato per leggere il corpo della risposta.

Infine, quando 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>();

Nel codice precedente, result rappresenta il corpo della risposta deserializzato come tipo T.

Gestione degli errori HTTP

Quando una richiesta HTTP ha esito negativo, viene generata l'eccezione HttpRequestException. Intercettare l'eccezione da sola potrebbe non essere sufficiente, in quanto sono presenti altre potenziali eccezioni generate che potrebbe essere necessario gestire. Ad esempio, il codice chiamante potrebbe aver usato un token di annullamento che è stato annullato prima del completamento della richiesta. In questo scenario si intercetta l'eccezione 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 superata l'eccezione HttpClient.Timeout, viene generata la stessa eccezione. In questo scenario, tuttavia, è possibile comprendere che il timeout si è verificato quando Exception.InnerException ha intercettato 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 precedente, quando l'eccezione interna è TimeoutException, si è verificato un timeout e la richiesta non è stata annullata dal token di annullamento.

Per valutare il codice di stato HTTP quando si intercetta un'eccezione 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 precedente, viene chiamato il metodo EnsureSuccessStatusCode() per generare un'eccezione se la risposta non ha esito positivo. La proprietà HttpRequestException.StatusCode viene quindi valutata per determinare se la risposta è un codice 404 (codice di stato HTTP 404). Esistono diversi metodi di supporto su HttpClient che chiamano in modo implicito EnsureSuccessStatusCode per conto dell'utente, considerare le API seguenti:

Suggerimento

Tutti i metodi HttpClient usati per effettuare richieste HTTP che non restituiscono HttpResponseMessage chiamano in modo implicito EnsureSuccessStatusCode per conto dell'utente.

Quando si chiamano questi metodi, è possibile gestire 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 essere presenti scenari in cui è necessario generare l'eccezione 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}");
}

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.

Proxy predefinito globale

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'HttpClientHandler passato attraverso il relativo costruttore.

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

  • Per Windows: legge la configurazione del proxy dalle variabili di ambiente o, qualora queste non fossero state definite, dalle impostazioni proxy dell'utente.
  • Per macOS: legge la configurazione del proxy dalle variabili di ambiente o, qualora queste non fossero state definite, dalle impostazioni proxy dell'utente.
  • Per Linux: legge la configurazione del proxy dalle variabili di ambiente o, nel caso in cui non siano state definite, questa proprietà inizializza un'istanza non configurata che ignora tutti gli indirizzi.

Le variabili di ambiente usate per l'inizializzazione di DefaultProxy nelle piattaforme basate su Windows e Unix sono le seguenti:

  • HTTP_PROXY: il server proxy usato nelle richieste HTTP.
  • HTTPS_PROXY: il server proxy usato nelle richieste HTTPS.
  • ALL_PROXY: il server proxy usato nelle richieste HTTP e/o HTTPS nel caso in cui le variabili HTTP_PROXY e/o HTTPS_PROXY non siano state definite.
  • NO_PROXY: elenco delimitato da virgole di nomi host che devono essere esclusi dal proxy. Gli asterischi non sono supportati per i caratteri jolly; usare un punto iniziale nel caso in cui si voglia trovare una corrispondenza con un sottodominio. Ad esempio: NO_PROXY=.example.com (con punto iniziale) corrisponderà a www.example.com, ma non a example.com. NO_PROXY=example.com (senza punto iniziale) non corrisponderà a www.example.com. Questo comportamento potrebbe essere rivisitato in futuro per adattarsi meglio ad altri ecosistemi.

Nei sistemi in cui le variabili di ambiente rilevano la 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, eventualmente seguito da due punti e da un numero di porta oppure può essere un URL http, che include eventualmente un nome utente e una password per l'autenticazione proxy. L'URL deve iniziare con http e non con https; inoltre, non può includere testo dopo il nome host, l'IP o la porta.

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 può specificare che viene utilizzato un proxy predefinito. Se viene specificata la proprietà Proxy, le impostazioni proxy della proprietà Proxy sostituiscono il file di configurazione del computer locale o dell'applicazione e il gestore utilizza le impostazioni proxy specificate. Se non viene specificato alcun proxy in un file di configurazione e la proprietà Proxy non viene 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.*". Quindi, un URL di tipo 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:

  1. La destinazione contiene un nome flat (nessun punto nell'URL).
  2. La destinazione contiene un indirizzo di loopback (Loopback o IPv6Loopback) o la destinazione contiene un IPAddress assegnato al computer locale.
  3. Il suffisso di dominio della destinazione corrisponde al suffisso di dominio del computer locale (DomainName).

Per ottenere ulteriori informazioni sulla configurazione di un proxy, consultare i seguenti articoli:

Vedi anche