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:
- https://jsonplaceholder.typicode.com: API fittizia gratuita per test e creazione di prototipi.
- https://www.example.com: questo dominio è destinato all'uso negli esempi illustrativi nei documenti.
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 variabilestatic
. Secondo le linee guida, è consigliabile riutilizzare le istanzeHttpClient
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:
- Impostazione di HttpClient.DefaultRequestHeaders.
- Applicazione di un oggetto non predefinito HttpClient.Timeout.
- Specifica dell'oggetto HttpClient.DefaultRequestVersion.
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 metodoSendAsync
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.
Nota
Anche se la destinazione sono i dispositivi Android (come lo sviluppo .NET MAUI), è necessario aggiungere android:usesCleartextTraffic="true"
a <application></application>
in AndroidManifest.xml. In questo modo si abilita il traffico non crittografato, come le richieste HTTP, che altrimenti è disabilitato per impostazione predefinita a causa dei criteri di sicurezza di 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.
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).
- ByteArrayContent: fornisce contenuto HTTP basato su una matrice di byte.
- FormUrlEncodedContent: fornisce contenuto HTTP per le tuple nome/valore codificate usando il tipo MIME
"application/x-www-form-urlencoded"
. - JsonContent: fornisce contenuto HTTP basato su JSON.
- MultipartContent: fornisce una raccolta di oggetti HttpContent che vengono serializzati usando la specifica del tipo MIME
"multipart/*"
. - MultipartFormDataContent: fornisce un contenitore per il contenuto codificato usando il tipo MIME
"multipart/form-data"
. - ReadOnlyMemoryContent: fornisce contenuto HTTP basato su un oggetto ReadOnlyMemory<T>.
- StreamContent: fornisce contenuto HTTP basato su un flusso.
- StringContent: fornisce contenuto HTTP basato su una stringa.
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 richiestaPOST
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 scriveTodo
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 richiestaPUT
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 scriveTodo
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 variabiliHTTP_PROXY
e/oHTTPS_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à awww.example.com
, ma non aexample.com
.NO_PROXY=example.com
(senza punto iniziale) non corrisponderà awww.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:
- La destinazione contiene un nome flat (nessun punto nell'URL).
- La destinazione contiene un indirizzo di loopback (Loopback o IPv6Loopback) o la destinazione contiene un IPAddress assegnato al computer locale.
- 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: