Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This tutorial builds an app that issues HTTP requests to a REST service on GitHub. The app reads information in JSON format and converts the JSON into C# objects. Converting from JSON to C# objects is known as deserialization.
The tutorial shows how to:
- Send HTTP requests.
- Deserialize JSON responses.
- Configure deserialization with attributes.
If you prefer to follow along with the final sample for this tutorial, you can download it. For download instructions, see Samples and Tutorials.
Prerequisites
- The latest .NET SDK
- Visual Studio Code editor
- The C# DevKit
Create the client app
Open a command prompt and create a new directory for your app. Make that the current directory.
Enter the following command in a console window:
dotnet new console --name WebAPIClientThis command creates the starter files for a basic "Hello World" app. The project name is "WebAPIClient".
Navigate into the "WebAPIClient" directory, and run the app.
cd WebAPIClientdotnet rundotnet runautomatically runsdotnet restoreto restore any dependencies that the app needs. It also runsdotnet buildif needed. You should see the app output"Hello, World!". In your terminal, press Ctrl+C to stop the app.
Make HTTP requests
This app calls the GitHub API to get information about the projects under the .NET Foundation umbrella. The endpoint is https://api.github.com/orgs/dotnet/repos. To retrieve information, it makes an HTTP GET request. Browsers also make HTTP GET requests, so you can paste that URL into your browser address bar to see what information you'll be receiving and processing.
Use the HttpClient class to make HTTP requests. HttpClient supports only async methods for its long-running APIs. So the following steps create an async method and call it from the Main method.
Open the
Program.csfile in your project directory and replace its contents with the following:await ProcessRepositoriesAsync(); static async Task ProcessRepositoriesAsync(HttpClient client) { }This code:
- Replaces the
Console.WriteLinestatement with a call toProcessRepositoriesAsyncthat uses theawaitkeyword. - Defines an empty
ProcessRepositoriesAsyncmethod.
- Replaces the
In the
Programclass, use an HttpClient to handle requests and responses, by replacing the content with the following C#.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) { }This code:
- Sets up HTTP headers for all requests:
- An
Acceptheader to accept JSON responses - A
User-Agentheader. These headers are checked by the GitHub server code and are necessary to retrieve information from GitHub.
- An
- Sets up HTTP headers for all requests:
In the
ProcessRepositoriesAsyncmethod, call the GitHub endpoint that returns a list of all repositories under the .NET foundation organization:static async Task ProcessRepositoriesAsync(HttpClient client) { var json = await client.GetStringAsync( "https://api.github.com/orgs/dotnet/repos"); Console.Write(json); }This code:
- Awaits the task returned from calling HttpClient.GetStringAsync(String) method. This method sends an HTTP GET request to the specified URI. The body of the response is returned as a String, which is available when the task completes.
- The response string
jsonis printed to the console.
Build the app and run it.
dotnet runThere is no build warning because the
ProcessRepositoriesAsyncnow contains anawaitoperator. The output is a long display of JSON text.
Deserialize the JSON Result
The following steps simplify the approach to fetching the data and processing it. You will use the GetFromJsonAsync extension method that's part of the 📦 System.Net.Http.Json NuGet package to fetch and deserialize the JSON results into objects.
Create a file named Repository.cs and add the following code:
public record class Repository(string Name);The preceding code defines a class to represent the JSON object returned from the GitHub API. You'll use this class to display a list of repository names.
The JSON for a repository object contains dozens of properties, but only the
Nameproperty will be deserialized. The serializer automatically ignores JSON properties for which there is no match in the target class. This feature makes it easier to create types that work with only a subset of fields in a large JSON packet.Although the
GetFromJsonAsyncmethod you will use in the next point has a benefit of being case-insensitive when it comes to property names, the C# convention is to capitalize the first letter of property names.Use the HttpClientJsonExtensions.GetFromJsonAsync method to fetch and convert JSON into C# objects. Replace the call to GetStringAsync(String) in the
ProcessRepositoriesAsyncmethod with the following lines:var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");The updated code replaces GetStringAsync(String) with HttpClientJsonExtensions.GetFromJsonAsync.
The first argument to
GetFromJsonAsyncmethod is anawaitexpression.awaitexpressions can appear almost anywhere in your code, even though up to now, you've only seen them as part of an assignment statement. The next parameter,requestUriis optional and doesn't have to be provided if was already specified when creating theclientobject. You didn't provide theclientobject with the URI to send request to, so you specified the URI now. The last optional parameter, theCancellationTokenis omitted in the code snippet.The
GetFromJsonAsyncmethod is generic, which means you supply type arguments for what kind of objects should be created from the fetched JSON text. In this example, you're deserializing to aList<Repository>, which is another generic object, a System.Collections.Generic.List<T>. TheList<T>class stores a collection of objects. The type argument declares the type of objects stored in theList<T>. The type argument is yourRepositoryrecord, because the JSON text represents a collection of repository objects.Add code to display the name of each repository. Replace the lines that read:
Console.Write(json);with the following code:
foreach (var repo in repositories ?? Enumerable.Empty<Repository>()) Console.WriteLine(repo.Name);The following
usingdirectives should be present at the top of the file:using System.Net.Http.Headers; using System.Net.Http.Json;Run the app.
dotnet runThe output is a list of the names of the repositories that are part of the .NET Foundation.
Refactor the code
The ProcessRepositoriesAsync method can do the async work and return a collection of the repositories. Change that method to return Task<List<Repository>>, and move the code that writes to the console near its caller.
Change the signature of
ProcessRepositoriesAsyncto return a task whose result is a list ofRepositoryobjects:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)Return the repositories after processing the JSON response:
var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos"); return repositories ?? new();The compiler generates the
Task<T>object for the return value because you've marked this method asasync.Modify the Program.cs file, replacing the call to
ProcessRepositoriesAsyncwith the following to capture the results and write each repository name to the console.var repositories = await ProcessRepositoriesAsync(client); foreach (var repo in repositories) Console.WriteLine(repo.Name);Run the app.
The output is the same.
Deserialize more properties
The following steps add code to process more of the properties in the received JSON packet. You probably won't want to process every property, but adding a few more demonstrates other features of C#.
Replace the contents of
Repositoryclass, with the followingrecorddefinition:public record class Repository( string Name, string Description, Uri GitHubHomeUrl, Uri Homepage, int Watchers, DateTime LastPushUtc );The Uri and
inttypes have built-in functionality to convert to and from string representation. No extra code is needed to deserialize from JSON string format to those target types. If the JSON packet contains data that doesn't convert to a target type, the serialization action throws an exception.JSON most often uses lowercase for names of it's objects, however we don't need to make any conversion and can keep the uppercase of the fields names, because, like mentioned in one of previous points, the
GetFromJsonAsyncextension method is case-insensitive when it comes to property names.Update the
foreachloop in the Program.cs file to display the property values: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(); }Run the app.
The list now includes the additional properties.
Add a date property
The date of the last push operation is formatted in this fashion in the JSON response:
2016-02-08T21:27:00Z
This format is for Coordinated Universal Time (UTC), so the result of deserialization is a DateTime value whose Kind property is Utc.
To get a date and time represented in your time zone, you have to write a custom conversion method.
In Repository.cs, add a property for the UTC representation of the date and time and a readonly
LastPushproperty that returns the date converted to local time, the file should look like the following:public record class Repository( string Name, string Description, Uri GitHubHomeUrl, Uri Homepage, int Watchers, DateTime LastPushUtc ) { public DateTime LastPush => LastPushUtc.ToLocalTime(); }The
LastPushproperty is defined using an expression-bodied member for thegetaccessor. There's nosetaccessor. Omitting thesetaccessor is one way to define a read-only property in C#. (Yes, you can create write-only properties in C#, but their value is limited.)Add another output statement in Program.cs: again:
Console.WriteLine($"Last push: {repo.LastPush}");The complete app should resemble the following Program.cs file:
using System.Net.Http.Headers; using System.Net.Http.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) { var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos"); return repositories ?? new List<Repository>(); }Run the app.
The output includes the date and time of the last push to each repository.
Next steps
In this tutorial, you created an app that makes web requests and parses the results. Your version of the app should now match the finished sample.
Learn more about how to configure JSON serialization in How to serialize and deserialize (marshal and unmarshal) JSON in .NET.