Esercitazione: Effettuare richieste HTTP in un'app console .NET usando C #

Questa esercitazione compila un'app che genera richieste HTTP a un servizio REST in GitHub. L'app legge le informazioni in formato JSON e converte il codice JSON in oggetti C#. La conversione da JSON a oggetti C# è nota come deserializzazione.

L'esercitazione illustra come:

  • Inviare richieste HTTP.
  • Deserializzare le risposte JSON.
  • Configurare la deserializzazione con attributi.

Se si preferisce seguire insieme all'esempio finale per questa esercitazione, è possibile scaricarlo. Per istruzioni sul download, vedere Esempi ed esercitazioni.

Prerequisiti

  • .NET SDK 6.0 o versione successiva
  • Editor di codice, ad esempio [Visual Studio Code (un editor open source e multipiattaforma). È possibile eseguire l'app di esempio in Windows, Linux o macOS o in un contenitore Docker.

Creare l'app client

  1. Aprire un prompt dei comandi e creare una nuova directory per l'app. impostandola come directory corrente.

  2. Immettere il comando seguente in una finestra della console:

    dotnet new console --name WebAPIClient
    

    Questo comando crea i file di avvio per un'app "Hello World" di base. Il nome del progetto è "WebAPIClient".

  3. Passare alla directory "WebAPIClient" ed eseguire l'app.

    cd WebAPIClient
    
    dotnet run
    

    dotnet run viene eseguito dotnet restore automaticamente per ripristinare le dipendenze necessarie all'app. Viene eseguito dotnet build anche se necessario. Verrà visualizzato l'output "Hello, World!"dell'app . Nel terminale premere CTRL+C per arrestare l'app.

Creare richieste HTTP

Questa app chiama l'API GitHub per ottenere informazioni sui progetti sotto l'ombrello .NET Foundation . L'endpoint è https://api.github.com/orgs/dotnet/repos. Per recuperare informazioni, effettua una richiesta HTTP GET. I browser effettuano anche richieste HTTP GET, in modo da poter incollare tale URL nella barra degli indirizzi del browser per visualizzare le informazioni che verranno ricevute ed elaborate.

Usare la HttpClient classe per effettuare richieste HTTP. HttpClient supporta solo metodi asincroni per le API a esecuzione prolungata. I passaggi seguenti creano quindi un metodo asincrono e lo chiamano dal metodo Main.

  1. Aprire il Program.cs file nella directory del progetto e sostituirlo con quanto segue:

    await ProcessRepositoriesAsync();
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    Questo codice:

    • Sostituisce l'istruzione con una chiamata a ProcessRepositoriesAsync che usa la Console.WriteLineawait parola chiave.
    • Definisce un metodo vuoto ProcessRepositoriesAsync .
  2. Program Nella classe usare un HttpClient oggetto per gestire le richieste e le risposte sostituendo il contenuto con il C#seguente.

    using System.Net.Http.Headers;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    await ProcessRepositoriesAsync(client);
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    Questo codice:

    • Configura le intestazioni HTTP per tutte le richieste:
      • Intestazione Accept per accettare risposte JSON
      • Intestazione User-Agent. Queste intestazioni vengono controllate dal codice del server GitHub e sono necessarie per recuperare informazioni da GitHub.
  3. ProcessRepositoriesAsync Nel metodo chiamare l'endpoint GitHub che restituisce un elenco di tutti i repository nell'organizzazione .NET Foundation:

     static async Task ProcessRepositoriesAsync(HttpClient client)
     {
         var json = await client.GetStringAsync(
             "https://api.github.com/orgs/dotnet/repos");
    
         Console.Write(json);
     }
    

    Questo codice:

    • Attende l'attività restituita dal metodo chiamante HttpClient.GetStringAsync(String) . Questo metodo invia una richiesta HTTP GET all'URI specificato. Il corpo della risposta viene restituito come String, disponibile al termine dell'attività.
    • La stringa json di risposta viene stampata nella console.
  4. Compilare l'app ed eseguirla.

    dotnet run
    

    Non è presente alcun avviso di compilazione perché ora ProcessRepositoriesAsync contiene un await operatore. L'output è una visualizzazione prolungata del testo JSON.

Deserializzare il risultato JSON

La procedura seguente converte la risposta JSON in oggetti C#. Si usa la System.Text.Json.JsonSerializer classe per deserializzare JSON in oggetti.

  1. Creare un file denominato Repository.cs e aggiungere il codice seguente:

    public record class Repository(string name);
    

    Il codice precedente definisce una classe per rappresentare l'oggetto JSON restituito dall'API GitHub. Questa classe verrà usata per visualizzare un elenco di nomi di repository.

    Json per un oggetto repository contiene decine di proprietà, ma solo la name proprietà verrà deserializzata. Il serializzatore ignora automaticamente le proprietà JSON per cui non esiste alcuna corrispondenza nella classe di destinazione. Questa funzionalità semplifica la creazione di tipi che funzionano solo con un subset di campi in un pacchetto JSON di grandi dimensioni.

    La convenzione C# consiste nel maiuscolare la prima lettera di nomi delle proprietà, ma la name proprietà inizia con una lettera minuscola perché corrisponde esattamente a ciò che è presente nel codice JSON. In seguito si vedrà come usare i nomi delle proprietà C# che non corrispondono ai nomi delle proprietà JSON.

  2. Usare il serializzatore per convertire JSON in oggetti C#. Sostituire la chiamata a GetStringAsync(String) nel ProcessRepositoriesAsync metodo con le righe seguenti:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    

    Il codice aggiornato sostituisce GetStringAsync(String) con GetStreamAsync(String). Questo metodo serializzatore usa un flusso anziché una stringa come origine.

    Il primo argomento da usare JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) è un'espressione await . await le espressioni possono essere visualizzate quasi ovunque nel codice, anche se fino a questo momento, sono state visualizzate solo come parte di un'istruzione di assegnazione. Gli altri due parametri e CancellationToken, sono facoltativi JsonSerializerOptions e vengono omessi nel frammento di codice.

    Il DeserializeAsync metodo è generico, il che significa che si forniscono argomenti di tipo per il tipo di oggetti da creare dal testo JSON. In questo esempio si sta deserializzando in un List<Repository>oggetto , che è un altro oggetto generico, un System.Collections.Generic.List<T>oggetto . La List<T> classe archivia una raccolta di oggetti. L'argomento type dichiara il tipo di oggetti archiviati in List<T>. L'argomento di tipo è il Repository record, perché il testo JSON rappresenta una raccolta di oggetti repository.

  3. Aggiungere codice per visualizzare il nome di ogni repository. Sostituire le righe seguenti:

    Console.Write(json);
    

    con il codice seguente:

    foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
        Console.Write(repo.name);
    
  4. Le direttive seguenti using devono essere presenti nella parte superiore del file:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. Eseguire l'app.

    dotnet run
    

    L'output è un elenco dei nomi dei repository che fanno parte di .NET Foundation.

