Skip to content

Commit 2a44b4b

Browse files
authored
Add code examples and update content for HTTP error handling (dotnet#31780)
* WIP * Add code and example content to existing HTTP conceptual doc for request exception handling * Tweak wording * Clean up * Correct statement * Address feedback
1 parent d970c42 commit 2a44b4b

18 files changed

+174
-27
lines changed

docs/fundamentals/networking/http/httpclient.md

+35-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Make HTTP requests with the HttpClient
33
description: Learn how to make HTTP requests and handle responses with the HttpClient in .NET.
44
author: IEvangelist
55
ms.author: dapine
6-
ms.date: 08/24/2022
6+
ms.date: 10/13/2022
77
---
88

99
# Make HTTP requests with the HttpClient class
@@ -265,7 +265,7 @@ This code will throw an `HttpRequestException` if the response status code is no
265265

266266
The HTTP response object (<xref:System.Net.Http.HttpResponseMessage>), when not successful, contains information about the error. The <xref:System.Net.HttpWebResponse.StatusCode%2A?displayProperty=nameWithType> property can be used to evaluate the error code.
267267

268-
For more information, see [Client error status codes](http-overview.md#client-error-status-codes) and [Server error status codes](http-overview.md#server-error-status-codes).
268+
For more information, see [Client error status codes](http-overview.md#client-error-status-codes) and [Server error status codes](http-overview.md#server-error-status-codes). In addition to handling errors in the response, you can also handle errors in the request. For more information, see [HTTP error handling](#http-error-handling).
269269

270270
### HTTP valid content responses
271271

@@ -289,6 +289,39 @@ Finally, when you know an HTTP endpoint returns JSON, you can deserialize the re
289289

290290
In the preceding code, `result` is the response body deserialized as the type `T`.
291291

292+
## HTTP error handling
293+
294+
When an HTTP request fails, the <xref:System.Net.Http.HttpRequestException> is thrown. Catching that exception alone may not be sufficient, as there are other potential exceptions thrown that you might want to consider handling. For example, the calling code may have used a cancellation token that was canceled before the request was completed. In this scenario, you'd catch the <xref:System.Threading.Tasks.TaskCanceledException>:
295+
296+
:::code language="csharp" source="../snippets/httpclient/Program.Cancellation.cs" id="cancellation":::
297+
298+
Likewise, when making an HTTP request, if the server doesn't respond before the <xref:System.Net.Http.HttpClient.Timeout?displayProperty=nameWithType> is exceeded the same exception is thrown. However, in this scenario, you can distinguish that the timeout occurred by evaluating the <xref:System.Exception.InnerException?displayProperty=nameWithType> when catching the <xref:System.Threading.Tasks.TaskCanceledException>:
299+
300+
:::code language="csharp" source="../snippets/httpclient/Program.CancellationInnerTimeout.cs" id="innertimeout":::
301+
302+
In the preceding code, when the inner exception is a <xref:System.TimeoutException> the timeout occurred, and the request wasn't canceled by the cancellation token.
303+
304+
To evaluate the HTTP status code when catching an <xref:System.Net.Http.HttpRequestException>, you can evaluate the <xref:System.Net.Http.HttpRequestException.StatusCode%2A?displayProperty=nameWithType> property:
305+
306+
:::code language="csharp" source="../snippets/httpclient/Program.CancellationStatusCode.cs" id="statuscode":::
307+
308+
In the preceding code, the <xref:System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode> method is called to throw an exception if the response is not successful. The <xref:System.Net.Http.HttpRequestException.StatusCode%2A?displayProperty=nameWithType> property is then evaluated to determine if the response was a `404` (HTTP status code 404). There are several helper methods on `HttpClient` that implicitly call `EnsureSuccessStatusCode` on your behalf, consider the following APIs:
309+
310+
- <xref:System.Net.Http.HttpClient.GetByteArrayAsync%2A?displayProperty=nameWithType>
311+
- <xref:System.Net.Http.HttpClient.GetStreamAsync%2A?displayProperty=nameWithType>
312+
- <xref:System.Net.Http.HttpClient.GetStringAsync%2A?displayProperty=nameWithType>
313+
314+
> [!TIP]
315+
> All `HttpClient` methods used to make HTTP requests that don't return an `HttpResponseMessage` implicitly call `EnsureSuccessStatusCode` on your behalf.
316+
317+
When calling these methods, you can handle the `HttpRequestException` and evaluate the <xref:System.Net.Http.HttpRequestException.StatusCode%2A?displayProperty=nameWithType> property to determine the HTTP status code of the response:
318+
319+
:::code language="csharp" source="../snippets/httpclient/Program.CancellationStream.cs" id="helpers":::
320+
321+
There might be scenarios in which you need to throw the <xref:System.Net.Http.HttpRequestException> in your code. The <xref:System.Net.Http.HttpRequestException.%23ctor> constructor is public, and you can use it to throw an exception with a custom message:
322+
323+
:::code language="csharp" source="../snippets/httpclient/Program.ThrowHttpException.cs" id="throw":::
324+
292325
## HTTP proxy
293326

294327
An HTTP proxy can be configured in one of two ways. A default is specified on the <xref:System.Net.Http.HttpClient.DefaultProxy?displayProperty=nameWithType> property. Alternatively, you can specify a proxy on the <xref:System.Net.Http.HttpClientHandler.Proxy?displayProperty=nameWithType> property.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
static partial class Program
2+
{
3+
static async Task WithCancellationAsync(HttpClient httpClient)
4+
{
5+
// <cancellation>
6+
using var cts = new CancellationTokenSource();
7+
try
8+
{
9+
// Assuming:
10+
// httpClient.Timeout = TimeSpan.FromSeconds(10)
11+
12+
using var response = await httpClient.GetAsync(
13+
"http://localhost:5001/sleepFor?seconds=100", cts.Token);
14+
}
15+
catch (TaskCanceledException ex) when (cts.IsCancellationRequested)
16+
{
17+
// When the token has been canceled, it is not a timeout.
18+
WriteLine($"Canceled: {ex.Message}");
19+
}
20+
// </cancellation>
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
static partial class Program
2+
{
3+
static async Task WithCancellationAndInnerTimeoutAsync(HttpClient httpClient)
4+
{
5+
// <innertimeout>
6+
try
7+
{
8+
// Assuming:
9+
// httpClient.Timeout = TimeSpan.FromSeconds(10)
10+
11+
using var response = await httpClient.GetAsync(
12+
"http://localhost:5001/sleepFor?seconds=100");
13+
}
14+
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException tex)
15+
{
16+
WriteLine($"Timed out: {ex.Message}, {tex.Message}");
17+
}
18+
// </innertimeout>
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
static partial class Program
2+
{
3+
static async Task WithCancellationAndStatusCodeAsync(HttpClient httpClient)
4+
{
5+
// <statuscode>
6+
try
7+
{
8+
// Assuming:
9+
// httpClient.Timeout = TimeSpan.FromSeconds(10)
10+
11+
using var response = await httpClient.GetAsync(
12+
"http://localhost:5001/doesNotExist");
13+
14+
response.EnsureSuccessStatusCode();
15+
}
16+
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
17+
{
18+
// Handle 404
19+
Console.WriteLine($"Not found: {ex.Message}");
20+
}
21+
// </statuscode>
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
static partial class Program
2+
{
3+
static async Task WithCancellationExtensionsAsync(HttpClient httpClient)
4+
{
5+
// <helpers>
6+
try
7+
{
8+
// These extension methods will throw HttpRequestException
9+
// with StatusCode set when the HTTP request status code isn't 2xx:
10+
//
11+
// GetByteArrayAsync
12+
// GetStreamAsync
13+
// GetStringAsync
14+
15+
using var stream = await httpClient.GetStreamAsync(
16+
"https://localhost:5001/doesNotExists");
17+
}
18+
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
19+
{
20+
// Handle 404
21+
Console.WriteLine($"Not found: {ex.Message}");
22+
}
23+
// </helpers>
24+
}
25+
}

docs/fundamentals/networking/snippets/httpclient/Program.Delete.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
static partial class Program
22
{
33
// <delete>
4-
static async Task DeleteAsync(HttpClient client)
4+
static async Task DeleteAsync(HttpClient httpClient)
55
{
6-
using HttpResponseMessage response = await client.DeleteAsync("todos/1");
6+
using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1");
77

88
response.EnsureSuccessStatusCode()
99
.WriteRequestToConsole();

docs/fundamentals/networking/snippets/httpclient/Program.Get.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
static partial class Program
22
{
33
// <get>
4-
static async Task GetAsync(HttpClient client)
4+
static async Task GetAsync(HttpClient httpClient)
55
{
6-
using HttpResponseMessage response = await client.GetAsync("todos/3");
6+
using HttpResponseMessage response = await httpClient.GetAsync("todos/3");
77

88
response.EnsureSuccessStatusCode()
99
.WriteRequestToConsole();

docs/fundamentals/networking/snippets/httpclient/Program.GetFromJson.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
static partial class Program
22
{
33
// <getfromjson>
4-
static async Task GetFromJsonAsync(HttpClient client)
4+
static async Task GetFromJsonAsync(HttpClient httpClient)
55
{
6-
var todos = await client.GetFromJsonAsync<List<Todo>>(
6+
var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
77
"todos?userId=1&completed=false");
88

99
WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1");

docs/fundamentals/networking/snippets/httpclient/Program.Head.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
static partial class Program
22
{
33
// <head>
4-
static async Task HeadAsync(HttpClient client)
4+
static async Task HeadAsync(HttpClient httpClient)
55
{
66
using HttpRequestMessage request = new(
77
HttpMethod.Head,
88
"https://www.example.com");
99

10-
using HttpResponseMessage response = await client.SendAsync(request);
10+
using HttpResponseMessage response = await httpClient.SendAsync(request);
1111

1212
response.EnsureSuccessStatusCode()
1313
.WriteRequestToConsole();

docs/fundamentals/networking/snippets/httpclient/Program.Options.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
static partial class Program
22
{
33
// <options>
4-
static async Task OptionsAsync(HttpClient client)
4+
static async Task OptionsAsync(HttpClient httpClient)
55
{
66
using HttpRequestMessage request = new(
77
HttpMethod.Options,
88
"https://www.example.com");
99

10-
using HttpResponseMessage response = await client.SendAsync(request);
10+
using HttpResponseMessage response = await httpClient.SendAsync(request);
1111

1212
response.EnsureSuccessStatusCode()
1313
.WriteRequestToConsole();

docs/fundamentals/networking/snippets/httpclient/Program.Patch.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
static partial class Program
22
{
33
// <patch>
4-
static async Task PatchAsync(HttpClient client)
4+
static async Task PatchAsync(HttpClient httpClient)
55
{
66
using StringContent jsonContent = new(
77
JsonSerializer.Serialize(new
@@ -11,7 +11,7 @@ static async Task PatchAsync(HttpClient client)
1111
Encoding.UTF8,
1212
"application/json");
1313

14-
using HttpResponseMessage response = await client.PatchAsync(
14+
using HttpResponseMessage response = await httpClient.PatchAsync(
1515
"todos/1",
1616
jsonContent);
1717

docs/fundamentals/networking/snippets/httpclient/Program.Post.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
static partial class Program
22
{
33
// <post>
4-
static async Task PostAsync(HttpClient client)
4+
static async Task PostAsync(HttpClient httpClient)
55
{
66
using StringContent jsonContent = new(
77
JsonSerializer.Serialize(new
@@ -14,7 +14,7 @@ static async Task PostAsync(HttpClient client)
1414
Encoding.UTF8,
1515
"application/json");
1616

17-
using HttpResponseMessage response = await client.PostAsync(
17+
using HttpResponseMessage response = await httpClient.PostAsync(
1818
"todos",
1919
jsonContent);
2020

docs/fundamentals/networking/snippets/httpclient/Program.PostAsJson.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
static partial class Program
22
{
33
// <postasjson>
4-
static async Task PostAsJsonAsync(HttpClient client)
4+
static async Task PostAsJsonAsync(HttpClient httpClient)
55
{
6-
using HttpResponseMessage response = await client.PostAsJsonAsync(
6+
using HttpResponseMessage response = await httpClient.PostAsJsonAsync(
77
"todos",
88
new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false));
99

docs/fundamentals/networking/snippets/httpclient/Program.Put.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
static partial class Program
22
{
33
// <put>
4-
static async Task PutAsync(HttpClient client)
4+
static async Task PutAsync(HttpClient httpClient)
55
{
66
using StringContent jsonContent = new(
77
JsonSerializer.Serialize(new
@@ -14,7 +14,7 @@ static async Task PutAsync(HttpClient client)
1414
Encoding.UTF8,
1515
"application/json");
1616

17-
using HttpResponseMessage response = await client.PutAsync(
17+
using HttpResponseMessage response = await httpClient.PutAsync(
1818
"todos/1",
1919
jsonContent);
2020

docs/fundamentals/networking/snippets/httpclient/Program.PutAsJson.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
static partial class Program
22
{
33
// <putasjson>
4-
static async Task PutAsJsonAsync(HttpClient client)
4+
static async Task PutAsJsonAsync(HttpClient httpClient)
55
{
6-
using HttpResponseMessage response = await client.PutAsJsonAsync(
6+
using HttpResponseMessage response = await httpClient.PutAsJsonAsync(
77
"todos/5",
88
new Todo(Title: "partially update todo", Completed: true));
99

docs/fundamentals/networking/snippets/httpclient/Program.Responses.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
static partial class Program
22
{
3-
static async Task HandleResponsesAsync<T>(HttpClient client)
3+
static async Task HandleResponsesAsync<T>(HttpClient httpClient)
44
{
55
using HttpRequestMessage request = new(
66
HttpMethod.Head,
77
"https://www.example.com");
88

99
// <request>
10-
using HttpResponseMessage response = await client.SendAsync(request);
10+
using HttpResponseMessage response = await httpClient.SendAsync(request);
1111
// </request>
1212

1313
// <isstatuscode>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
static partial class Program
2+
{
3+
static async Task ThrowHttpRequestExceptionAsync(HttpClient httpClient)
4+
{
5+
// <throw>
6+
try
7+
{
8+
using var response = await httpClient.GetAsync(
9+
"https://localhost:5001/doesNotExists");
10+
11+
// Throw for anything higher than 400.
12+
if (response is { StatusCode: >= HttpStatusCode.BadRequest })
13+
{
14+
throw new HttpRequestException(
15+
"Something went wrong", inner: null, response.StatusCode);
16+
}
17+
}
18+
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
19+
{
20+
Console.WriteLine($"Not found: {ex.Message}");
21+
}
22+
// </throw>
23+
}
24+
}

docs/fundamentals/networking/snippets/httpclient/Program.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ public static async Task Main(string[] args)
2020
await DeleteAsync(sharedClient);
2121

2222
// <client>
23-
using HttpClient client = new();
23+
using HttpClient httpClient = new();
2424
// </client>
2525

26-
await HeadAsync(client);
27-
await OptionsAsync(client);
26+
await HeadAsync(httpClient);
27+
await OptionsAsync(httpClient);
2828
}
2929
}

0 commit comments

Comments
 (0)