Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions docs/fundamentals/networking/http/httpclient.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -265,7 +265,7 @@ This code will throw an `HttpRequestException` if the response status code is no

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.

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

Expand All @@ -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 <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>:

:::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 <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>:

:::code language="csharp" source="../snippets/httpclient/Program.CancellationInnerTimeout.cs" id="innertimeout":::

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.

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:

:::code language="csharp" source="../snippets/httpclient/Program.CancellationStatusCode.cs" id="statuscode":::

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:

- <xref:System.Net.Http.HttpClient.GetByteArrayAsync%2A?displayProperty=nameWithType>
- <xref:System.Net.Http.HttpClient.GetStreamAsync%2A?displayProperty=nameWithType>
- <xref:System.Net.Http.HttpClient.GetStringAsync%2A?displayProperty=nameWithType>

> [!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 <xref:System.Net.Http.HttpRequestException.StatusCode%2A?displayProperty=nameWithType> 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 <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:

:::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 <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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
static partial class Program
{
static async Task WithCancellationAsync(HttpClient httpClient)
{
// <cancellation>
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}");
}
// </cancellation>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
static partial class Program
{
static async Task WithCancellationAndInnerTimeoutAsync(HttpClient httpClient)
{
// <innertimeout>
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}");
}
// </innertimeout>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
static partial class Program
{
static async Task WithCancellationAndStatusCodeAsync(HttpClient httpClient)
{
// <statuscode>
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}");
}
// </statuscode>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
static partial class Program
{
static async Task WithCancellationExtensionsAsync(HttpClient httpClient)
{
// <helpers>
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}");
}
// </helpers>
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
static partial class Program
{
// <delete>
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
static partial class Program
{
// <get>
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
static partial class Program
{
// <getfromjson>
static async Task GetFromJsonAsync(HttpClient client)
static async Task GetFromJsonAsync(HttpClient httpClient)
{
var todos = await client.GetFromJsonAsync<List<Todo>>(
var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
"todos?userId=1&completed=false");

WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
static partial class Program
{
// <head>
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
static partial class Program
{
// <options>
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
static partial class Program
{
// <patch>
static async Task PatchAsync(HttpClient client)
static async Task PatchAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
static partial class Program
{
// <post>
static async Task PostAsync(HttpClient client)
static async Task PostAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
static partial class Program
{
// <postasjson>
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));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
static partial class Program
{
// <put>
static async Task PutAsync(HttpClient client)
static async Task PutAsync(HttpClient httpClient)
{
using StringContent jsonContent = new(
JsonSerializer.Serialize(new
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
static partial class Program
{
// <putasjson>
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));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
static partial class Program
{
static async Task HandleResponsesAsync<T>(HttpClient client)
static async Task HandleResponsesAsync<T>(HttpClient httpClient)
{
using HttpRequestMessage request = new(
HttpMethod.Head,
"https://www.example.com");

// <request>
using HttpResponseMessage response = await client.SendAsync(request);
using HttpResponseMessage response = await httpClient.SendAsync(request);
// </request>

// <isstatuscode>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
static partial class Program
{
static async Task ThrowHttpRequestExceptionAsync(HttpClient httpClient)
{
// <throw>
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}");
}
// </throw>
}
}
6 changes: 3 additions & 3 deletions docs/fundamentals/networking/snippets/httpclient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ public static async Task Main(string[] args)
await DeleteAsync(sharedClient);

// <client>
using HttpClient client = new();
using HttpClient httpClient = new();
// </client>

await HeadAsync(client);
await OptionsAsync(client);
await HeadAsync(httpClient);
await OptionsAsync(httpClient);
}
}