diff --git a/docs/fundamentals/networking/http/httpclient.md b/docs/fundamentals/networking/http/httpclient.md index 001d03c32dd60..e331638127bc3 100644 --- a/docs/fundamentals/networking/http/httpclient.md +++ b/docs/fundamentals/networking/http/httpclient.md @@ -3,7 +3,7 @@ title: Make HTTP requests with the HttpClient description: Learn how to make HTTP requests and handle responses with the HttpClient in .NET. author: IEvangelist ms.author: dapine -ms.date: 08/24/2022 +ms.date: 10/13/2022 --- # Make HTTP requests with the HttpClient class @@ -265,7 +265,7 @@ This code will throw an `HttpRequestException` if the response status code is no The HTTP response object (), when not successful, contains information about the error. The property can be used to evaluate the error code. -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). +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). ### HTTP valid content responses @@ -289,6 +289,39 @@ Finally, when you know an HTTP endpoint returns JSON, you can deserialize the re In the preceding code, `result` is the response body deserialized as the type `T`. +## HTTP error handling + +When an HTTP request fails, the 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 : + +:::code language="csharp" source="../snippets/httpclient/Program.Cancellation.cs" id="cancellation"::: + +Likewise, when making an HTTP request, if the server doesn't respond before the is exceeded the same exception is thrown. However, in this scenario, you can distinguish that the timeout occurred by evaluating the when catching the : + +:::code language="csharp" source="../snippets/httpclient/Program.CancellationInnerTimeout.cs" id="innertimeout"::: + +In the preceding code, when the inner exception is a the timeout occurred, and the request wasn't canceled by the cancellation token. + +To evaluate the HTTP status code when catching an , you can evaluate the property: + +:::code language="csharp" source="../snippets/httpclient/Program.CancellationStatusCode.cs" id="statuscode"::: + +In the preceding code, the method is called to throw an exception if the response is not successful. The 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: + +- +- +- + +> [!TIP] +> All `HttpClient` methods used to make HTTP requests that don't return an `HttpResponseMessage` implicitly call `EnsureSuccessStatusCode` on your behalf. + +When calling these methods, you can handle the `HttpRequestException` and evaluate the property to determine the HTTP status code of the response: + +:::code language="csharp" source="../snippets/httpclient/Program.CancellationStream.cs" id="helpers"::: + +There might be scenarios in which you need to throw the in your code. The constructor is public, and you can use it to throw an exception with a custom message: + +:::code language="csharp" source="../snippets/httpclient/Program.ThrowHttpException.cs" id="throw"::: + ## HTTP proxy An HTTP proxy can be configured in one of two ways. A default is specified on the property. Alternatively, you can specify a proxy on the property. diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Cancellation.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Cancellation.cs new file mode 100644 index 0000000000000..7671299425e97 --- /dev/null +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Cancellation.cs @@ -0,0 +1,22 @@ +static partial class Program +{ + static async Task WithCancellationAsync(HttpClient httpClient) + { + // + 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 (TaskCanceledException ex) when (cts.IsCancellationRequested) + { + // When the token has been canceled, it is not a timeout. + WriteLine($"Canceled: {ex.Message}"); + } + // + } +} diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.CancellationInnerTimeout.cs b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationInnerTimeout.cs new file mode 100644 index 0000000000000..51f18c9217048 --- /dev/null +++ b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationInnerTimeout.cs @@ -0,0 +1,20 @@ +static partial class Program +{ + static async Task WithCancellationAndInnerTimeoutAsync(HttpClient httpClient) + { + // + try + { + // Assuming: + // httpClient.Timeout = TimeSpan.FromSeconds(10) + + using var response = await httpClient.GetAsync( + "http://localhost:5001/sleepFor?seconds=100"); + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException tex) + { + WriteLine($"Timed out: {ex.Message}, {tex.Message}"); + } + // + } +} diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStatusCode.cs b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStatusCode.cs new file mode 100644 index 0000000000000..435638c63d98e --- /dev/null +++ b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStatusCode.cs @@ -0,0 +1,23 @@ +static partial class Program +{ + static async Task WithCancellationAndStatusCodeAsync(HttpClient httpClient) + { + // + 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}"); + } + // + } +} diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStream.cs b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStream.cs new file mode 100644 index 0000000000000..6c483ad04efa7 --- /dev/null +++ b/docs/fundamentals/networking/snippets/httpclient/Program.CancellationStream.cs @@ -0,0 +1,25 @@ +static partial class Program +{ + static async Task WithCancellationExtensionsAsync(HttpClient httpClient) + { + // + 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}"); + } + // + } +} diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Delete.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Delete.cs index 073f57ef84d77..9c0727d8ea0a2 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Delete.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Delete.cs @@ -1,9 +1,9 @@ static partial class Program { // - static async Task DeleteAsync(HttpClient client) + static async Task DeleteAsync(HttpClient httpClient) { - using HttpResponseMessage response = await client.DeleteAsync("todos/1"); + using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1"); response.EnsureSuccessStatusCode() .WriteRequestToConsole(); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Get.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Get.cs index a37194473b46f..13ca4827cb9b2 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Get.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Get.cs @@ -1,9 +1,9 @@ static partial class Program { // - static async Task GetAsync(HttpClient client) + static async Task GetAsync(HttpClient httpClient) { - using HttpResponseMessage response = await client.GetAsync("todos/3"); + using HttpResponseMessage response = await httpClient.GetAsync("todos/3"); response.EnsureSuccessStatusCode() .WriteRequestToConsole(); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.GetFromJson.cs b/docs/fundamentals/networking/snippets/httpclient/Program.GetFromJson.cs index 05d7aed3418ba..72ac1efea9cbf 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.GetFromJson.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.GetFromJson.cs @@ -1,9 +1,9 @@ static partial class Program { // - static async Task GetFromJsonAsync(HttpClient client) + static async Task GetFromJsonAsync(HttpClient httpClient) { - var todos = await client.GetFromJsonAsync>( + var todos = await httpClient.GetFromJsonAsync>( "todos?userId=1&completed=false"); WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1"); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Head.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Head.cs index 56d1cae3b9ffa..65c871bf760f6 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Head.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Head.cs @@ -1,13 +1,13 @@ static partial class Program { // - static async Task HeadAsync(HttpClient client) + static async Task HeadAsync(HttpClient httpClient) { using HttpRequestMessage request = new( HttpMethod.Head, "https://www.example.com"); - using HttpResponseMessage response = await client.SendAsync(request); + using HttpResponseMessage response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode() .WriteRequestToConsole(); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Options.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Options.cs index 8c9e4712c31e9..8060ac1bc2b10 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Options.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Options.cs @@ -1,13 +1,13 @@ static partial class Program { // - static async Task OptionsAsync(HttpClient client) + static async Task OptionsAsync(HttpClient httpClient) { using HttpRequestMessage request = new( HttpMethod.Options, "https://www.example.com"); - using HttpResponseMessage response = await client.SendAsync(request); + using HttpResponseMessage response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode() .WriteRequestToConsole(); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Patch.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Patch.cs index edc3224679f70..e8680a6d8ceb9 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Patch.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Patch.cs @@ -1,7 +1,7 @@ static partial class Program { // - static async Task PatchAsync(HttpClient client) + static async Task PatchAsync(HttpClient httpClient) { using StringContent jsonContent = new( JsonSerializer.Serialize(new @@ -11,7 +11,7 @@ static async Task PatchAsync(HttpClient client) Encoding.UTF8, "application/json"); - using HttpResponseMessage response = await client.PatchAsync( + using HttpResponseMessage response = await httpClient.PatchAsync( "todos/1", jsonContent); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Post.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Post.cs index 79f24a363e526..6d33b6b78f052 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Post.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Post.cs @@ -1,7 +1,7 @@ static partial class Program { // - static async Task PostAsync(HttpClient client) + static async Task PostAsync(HttpClient httpClient) { using StringContent jsonContent = new( JsonSerializer.Serialize(new @@ -14,7 +14,7 @@ static async Task PostAsync(HttpClient client) Encoding.UTF8, "application/json"); - using HttpResponseMessage response = await client.PostAsync( + using HttpResponseMessage response = await httpClient.PostAsync( "todos", jsonContent); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.PostAsJson.cs b/docs/fundamentals/networking/snippets/httpclient/Program.PostAsJson.cs index 3576af130cb05..6d70c9a1433e0 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.PostAsJson.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.PostAsJson.cs @@ -1,9 +1,9 @@ static partial class Program { // - static async Task PostAsJsonAsync(HttpClient client) + static async Task PostAsJsonAsync(HttpClient httpClient) { - using HttpResponseMessage response = await client.PostAsJsonAsync( + using HttpResponseMessage response = await httpClient.PostAsJsonAsync( "todos", new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false)); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Put.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Put.cs index 2db3ca2af784a..2af1adf984291 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Put.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Put.cs @@ -1,7 +1,7 @@ static partial class Program { // - static async Task PutAsync(HttpClient client) + static async Task PutAsync(HttpClient httpClient) { using StringContent jsonContent = new( JsonSerializer.Serialize(new @@ -14,7 +14,7 @@ static async Task PutAsync(HttpClient client) Encoding.UTF8, "application/json"); - using HttpResponseMessage response = await client.PutAsync( + using HttpResponseMessage response = await httpClient.PutAsync( "todos/1", jsonContent); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.PutAsJson.cs b/docs/fundamentals/networking/snippets/httpclient/Program.PutAsJson.cs index 1e0840356eebb..25cb99afd0a71 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.PutAsJson.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.PutAsJson.cs @@ -1,9 +1,9 @@ static partial class Program { // - static async Task PutAsJsonAsync(HttpClient client) + static async Task PutAsJsonAsync(HttpClient httpClient) { - using HttpResponseMessage response = await client.PutAsJsonAsync( + using HttpResponseMessage response = await httpClient.PutAsJsonAsync( "todos/5", new Todo(Title: "partially update todo", Completed: true)); diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.Responses.cs b/docs/fundamentals/networking/snippets/httpclient/Program.Responses.cs index b1fbde08427bb..3ad3f5006a0fe 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.Responses.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.Responses.cs @@ -1,13 +1,13 @@ static partial class Program { - static async Task HandleResponsesAsync(HttpClient client) + static async Task HandleResponsesAsync(HttpClient httpClient) { using HttpRequestMessage request = new( HttpMethod.Head, "https://www.example.com"); // - using HttpResponseMessage response = await client.SendAsync(request); + using HttpResponseMessage response = await httpClient.SendAsync(request); // // diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.ThrowHttpException.cs b/docs/fundamentals/networking/snippets/httpclient/Program.ThrowHttpException.cs new file mode 100644 index 0000000000000..1180c0f5fc6c5 --- /dev/null +++ b/docs/fundamentals/networking/snippets/httpclient/Program.ThrowHttpException.cs @@ -0,0 +1,24 @@ +static partial class Program +{ + static async Task ThrowHttpRequestExceptionAsync(HttpClient httpClient) + { + // + 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}"); + } + // + } +} diff --git a/docs/fundamentals/networking/snippets/httpclient/Program.cs b/docs/fundamentals/networking/snippets/httpclient/Program.cs index 08763d3a2438a..9a42a86b9c2ee 100644 --- a/docs/fundamentals/networking/snippets/httpclient/Program.cs +++ b/docs/fundamentals/networking/snippets/httpclient/Program.cs @@ -20,10 +20,10 @@ public static async Task Main(string[] args) await DeleteAsync(sharedClient); // - using HttpClient client = new(); + using HttpClient httpClient = new(); // - await HeadAsync(client); - await OptionsAsync(client); + await HeadAsync(httpClient); + await OptionsAsync(httpClient); } }