Configurare la deserializzazione

  1. In Repository.cs sostituire il contenuto del file con il codice C#seguente.

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name);
    

    Questo codice:

    • Modifica il nome della proprietà name in Name.
    • Aggiunge l'oggetto JsonPropertyNameAttribute per specificare la modalità di visualizzazione di questa proprietà in JSON.
  2. In Program.cs aggiornare il codice per usare la nuova maiuscola della Name proprietà:

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. Eseguire l'app.

    L'output è lo stesso.

Eseguire il refactoring del codice

Il metodo ProcessRepositoriesAsync può operare in modo asincrono e restituire una raccolta di repository. Modificare tale metodo per restituire Task<List<Repository>>e spostare il codice che scrive nella console vicino al chiamante.

  1. Modificare la firma di ProcessRepositoriesAsync in modo da restituire un'attività il cui risultato sia un elenco di oggetti Repository:

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. Restituire i repository dopo l'elaborazione della risposta JSON:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    return repositories ?? new();
    

    Il compilatore genera l'oggetto Task<T> per il valore restituito perché è stato contrassegnato come async.

  3. Modificare il file Program.cs , sostituendo la chiamata a ProcessRepositoriesAsync con la seguente per acquisire i risultati e scrivere ogni nome del repository nella console.

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. Eseguire l'app.

    L'output è lo stesso.

Deserializzare altre proprietà

La procedura seguente consente di aggiungere codice per elaborare altre proprietà nel pacchetto JSON ricevuto. Probabilmente non si vuole elaborare ogni proprietà, ma aggiungendo alcune altre funzionalità di C#.

  1. Sostituire il contenuto della Repository classe con la definizione seguente record :

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers);
    

    I Uri tipi e int dispongono di funzionalità predefinite per la conversione in e dalla rappresentazione stringa. Non è necessario alcun codice aggiuntivo per deserializzare dal formato stringa JSON a tali tipi di destinazione. Se il pacchetto JSON contiene dati che non vengono convertiti in un tipo di destinazione, l'azione di serializzazione genera un'eccezione.

  2. Aggiornare il foreach ciclo nel file Program.cs per visualizzare i valori delle proprietà:

    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine();
    }
    
  3. Eseguire l'app.

    L'elenco include ora le proprietà aggiuntive.

Aggiungere una proprietà date

La data dell'ultima operazione push viene formattata in questo modo nella risposta JSON:

2016-02-08T21:27:00Z

Questo formato è per Coordinated Universal Time (UTC), quindi il risultato della deserializzazione è un DateTime valore la cui Kind proprietà è Utc.

Per ottenere una data e un'ora rappresentata nel fuso orario, è necessario scrivere un metodo di conversione personalizzato.

  1. In Repository.cs aggiungere una proprietà per la rappresentazione UTC della data e dell'ora e una proprietà readonly LastPush che restituisce la data convertita nell'ora locale, il file dovrebbe essere simile al seguente:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers,
        [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
    {
        public DateTime LastPush => LastPushUtc.ToLocalTime();
    }
    

    La LastPush proprietà viene definita usando un membro con corpo dell'espressione per la get funzione di accesso. Non è disponibile alcuna set funzione di accesso. L'omissione della set funzione di accesso è un modo per definire una proprietà di sola lettura in C#. È possibile creare anche proprietà di sola scrittura in C#, ma con un valore limitato.

  2. Aggiungere un'altra istruzione di output in Program.cs: di nuovo:

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. L'app completa dovrebbe essere simile al file Program.cs seguente:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine($"{repo.LastPush}");
        Console.WriteLine();
    }
    
    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    {
        await using Stream stream =
            await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
        var repositories =
            await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
        return repositories ?? new();
    }
    
  4. Eseguire l'app.

    L'output include la data e l'ora dell'ultimo push in ogni repository.

Passaggi successivi

In questa esercitazione è stata creata un'app che effettua richieste Web e analizza i risultati. La versione dell'app dovrebbe ora corrispondere all'esempio completato.

Altre informazioni su come configurare la serializzazione JSON in Come serializzare e deserializzare (marshallal e unmarshal) JSON in .NET.