Skip to content

Commit aacf19b

Browse files
Refactor, improve the WebAPIClient tutorial page to use GetFromJsonAsync instead of DeserializeAsync (#48202)
* Use Net.Http.Json extension method GetFromJsonAsync instead of JsonSerializer * Remove configuring the deserialization * Capitalize the Name property * Update Repository class * Remove unused Text.Json.Serialization * Correct the orders of points * WriteLine instead of Write in early phase to improve output readability * Revert "Correct the orders of points" This reverts commit bb708ac. * Remove leftover sentence * Mention that GetFromJsonAsync is case-insensitive This is mentioned in two places - with the C# naming convention, where it's worth to mention why we keep on having uppercase, and in the Repository class defining, where we would normally had to make a conversion, but now we don't because we have case-insensitive method as a tool.
1 parent 8fd45bf commit aacf19b

File tree

3 files changed

+33
-69
lines changed

3 files changed

+33
-69
lines changed

docs/csharp/tutorials/console-webapiclient.md

Lines changed: 22 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -121,35 +121,31 @@ Use the <xref:System.Net.Http.HttpClient> class to make HTTP requests. <xref:Sys
121121

122122
## Deserialize the JSON Result
123123

124-
The following steps convert the JSON response into C# objects. You use the <xref:System.Text.Json.JsonSerializer?displayProperty=nameWithType> class to deserialize JSON into objects.
124+
The following steps simplify the approach to fetching the data and processing it. You will use the <xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A> extension method that's part of the [📦 System.Net.Http.Json](https://www.nuget.org/packages/System.Net.Http.Json) NuGet package to fetch and deserialize the JSON results into objects.
125125

126126
1. Create a file named *Repository.cs* and add the following code:
127127

128128
```csharp
129-
public record class Repository(string name);
129+
public record class Repository(string Name);
130130
```
131131

132132
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.
133133

134-
The JSON for a repository object contains dozens of properties, but only the `name` property 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.
134+
The JSON for a repository object contains dozens of properties, but only the `Name` property 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.
135135

136-
The C# convention is to [capitalize the first letter of property names](../../standard/design-guidelines/capitalization-conventions.md), but the `name` property here starts with a lowercase letter because that matches exactly what's in the JSON. Later you'll see how to use C# property names that don't match the JSON property names.
136+
Although the `GetFromJsonAsync` method 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](../../standard/design-guidelines/capitalization-conventions.md).
137137

138-
1. Use the serializer to convert JSON into C# objects. Replace the call to
139-
<xref:System.Net.Http.HttpClient.GetStringAsync(System.String)> in the `ProcessRepositoriesAsync` method with the following lines:
138+
1. Use the <xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A?displayProperty=nameWithType> method to fetch and convert JSON into C# objects. Replace the call to <xref:System.Net.Http.HttpClient.GetStringAsync(System.String)> in the `ProcessRepositoriesAsync` method with the following lines:
140139

141140
```csharp
142-
await using Stream stream =
143-
await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
144-
var repositories =
145-
await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
141+
var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
146142
```
147143

148-
The updated code replaces <xref:System.Net.Http.HttpClient.GetStringAsync(System.String)> with <xref:System.Net.Http.HttpClient.GetStreamAsync(System.String)>. This serializer method uses a stream instead of a string as its source.
144+
The updated code replaces <xref:System.Net.Http.HttpClient.GetStringAsync(System.String)> with <xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A?displayProperty=nameWithType>.
149145

150-
The first argument to <xref:System.Text.Json.JsonSerializer.DeserializeAsync%60%601(System.IO.Stream,System.Text.Json.JsonSerializerOptions,System.Threading.CancellationToken)?displayProperty=nameWithType> is an `await` expression. `await` expressions can appear almost anywhere in your code, even though up to now, you've only seen them as part of an assignment statement. The other two parameters, `JsonSerializerOptions` and `CancellationToken`, are optional and are omitted in the code snippet.
146+
The first argument to `GetFromJsonAsync` method is an `await` expression. `await` expressions 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, `requestUri` is optional and doesn't have to be provided if was already specified when creating the `client` object. You didn't provide the `client` object with the URI to send request to, so you specified the URI now. The last optional parameter, the `CancellationToken` is omitted in the code snippet.
151147

152-
The `DeserializeAsync` method is [*generic*](../fundamentals/types/generics.md), which means you supply type arguments for what kind of objects should be created from the JSON text. In this example, you're deserializing to a `List<Repository>`, which is another generic object, a <xref:System.Collections.Generic.List%601?displayProperty=nameWithType>. The `List<T>` class stores a collection of objects. The type argument declares the type of objects stored in the `List<T>`. The type argument is your `Repository` record, because the JSON text represents a collection of repository objects.
148+
The `GetFromJsonAsync` method is [*generic*](../fundamentals/types/generics.md), 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 a `List<Repository>`, which is another generic object, a <xref:System.Collections.Generic.List%601?displayProperty=nameWithType>. The `List<T>` class stores a collection of objects. The type argument declares the type of objects stored in the `List<T>`. The type argument is your `Repository` record, because the JSON text represents a collection of repository objects.
153149

