-
Notifications
You must be signed in to change notification settings - Fork 25.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Starting work on HttpClientFactory documentation #5483
Changes from 2 commits
1b897ac
37a56fb
0523d99
ca0cc90
7026d16
ed1b3dd
bfed490
812cf28
86e239d
ec14f81
d527081
ad34a62
37a5deb
b5aa44b
135a6e1
b62b134
c71f194
a7e1c51
590150d
567afbb
5d18655
2251cb9
c8b6438
aaf26d3
c9d0daf
7825296
b077c4b
44be1a5
f95bd56
d231feb
957f760
f715142
05e139f
af9c32f
aaf3240
fdf6d7c
c7d9e6d
b2c7d60
699e07d
e4bd7f0
d7a5b8c
4c76827
8a37fc7
b34cda6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
--- | ||
title: Making HTTP Requests | ||
author: stevejgordon | ||
description: Learn about using the HttpClientFactory features to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to complete this sentence. |
||
manager: wpickett | ||
ms.author: riande | ||
ms.date: 02/15/2018 | ||
ms.prod: asp.net-core | ||
ms.technology: aspnet | ||
ms.topic: article | ||
uid: fundamentals/http-requests | ||
--- | ||
# Making HTTP Requests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For titles, we use sentence casing and avoid using gerunds. I suggest a title like "Initiate HTTP requests". |
||
|
||
By [Glenn Condron](https://github.com/glennc), [Ryan Nowak](https://github.com/rynowak) and [Steve Gordon](https://github.com/stevejgordon) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a comma before "and" |
||
|
||
A `HttpClientFactory` can be registered and used to configure and consume `HttpClient` instances in your application. It provides several benefits: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shorten "application" to "app" all throughout. |
||
|
||
1. Provides a central location for naming and configuring logical HttpClients. For example, you may configure a “github” client that is pre-configured to access github and a default client for other purposes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace the smart quotes on "github" with normal straight quotes. |
||
2. Codifies the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly based middleware to take advantage of that. HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. The factory will make registering of these per named client more intuitive as well as implement a Polly handler that allows Polly policies to be used for Retry, CircuitBreakers, etc. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
3. Manage the lifetime of HttpClientMessageHandlers to avoid common DNS problems that can be hit when managing HttpClient lifetimes yourself. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest adding another bullet point here:
|
||
## Consumption patterns | ||
|
||
There are several ways that `HttpClientFactory` can be used in your application. None of them are strictly superior to another, it really depends on your application and the constraints you are working under. | ||
|
||
## Basic usage | ||
|
||
The `HttpClientFactory` can be used directly in your code to access `HttpClient` instances. First the services must be registered with the ServiceProvider. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a comma after "First". |
||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddHttpClient(); | ||
services.AddMvc(); | ||
} | ||
``` | ||
|
||
Once registered, you can accept a `IHttpClientFactory` in your constructor which can then be used to create a `HttpClient`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably want to generalize this a bit, make sure people know it is DI magic and can work wherever you can inject services. Not just constructors. |
||
|
||
```csharp | ||
public class MyController : Controller | ||
{ | ||
IHttpClientFactory _httpClientFactory; | ||
|
||
public MyController(IHttpClientFactory httpClientFactory) | ||
{ | ||
_httpClientFactory = httpClientFactory; | ||
} | ||
|
||
public IActionResult Index() | ||
{ | ||
var client = _httpClientFactory.CreateClient(); | ||
var result = client.GetStringAsync("http://myurl/"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this line use |
||
return View(); | ||
} | ||
} | ||
``` | ||
|
||
Using `HttpClientFactory` like this is a good way to start refactoring an existing application, as it has no impact on the way you use HttpClient. In places where you create HttpClients, replace those with a call to `CreateClient()`. | ||
|
||
## Named clients | ||
|
||
If you have multiple distinct uses of HttpClient, each with different configurations, then you may want to use **named clients**. The common configuration for the use of that named HttpClient can be specified during registration. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddHttpClient("github", c => | ||
{ | ||
c.BaseAddress = new Uri("https://api.github.com/"); | ||
|
||
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning | ||
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent | ||
}); | ||
services.AddHttpClient(); | ||
} | ||
``` | ||
|
||
Here `AddHttpClient` has been called twice; once with the name 'github' and once without. The github specific client has some default configuration applied, namely the base address and two headers required to work with the GitHub API. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
The configuration function here will get called every time CreateClient is called, as a new instance of HttpClient is created each time. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CreateClient -->
|
||
|
||
To consume a named client in your code you can pass the name of the client to `CreateClient`. | ||
|
||
```csharp | ||
public class MyController : Controller | ||
{ | ||
IHttpClientFactory _httpClientFactory; | ||
|
||
public MyController(IHttpClientFactory httpClientFactory) | ||
{ | ||
_httpClientFactory = httpClientFactory; | ||
} | ||
|
||
public IActionResult Index() | ||
{ | ||
var defaultClient = _httpClientFactory.CreateClient(); | ||
var gitHubClient = _httpClientFactory.CreateClient("github"); | ||
return View(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the wrong line got replaced here, 2 clients are created and neither is used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you intended to create two clients here, I have a bit of a gripe with it because we'd never tell a user to do this 😆 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally took this sample the text below it from @glennc's work on the Wiki. It appeared the intent was to show both options being used within one app. Happy to factor this out of the final sample app though and focus on each use case in isolation. In the sample the current approach is to register multiple clients to show the various consumption patterns. Do we need to consider that when designing the sample? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think registering multiple clients is fine, my specific gripe is about this method ... |
||
} | ||
} | ||
``` | ||
|
||
In the above code the gitHubClient will have the BaseAddress and headers set whereas the defaultClient does not. This provides you the with the ability to have different configurations for different purposes. This may mean different configurations per endpoint/API for example. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the above code --> In the preceding code,
|
||
|
||
HttpClientFactory will create, and cache, a single HttpMessageHandler per named client. Meaning that if you were to use netstat or some other tool to view connections on the host machine you would generally see a single TCP connection for each named client, rather than one per instance when you new-up and dispose of a HttpClient manually. | ||
|
||
## Typed clients | ||
|
||
Typed Clients give you the same capabilities as named clients without the need for using strings as keys. This provides intellisense and compiler help when consuming clients. They also provide a single location to configure and interact with a particular HttpClient. For example, a single typed client might be used for a single backend endpoint and encapsulate all logic which deals with that endpoint. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
A typed client is expected to accept a HttpClient via it's constructor. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HttpClient --> |
||
|
||
```csharp | ||
public class GitHubService | ||
{ | ||
public HttpClient Client { get; private set; } | ||
|
||
public GitHubService(HttpClient client) | ||
{ | ||
client.BaseAddress = new Uri("https://api.github.com/"); | ||
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning | ||
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent | ||
|
||
Client = client; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /cc @glennc for visibility. I'm not a super fan of this. If you end up needing to use configuration to get these values then you have to find a way to pass that in here. Perhaps Glenn and I need to discuss this and come to an understanding. |
||
} | ||
} | ||
``` | ||
|
||
To register a typed client the generic AddHttpClient method can be used, specifying our typed client class. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddHttpClient<GitHubService>(); | ||
services.AddMvc(); | ||
} | ||
``` | ||
|
||
The typed client is registered as transient with the DI framework. | ||
|
||
The typed client can then be injected and consumed directly. | ||
|
||
```csharp | ||
public class MyController : Controller | ||
{ | ||
private GitHubService _gitHubService; | ||
|
||
public MyController(GitHubService gitHubService) | ||
{ | ||
_gitHubService = gitHubService; | ||
} | ||
|
||
public IActionResult Index() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to mark this action as |
||
{ | ||
var result = await _gitHubService.Client.GetStringAsync("/orgs/octokit/repos"); | ||
return Ok(result); | ||
} | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great 👍 |
||
|
||
In this example we only moved configuration into the type, but we could also have methods with behaviour and not actually expose the HttpClient if we want all access to go through this type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a comma after "In this example". |
||
|
||
If you prefer, the configuration for a typed client can be specified during registration, rather than in the constructor for the typed client. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddHttpClient<GitHubService>(c => | ||
{ | ||
c.BaseAddress = new Uri("https://api.github.com/"); | ||
|
||
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning | ||
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent | ||
}); | ||
services.AddMvc(); | ||
} | ||
``` | ||
|
||
If we want to entirely encapsulate the HttpClient in our typed client, rather than exposing it as a property we can define our own methods which control the client | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be rewritten without the first person. |
||
|
||
```csharp | ||
public class ValuesService | ||
{ | ||
private readonly HttpClient _httpClient; | ||
private readonly IMemoryCache _cache; | ||
private readonly ILogger<ValuesService> _logger; | ||
|
||
public ValuesService() { } | ||
|
||
public ValuesService(HttpClient client, IMemoryCache cache, ILogger<ValuesService> logger) | ||
{ | ||
_httpClient = client; | ||
_cache = cache; | ||
_logger = logger; | ||
} | ||
|
||
public async Task<IEnumerable<string>> GetValues() | ||
{ | ||
var result = await _httpClient.GetAsync("api/values"); | ||
var resultObj = Enumerable.Empty<string>(); | ||
|
||
if (result.IsSuccessStatusCode) | ||
{ | ||
resultObj = JsonConvert.DeserializeObject<IEnumerable<string>>(await result.Content.ReadAsStringAsync()); | ||
_cache.Set("GetValue", resultObj); | ||
} | ||
else | ||
{ | ||
if (_cache.TryGetValue("GetValue", out resultObj)) | ||
{ | ||
_logger.LogWarning("Returning cached values as the values service is unavailable."); | ||
return resultObj; | ||
} | ||
result.EnsureSuccessStatusCode(); | ||
} | ||
return resultObj; | ||
} | ||
} | ||
``` | ||
|
||
In this example the HttpClient is stored as a private field and all access to make external calls goes through the GetValues method. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a comma after "In this example". |
||
|
||
## Generated clients | ||
|
||
TODO - Refit? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refit integration demo is here: https://github.com/rynowak/HttpClientFactoryDemos/tree/master/RefitSample There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added, thanks! |
||
|
||
## Outgoing request middleware | ||
|
||
TODO | ||
|
||
## Handling errors with Polly | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to pass over this section for now since Polly is WIP |
||
|
||
TODO | ||
|
||
## DNS and handler rotation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be great to call this section 'HttpClient' and lifetime management and introduce some of these concepts a bit more gently. |
||
|
||
Each time you call CreateClient you get a new instance of HttpClient, but the factory will reuse the underlying HttpMessageHandler when appropriate. The HttpMessageHandler is responsible for creating and maintainging the underlying Operating System connection. Reusing the HttpMessageHandler will save you from creating many connections on your host machine. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,3 +208,7 @@ For more information, see [Choosing between .NET Core and .NET Framework](/dotne | |
## Choose between ASP.NET Core and ASP.NET | ||
|
||
For more information on choosing between ASP.NET Core and ASP.NET, see [Choose between ASP.NET Core and ASP.NET](xref:fundamentals/choose-between-aspnet-and-aspnetcore). | ||
|
||
## Making HTTP requests | ||
|
||
For information about using `HttpClientFactory` to access HttpClient instances to make HTTP requests, see [Making HTTP requests](xref:fundamentals/http-requests). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the title in the link text to the suggested title (e.g., "Initiate HTTP requests"). |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,6 +136,7 @@ | |
## [Microsoft.AspNetCore.All metapackage](xref:fundamentals/metapackage) | ||
## [Choose between .NET Core and .NET Framework](/dotnet/articles/standard/choosing-core-framework-server) | ||
## [Choose between ASP.NET Core and ASP.NET](choose-aspnet-framework.md) | ||
## [Making HTTP Requests](fundamentals/http-requests.md) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since changing this it seems to throw a warning on the build system? |
||
|
||
# [MVC](mvc/overview.md) | ||
## [Razor Pages](xref:mvc/razor-pages/index) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For titles, we use sentence casing and avoid using gerunds. I suggest a title like "Initiate HTTP requests".