Skip to content
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

Merged
merged 44 commits into from
May 2, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1b897ac
Starting work on HttpClientFactory documentation (WIP)
stevejgordon Feb 15, 2018
37a56fb
Fixing some minor issues
stevejgordon Feb 15, 2018
0523d99
Updating per initial feedback
stevejgordon Feb 16, 2018
ca0cc90
Adding refit example
stevejgordon Feb 16, 2018
7026d16
Further feedback adjustments
stevejgordon Feb 16, 2018
ed1b3dd
Adding delegating handler middleware section
stevejgordon Feb 16, 2018
bfed490
Minor typo
stevejgordon Feb 16, 2018
812cf28
Initial Polly example
stevejgordon Feb 16, 2018
86e239d
Updating with latest feedback
stevejgordon Feb 19, 2018
ec14f81
Pass to remove you/your + improve readability and clarity
stevejgordon Feb 19, 2018
d527081
Additional tidy-up
stevejgordon Feb 19, 2018
ad34a62
Edits for latest feedback
stevejgordon Feb 21, 2018
37a5deb
UE pass
scottaddie Feb 21, 2018
b5aa44b
Snippet fixes
stevejgordon Feb 22, 2018
135a6e1
Add ms.custom metadata
scottaddie Feb 22, 2018
b62b134
Adding WIP sample project
stevejgordon Feb 23, 2018
c71f194
Minor edits to Fundamentals index page
scottaddie Feb 23, 2018
a7e1c51
Relocate master TOC link
scottaddie Feb 23, 2018
590150d
Feedback and sample update to 2.1 preview
stevejgordon Feb 27, 2018
567afbb
Fixing some missed HttpClientFactory items
stevejgordon Mar 1, 2018
5d18655
Initial snippets from sample
stevejgordon Mar 1, 2018
2251cb9
Fixing a copy/paste mistake
stevejgordon Mar 1, 2018
c8b6438
fixing code link
stevejgordon Mar 1, 2018
aaf26d3
Remove gerund from title
scottaddie Mar 1, 2018
c9d0daf
Fix wording in the description metadata value
scottaddie Mar 1, 2018
7825296
Minor verbiage tweaks
scottaddie Mar 2, 2018
b077c4b
WIP
stevejgordon Mar 20, 2018
44be1a5
Working on Polly sample and documentation
stevejgordon Apr 4, 2018
f95bd56
Using code from sample in docs
stevejgordon Apr 4, 2018
d231feb
UE pass
scottaddie Apr 5, 2018
957f760
Convert usage H2 headings to H3
scottaddie Apr 5, 2018
f715142
Adding initial logging information
stevejgordon Apr 6, 2018
05e139f
Verbiage tweaks
scottaddie Apr 10, 2018
af9c32f
Updating to 2.1 preview 2
stevejgordon Apr 16, 2018
aaf3240
Removing HTTPS redirection since launchSettings does not default to c…
stevejgordon Apr 17, 2018
fdf6d7c
Add monikerRange metadata for 2.1+
scottaddie Apr 23, 2018
c7d9e6d
Major sample and doc content update
stevejgordon Apr 26, 2018
b2c7d60
Adding section about configuring HttpMessageHandler
stevejgordon Apr 27, 2018
699e07d
Updating with feedback from Ryan
stevejgordon Apr 27, 2018
e4bd7f0
Feedback and updates
stevejgordon Apr 28, 2018
d7a5b8c
Incorporate Acrolinx feedback
scottaddie Apr 30, 2018
4c76827
Updating sample with preferred idioms and minor doc tweaks
stevejgordon May 2, 2018
8a37fc7
Fixing grammar and spelling
stevejgordon May 2, 2018
b34cda6
Add 2.1 Preview include
scottaddie May 2, 2018
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
241 changes: 241 additions & 0 deletions aspnetcore/fundamentals/http-requests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
---
title: Making HTTP Requests
Copy link
Member

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".

author: stevejgordon
description: Learn about using the HttpClientFactory features to
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

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".


By [Glenn Condron](https://github.com/glennc), [Ryan Nowak](https://github.com/rynowak) and [Steve Gordon](https://github.com/stevejgordon)
Copy link
Member

Choose a reason for hiding this comment

The 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:
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use a bulleted list here instead of a numbered list.
  • github --> GitHub

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Polly based --> Polly-based
  • We try to avoid the word "will" in our docs. In this case, start the sentence with "The factory makes registering these...".

3. Manage the lifetime of HttpClientMessageHandlers to avoid common DNS problems that can be hit when managing HttpClient lifetimes yourself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest adding another bullet point here:

  • Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

## 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.
Copy link
Member

Choose a reason for hiding this comment

The 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`.
Copy link
Contributor

Choose a reason for hiding this comment

The 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/");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this line use await? If so, also update the action signature.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Change the semicolon to a colon.
  • github specific --> GitHub-specific


The configuration function here will get called every time CreateClient is called, as a new instance of HttpClient is created each time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateClient --> CreateClient

  • Whenever you're referencing a member name (e.g., class, method, variable, etc.), it should be code-fenced.


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();
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the above code --> In the preceding code,

  • Prefer the word "preceding" over the word "above"


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Typed Clients --> Typed clients
  • intellisense --> IntelliSense


A typed client is expected to accept a HttpClient via it's constructor.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpClient --> 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;
Copy link
Member

Choose a reason for hiding this comment

The 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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to mark this action as async and return a Task.

{
var result = await _gitHubService.Client.GetStringAsync("/orgs/octokit/repos");
return Ok(result);
}
}
```
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Need a comma after "rather than exposing it as a property".
  • Need a period at the end of this sentence.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a comma after "In this example".


## Generated clients

TODO - Refit?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, thanks!


## Outgoing request middleware

TODO

## Handling errors with Polly
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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.


4 changes: 4 additions & 0 deletions aspnetcore/fundamentals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Copy link
Member

Choose a reason for hiding this comment

The 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").

1 change: 1 addition & 0 deletions aspnetcore/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

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".
  • For links to other docs, prefer the xref style of linking. In this case, use the following inside the parens:
    xref:fundamentals/http-requests

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Expand Down