Skip to main content

3 ways to use HTTPClientFactory in ASP.NET Core 2.1

Microsoft introduced the HttpClient in .Net Framework 4.5 and is the most popular way to consume a Web API in your .NET server-side code. But it has some serious issues like disposing the HttpClient object doesn’t close the socket immediately, too many instances affecting the performance and Singleton HttpClient or shared HttpClient instance not respecting the DNS Time to Live (TTL) settings. HttpClientFactory solves the all these problems. It is one of the newest feature of ASP.NET Core 2.1. It provides a central location for naming and configuring and consuming logical HttpClients in your application, and this post talks about 3 ways to use HTTPClientFactory in ASP.NET Core 2.1.

3 ways to use HTTPClientFactory in ASP.NET Core 2.1

There are 3 different ways to use it and we’ll see an example of each of them.

  • Using HttpClientFactory Directly
  • Named Clients
  • Typed Clients

Using HttpClientFactory Directly

Irrespective of the above mention way you choose, you’ll always have to register the HttpClient in ConfigureServices method of the Startup.cs class. The following line of code registers HttpClient with no special configuration.

services.AddHttpClient();

You can use it in the following way in the API controller.

public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;
 
    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }
 
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = new Uri("http://api.github.com");
        string result = await client.GetStringAsync("/");
        return Ok(result);
    }
}

Named Clients

The basic use of HTTPClientFactory in above example is ideal in a situation where you need to make a quick request from a single place in the code. When you need to make multiple requests from multiple places from your code, “Named Clients” will help you. With named clients, you can define the HTTP client with some pre-configured settings which will be applied when creating the HttpClient. Like,

services.AddHttpClient();
services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

Here we call AddHttpClient twice, once with the name ‘github’ and once without. The github client has some default configuration applied, namely the base address and two headers required to work with the GitHub API. The overload of AddHttpClient method accepts two parameters, a name and an Action delegate taking a HttpClient which allows us to configure the HttpClient.

You can use named client in the following way in the API controller.

public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;
 
    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }
 
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient("github");
        string result = await client.GetStringAsync("/");
        return Ok(result);
    }
}

Here, we are passing the registered name of the client in CreateClient() method to create HttpClient. This is useful as the default configuration defined at the time of registration will be pre-applied when we ask for a named client.

Typed Client

Using Typed clients, you can define pre-configuration for your HttpClient inside a custom class. This custom class can be registered as Typed client, and later when needed, it can be injected via the calling class constructor. I prefer Typed Client for the following reasons,

  • Flexible approach compare to named clients.
  • You no longer have to deal with strings (like in named clients).
  • You can encapsulate the HTTP calls and all logic dealing with that endpoint.

Let’s see an example. Below is a custom class defined for Github client.

public class GitHubClient
{
    public HttpClient Client { get; private set; }
   
    public GitHubClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri("https://api.github.com/");
        httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
        Client = httpClient;
    }
}

You can register this as a typed client using the following line.

services.AddHttpClient<GitHubClient>();

And, use it in the following way in the API controller.

public class ValuesController : Controller
{
    private readonly GitHubClient _gitHubClient;;
 
    public ValuesController(GitHubClient gitHubClient)
    {
        _gitHubClient = gitHubClient;
    }
 
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        string result = await _gitHubClient.client.GetStringAsync("/");
        return Ok(result);
    }
}

This works great. There is another better way of making typed client work. Here, the HttpClient is exposed directly, but you can encapsulate the HttpClient entirely using the following way. First, define a contract for the GitHubClient.

public interface IGitHubClient
{
    Task<string> GetData();
}

public class GitHubClient : IGitHubClient
{
    private readonly HttpClient _client;

    public GitHubClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri("https://api.github.com/");
        httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
        _client = httpClient;
    }

    public async Task<string> GetData()
    {
        return await _client.GetStringAsync("/");
    }
}

Register this as a typed client using the following line.

services.AddHttpClient<IGitHubClient, GitHubClient>();

And, use it in the following way in the API controller.

public class ValuesController : Controller
{
    private readonly IGitHubClient _gitHubClient;;
    
    public ValuesController(IGitHubClient gitHubClient)
    {
        _gitHubClient = gitHubClient;
    }
    
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        string result = await _gitHubClient.GetData();
        return Ok(result);
    }
}

This approach also makes unit testing easy while testing HttpClients as you no longer have to mock them.

That’s it. You can read all my ASP.NET Core 2.1 post here.

Recommended Reading:

Summary

To summarize, this post talks about all 3 possible ways to use HttpClientFactory with ASP.NET Core 2.1. I prefer to use TypedClient as it’s a lot more flexible, provides encapsulation for HttpClient and makes it easy to test the code. HttpClientFactory is great addition to ASP.NET Core 2.1 as it addresses the problems of HttpClient.

Thank you for reading. Keep visiting this blog and share this in your network. Please put your thoughts and feedback in the comments section.

PS: If you found this content valuable and want to return the favour, then Buy Me A Coffee

17 thoughts to “3 ways to use HTTPClientFactory in ASP.NET Core 2.1”

  1. New to this. Can I use httpclientfactory manually in the .Net Framework console app.
    Like in the main method, do something like factory = new httpclientfactory(), and then pass the factory to my other class… just simply don’t do anything with the StartUp, Builder, Dependency Injection thing….

  2. I am trying to use a Typed client and send in another scoped item in the constructor but it is not working.
    Here is my Constructor for the Client:
    public LeagueStatClient(HttpClient httpClient, ITeamHelpers teamHelpers)
    {
    httpClient.BaseAddress = new Uri(“http://lscluster.hockeytech.com/feed/index.php”);
    httpClient.DefaultRequestHeaders.Clear();
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
    client = httpClient;

    this.teamHelpers = teamHelpers;
    }

    I am getting the error Cannot resolve scoped service ‘ScoreTrackers.Interfaces.ITeamHelpers’ from root provider.

    I have in my startup:
    services.AddHttpClient();
    services.AddScoped();

    Any help would be appreciated.

      1. I am doing that, sorry, when I pasted the code, it didn’t paste everything.
        My startup lines are:
        services.AddHttpClient[ILeagueStatClient, LeagueStatClient]();
        services.AddScoped[TeamHelpers, TeamHelpers]();

        Note, I change the greater than and less than symbols with brackets so they show.

        1. Thanks for letting me know. I think the issue is due to scoped service registration. The registered scoped services can’t be injected via constructor. You need to directly inject it in the action method parameters or change it to Transient.

          Like,
          services.AddTransient<ITeamHelpers, TeamHelpers>();

          1. Changing to AddTransient worked.
            I need to read up on the difference between Scoped and Transient.
            Thanks for your quick help!
            I love your Blog!

          2. For typed clients, how can I set the HttpClientHandler if it comes in as a dependency. Would they be defined in the AddServices? Can you give an example?

  3. For typed clients, how can I set the HttpClientHandler if it comes in as a dependency. Would they be defined in the AddServices? Can you give an example?

  4. Hi, how can I specify the outgoing IP address of a http request in ASP.NET Core 2.1 ?
    I mean on a machine that has two or more IP addresses I would like to use the source (originating) addresses selectively for the outgoing http requests.
    I am desperately searching for info on this all over the net, but have not yet found the solution,
    Thank you in advance for any clues.

    1. This was reported to me earlier also and I corrected it 2 times, don’t know why it keeps coming again and again. Meanwhile, corrected it again and changed the variables names this time. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *