Click here to Skip to main content
15,868,099 members
Articles / Web Development / ASP.NET

Using HttpClient As It Was Intended (Because You’re Not)

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
1 Jul 2017CPOL5 min read 63.6K   50   13
Using HttpClient as it was intended

Async programming has become ubiquitous and the standard tool for making async HTTP requests with C# is HttpClient from the System.Net.Http namespace. Examples are aplenty, but good examples are few and far between. Because HttpClient implements IDisposable, we are conditioned to new it up in a using statement, make the call and get out as fast as possible.

This is WRONG.

This blog post will pull together advice from the few good resources online that touch on various aspects of why you are using it wrong, and how to use it correctly.

 

HttpClient is designed for re-use. You should use a single instance of it throughout the lifetime of your application, or at least as few as possible when request signatures will vary (explained later). The main reason for this is that each instance of HttpClient will open a new socket connection and on high traffic sites, you can exhaust the available pool and receive a System.Net.Sockets.SocketException.

Additionally, by re-using instances, you can avoid the not-insignificant overhead of establishing new TCP connections because HttpClient can re-use its existing connections, and do so in a thread safe manner.

So, if : using(var client = new HttpClient())
is wrong, what do we do?

You create a single static instance of HttpClient. In console or desktop applications, you can expose this instance just about anywhere. In an ASP.NET Web API application, you can create it in the Global.asax.cs file.

Recipe: Your Web API always uses the same header values and the same base URL

Global.asax.cs:

C#
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
 
namespace HttpClientGuidance
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        internal static HttpClient httpClientInstance;
 
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
 
            Uri baseUri = new Uri("https://someresource.com");
 
            httpClientInstance = new HttpClient();
            httpClientInstance.BaseAddress = baseUri;
            httpClientInstance.DefaultRequestHeaders.Clear();
            httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
            httpClientInstance.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
 
            ServicePointManager.FindServicePoint(baseUri).ConnectionLeaseTimeout = 60 * 1000;
        }
    }
}

Here we have set up the client one time. All uses of it will:

  1. Use the same base address. You can tack on varying routes (e.g. /api/somecontroller, /api/someothercontroller) without issue.
  2. Set the request content-type to application/json
  3. Will attempt to keep the connection open (.ConnectionClose = false) which makes more efficient use of the client.

Since keeping connections open can prevent load balancing, we compensate by setting a connection lease timeout on the base URL through the ServicePointManager. This ensures connections are used efficiently but not indefinitely.

The ConnectionLeaseTimeout has an additional bonus. Since HttpClient will cache DNS data, you could run into a situation when a DNS change breaks your code because an instance of HttpClient is still using DNS from before the change. This can happen particularly when using a Blue/Green deployment strategy where you deploy an update of your app to production but don’t make it live until flipping DNS – and your base URI depends on those DNS settings. Setting the timeout will minimize downtime.

Basic Usage Example:

C#
using HttpClientGuidance.Models;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
 
namespace HttpClientGuidance.Controllers
{
    public class BasicClientUsageController : ApiController
    {
        public async Task<HttpResponseMessage> Post()
        {
            Fruit grape = new Fruit 
            { FruitName = "Grape", FruitColor = "Purple" };
            return await WebApiApplication.httpClientInstance
                .PostAsJsonAsync("/api/somecontroller", grape);
        }
    }
}

RECIPE: YOUR WEB API uses a few different configurations

While HttpClient is thread safe, not all of its properties are. You can cause some very difficult to identify bugs by re-using the same instance but changing the URL and/or headers before each call. If this configuration will vary, then you cannot use the first recipe.

If you have a manageable number of configurations, create an HttpClient instance for each one. You will be slightly less efficient than an single instance, but significantly more efficient than creating and disposing each time.

C#
//complete Global.asax.cs elided
internal static HttpClient httpClientInstance;
internal static HttpClient twitterClient;
internal static HttpClient bitcoinExchngClient;

In this way, you can re-use the client instances as appropriate. Just set all the headers, base URLs, and ServicePointManager connection timeouts individually.

RECIPE: You have many different APIs to call and maybe you even often add new ones with each software release (or you just like this method best).

In this case, it can become unmanageable to create separate client instances for all of the HTTP calls you make. You can still gain all the advantages of a single client instance by varying your HttpRequestMessage instead of your HttpClient.

Observe our new Global.asax.cs file:

C#
using System.Net;
using System.Net.Http;
using System.Web.Http;
 
