Skip to content
This repository was archived by the owner on Nov 17, 2018. It is now read-only.

Consumption Patterns

Ryan Nowak edited this page Mar 21, 2018 · 6 revisions

There are several ways that HttpClientFactory can be used and none of them are strictly superior to another, it really depends on your application and the constraints you are working under.

Using HttpClientFactory Directly

Registration

Here we register a generic HttpClient with no special configuration.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
    services.AddMvc();
}
Consumption

Accepting an IHttpClientFactory and using it to create a HttpClient to use when needed.

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/");
        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. You would just replace the places you create new HttpClients with a call to CreateClient. If you have some configuration commmon to all uses of HttpClient then you can put it into the registration section instead of duplicating it throughout your codebase.

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

Using Named Clients

If you have multiple distinct uses of HttpClient, each with different configurations, then you may want to use named clients.

Registration
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 we call AddHttpClient 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.

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

Consumption
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();
    }
}

In the above code the gitHubClient will have the BaseAddress and headers set where as the defaultClient doesn't. This provides you the with the ability to have different configurations for different purposes. This may mean different configurations per endpoint/API, but could also mean different configuration for different purposes or any other way you choose to pivot client configuration.

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.

NOTE: The factory will re-create a HttpMessageHandler periodically, so you may see more connections as a new connection is created and the previous has not yet been released.

Using Typed Clients

Typed Clients give you the same capabilities as named clients without strings as keys, giving you intellisense and compiler help. 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 dealing with that endpoint. 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 is an example of this later in this document.

Not everyone will want or be able to move configuration like this to the constructor of their Typed Client. It's shown here as an example of something that could be done, rather than a hard recommendation.

Registration
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<GitHubService>();
    services.AddMvc();
}
GitHubService.cs
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;
    }
}
Consumption
public class IndexModel : PageModel
{
    private GitHubService _ghService;

    public IndexModel(GitHubService ghService)
    {
        _ghService = ghService;
    }

    public async Task OnGet()
    {
        var result = await _ghService.Client.GetStringAsync("/orgs/octokit/repos");
    }
}

Generated Clients

When generating a client or proxy that uses HttpClient, such as when using a library such as Refit, you will want the HttpClientFactory to be responsible for creating the HttpClient that your generated code will use.

In this example we will use Refit to generate a proxy for a REST API we are going to consume. When using Refit you create an interface for the API and let Refit generate the code that actually calls the API. You can find out more about Refit here: https://github.com/paulcbetts/refit

We are taking advantage of Refits generated code being able to accept an instance of HttpClient instead of creating its own. As long as the generated code being used in your application allows the same then you should be able to do something similar to what we are doing here without having to change your generated code to take a direct dependency on HttpClientFactory, though that might possible as well.

Interface
public interface IGitHubApi
{
    [Get("/users/{user}")]
    Task<User> GetUser(string user);
}
Registration

The same GitHub registration that we used earlier.

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
})
.AddTypedClient(c => Refit.RestService.For<IGitHubApi>(c));
Consumption

Here we take advantage of Refits ability to accept a HttpClient and give it one that HttpClientFactory manages.

private IGitHubApi _gh;

public HomeController(IGitHubApi gh)
{
    _gh = gh;
}
public async Task<IActionResult> Index()
{
    var user = await _gh.GetUser("glennc");
    return View(user);
}

Examples

Caching Typed Client

In this example we will build a Typed Client that encapsulates all access to my backend service, which in this case is the default Web API template in Visual Studio.

NOTE: This logic would be improved with the addition of a Circuit Breaker library, such as Polly, which will also be available with HttpClientFactory.

public class ValuesService
{
    public HttpClient Client { get; set; }
    public IMemoryCache Cache { get; set; }

    private ILogger<ValuesService> _logger;

    public ValuesService() { }


    public ValuesService(HttpClient client, IMemoryCache cache, ILogger<ValuesService> logger)
    {
        Client = client;
        Cache = cache;
        _logger = logger;
    }

    public virtual async Task<IEnumerable<string>> GetValues()
    {
        var result = await Client.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;
    }
}

This type can then be registered and consumed with code like the following:

Registration
services.AddHttpClient<ValuesService>(client => client.BaseAddress = new Uri(Configuration["values:uri"]));
Consumption
public class IndexModel : PageModel
{
    private ValuesService _valuesService;

    public IEnumerable<string> Values;

    public IndexModel(ValuesService valuesService)
    {
        _valuesService = valuesService;
    }

    public async Task OnGet()
    {
        Values = await _valuesService.GetValues();
    }
}

This pattern shows encapsulating common logic about accessing HTTP endpoints within a single type and makes consumption of that type easy.

Unit Testing

If we take the ValuesService and IndexModel that we showed in the previous example, then we can write tests like the following using Xunit and Moq though the same should work with any testing framework and mocking library (or your own mock types if you prefer):

IndexModel test
[Fact]
public async Task GET_populates_values()
{
    IEnumerable<string> testValues = new List<string>() { "value1", "value2", "value3" };

    var valueService = new Mock<ValuesService>();
    valueService.Setup(x => x.GetValues()).Returns(Task.FromResult(testValues));

    var indexUnderTest = new IndexModel(valueService.Object);

    await indexUnderTest.OnGet();

    Assert.Equal(testValues, indexUnderTest.Values);

}
ValuesService test

The test for the service itself are more complicated as we are mocking the HttpClient itself.

//TODO: Finish cleaning up this example and add to the document.

Clone this wiki locally