154150
1. Add code to display the name of each repository. Replace the lines that read:
155151

@@ -161,14 +157,14 @@ The following steps convert the JSON response into C# objects. You use the <xref
161157

162158
```csharp
163159
foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
164-
Console.Write(repo.name);
160+
Console.WriteLine(repo.Name);
165161
```
166162

167163
1. The following `using` directives should be present at the top of the file:
168164

169165
```csharp
170166
using System.Net.Http.Headers;
171-
using System.Text.Json;
167+
using System.Net.Http.Json;
172168
```
173169

174170
1. Run the app.
@@ -179,33 +175,6 @@ The following steps convert the JSON response into C# objects. You use the <xref
179175

180176
The output is a list of the names of the repositories that are part of the .NET Foundation.
181177

182-
## Configure deserialization
183-
184-
1. In *Repository.cs*, replace the file contents with the following C#.
185-
186-
```csharp
187-
using System.Text.Json.Serialization;
188-
189-
public record class Repository(
190-
[property: JsonPropertyName("name")] string Name);
191-
```
192-
193-
This code:
194-
195-
* Changes the name of the `name` property to `Name`.
196-
* Adds the <xref:System.Text.Json.Serialization.JsonPropertyNameAttribute> to specify how this property appears in the JSON.
197-
198-
1. In *Program.cs*, update the code to use the new capitalization of the `Name` property:
199-
200-
```csharp
201-
foreach (var repo in repositories)
202-
Console.Write(repo.Name);
203-
```
204-
205-
1. Run the app.
206-
207-
The output is the same.
208-
209178
## Refactor the code
210179

211180
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.
@@ -219,10 +188,7 @@ The `ProcessRepositoriesAsync` method can do the async work and return a collect
219188
1. Return the repositories after processing the JSON response:
220189

221190
```csharp
222-
await using Stream stream =
223-
await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
224-
var repositories =
225-
await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
191+
var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
226192
return repositories ?? new();
227193
```
228194

@@ -234,7 +200,7 @@ The `ProcessRepositoriesAsync` method can do the async work and return a collect
234200
var repositories = await ProcessRepositoriesAsync(client);
235201

236202
foreach (var repo in repositories)
237-
Console.Write(repo.Name);
203+
Console.WriteLine(repo.Name);
238204
```
239205

240206
1. Run the app.
@@ -248,18 +214,20 @@ The following steps add code to process more of the properties in the received J
248214
1. Replace the contents of `Repository` class, with the following `record` definition:
249215

250216
```csharp
251-
using System.Text.Json.Serialization;
252-
253217
public record class Repository(
254-
[property: JsonPropertyName("name")] string Name,
255-
[property: JsonPropertyName("description")] string Description,
256-
[property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
257-
[property: JsonPropertyName("homepage")] Uri Homepage,
258-
[property: JsonPropertyName("watchers")] int Watchers);
218+
string Name,
219+
string Description,
220+
Uri GitHubHomeUrl,
221+
Uri Homepage,
222+
int Watchers,
223+
DateTime LastPushUtc
224+
);
259225
```
260226

261227
The <xref:System.Uri> and `int` types 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.
262228

229+
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 `GetFromJsonAsync` extension method is case-insensitive when it comes to property names.
230+
263231
1. Update the `foreach` loop in the *Program.cs* file to display the property values:
264232

265233
```csharp

docs/csharp/tutorials/snippets/WebAPIClient/Program.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Net.Http.Headers;
2-
using System.Text.Json;
2+
using System.Net.Http.Json;
33

44
using HttpClient client = new();
55
client.DefaultRequestHeaders.Accept.Clear();
@@ -22,9 +22,6 @@
2222

2323
static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
2424
{
25-
await using Stream stream =
26-
await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
27-
var repositories =
28-
await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
29-
return repositories ?? new();
25+
var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
26+
return repositories ?? new List<Repository>();
3027
}
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
using System.Text.Json.Serialization;
2-
3-
public record class Repository(
4-
[property: JsonPropertyName("name")] string Name,
5-
[property: JsonPropertyName("description")] string Description,
6-
[property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
7-
[property: JsonPropertyName("homepage")] Uri Homepage,
8-
[property: JsonPropertyName("watchers")] int Watchers,
9-
[property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
1+
public record class Repository(
2+
string Name,
3+
string Description,
4+
Uri GitHubHomeUrl,
5+
Uri Homepage,
6+
int Watchers,
7+
DateTime LastPushUtc
8+
)
109
{
1110
public DateTime LastPush => LastPushUtc.ToLocalTime();
1211
}

0 commit comments

Comments
 (0)