namespace HttpClientGuidance
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        internal static HttpClient httpClientInstance;
 
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);            
 
            httpClientInstance = new HttpClient();
            httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
            
            ServicePointManager.FindServicePoint
            ("some uri").ConnectionLeaseTimeout = 60 * 1000;
            ServicePointManager.FindServicePoint
            ("some other uri").ConnectionLeaseTimeout = 60 * 1000;
            ServicePointManager.FindServicePoint
            ("some other other uri").ConnectionLeaseTimeout = 60 * 1000;
            //etc.....
        }
    }
}

You can see that we have eliminated the content type and the base URI because that is going to change often in our use of the client.

Also, we have configured the ConnectionLeaseTimeout for all the URI’s that we know we will be using. If you don’t know all the URIs at design time, then it is okay to do this at runtime – at least it is better to do it at runtime than not at all.

To make use of this single client instance, you will create an HttpRequestMessage with all the configuration that you would have set directly on the HttpClient if it were always going to be the same. Then you make the request through the client’s SendAsync method.

Since it is so common in ASP.NET Web API to send object instances as request content, I am demonstrating the use of ObjectContent but any of the derivatives of HttpContent can be used, StringContent also being very common.

C#
using HttpClientGuidance.Models;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web.Http;
 
namespace HttpClientGuidance.Controllers
{
    public class BasicClientUsageController : ApiController
    {
        public async Task<HttpResponseMessage> Post()
        {
            Fruit grape = new Fruit { FruitName = "Grape", FruitColor = "Purple" };
 
            var msg = new HttpRequestMessage(HttpMethod.Post, "http://someurl");
            msg.Content = new ObjectContent<Fruit>
            (grape, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null);
            msg.Headers.Authorization = new AuthenticationHeaderValue
            ("whatever scheme", "whatever value");
            msg.Headers.Add("some custom hdr", "some value");
 
            return await WebApiApplication.httpClientInstance.SendAsync(msg);
        }
    }
}

While this clearly does not demonstrate all of the options on HttpRequestMessage, the point is that all of the configuration can be done on the message without affecting the efficiency of using a single instance of the client. So you can feel free to create as many varying request messages as you need.

Summing Up

I hope this sheds some light on a topic that is under-documented and yet highly-relevant to .NET developers that are using APIs (is anyone not?).

Despite all you have seen and been taught, do not dispose your HttpClient instance(s). Okay, if your app is some utility app that would never come close to exhausting connections, then it doesn’t really matter as long as you know how to do it right when the time comes. But generally, re-use it, don’t abuse it.

Special thanks to the following resources that I used to pull this together:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) EVO Payments International
United States United States
Full stack developer on .Net and related technologies with heavy emphasis on back-end API development and integrations.

Comments and Discussions

 
Questionhow to use this properly Pin
Steve Stacel20-Apr-20 5:42
Steve Stacel20-Apr-20 5:42 
QuestionHandling GET requests Pin
vbalajich10-Apr-20 23:22
vbalajich10-Apr-20 23:22 
QuestionError while using HttpClient object through out the application Pin
Member 1457072527-Aug-19 22:34
Member 1457072527-Aug-19 22:34 
QuestionDo we need to consider MaxServicePointIdleTime when using a static http client? Pin
ha-man6-Dec-17 9:50
ha-man6-Dec-17 9:50 
AnswerRe: Do we need to consider MaxServicePointIdleTime when using a static http client? Pin
Bob Crowley7-Dec-17 9:15
Bob Crowley7-Dec-17 9:15 
AnswerRe: Do we need to consider MaxServicePointIdleTime when using a static http client? Pin
Bob Crowley7-Dec-17 9:36
Bob Crowley7-Dec-17 9:36 
GeneralRe: Do we need to consider MaxServicePointIdleTime when using a static http client? Pin
ha-man7-Dec-17 9:47
ha-man7-Dec-17 9:47 
GeneralMy vote of 5 Pin
DrABELL12-Aug-17 19:15
DrABELL12-Aug-17 19:15 
Very insightful article serving both practical and didactic aspects. Solid 5* and the vote for the best one!
GeneralMy vote of 5 Pin
Raul Iloc10-Aug-17 9:39
Raul Iloc10-Aug-17 9:39 
GeneralMy vote of 4 Pin
tbayart7-Jul-17 4:31
professionaltbayart7-Jul-17 4:31 
GeneralRe: My vote of 4 Pin
Bob Crowley5-Nov-17 23:10
Bob Crowley5-Nov-17 23:10 
GeneralMy vote of 5 Pin
Kunal Chowdhury «IN»4-Jul-17 2:41
professionalKunal Chowdhury «IN»4-Jul-17 2:41 
GeneralMy vote of 5 Pin
Robert_Dyball2-Jul-17 15:57
professionalRobert_Dyball2-Jul-17 15:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.