C# – Switch from using HttpWebRequest to HttpClient

There are many reasons to use HttpClient instead of HttpWebRequest. For one, the MSDN docs strongly recommends against using HttpWebRequest, and using HttpClient instead. That should be enough, but if you need a little more convincing, take a look at the Practical reasons to not use HttpWebRequest section below.

In addition to explaining why not to use HttpWebRequest, this article shows how to refactor code that uses HttpWebRequest to use HttpClient instead.

Practical reasons to not use HttpWebRequest

Here are four practical reasons to not use HttpWebRequest.

  • Practical Reason 1 – Microsoft strongly recommends to not use HttpWebRequest anymore.

This warning is straight from the MSDN documentation regarding HttpWebRequest:

We don’t recommend that you use HttpWebRequest for new development. Instead, use the System.Net.Http.HttpClient class.

  • Practical Reason 2 – You cannot reuse the same HttpWebRequest object for multiple requests.

If you try to reuse the same HttpWebRequest object, you get the following exception:

System.InvalidOperationException: ‘This operation cannot be performed after the request has been submitted.’

  • Practical Reason 3 – Every time you create a new HttpWebRequest and make a request, it allocates a socket on a new port. Not only is this terrible for performance, but it will eventually lead to port exhaustion. This can be seen by using netstat -an. I sent 6 requests using HttpWebRequest within 30 seconds, and here you can see there are 6 ports in the TIME_WAIT state.
netstat -an | find "44388" TCP LISTENING TCP [::]:44388 [::]:0 LISTENING TCP [::1]:13095 [::1]:44388 TIME_WAIT TCP [::1]:13097 [::1]:44388 TIME_WAIT TCP [::1]:13098 [::1]:44388 TIME_WAIT TCP [::1]:13099 [::1]:44388 TIME_WAIT TCP [::1]:13102 [::1]:44388 TIME_WAIT TCP [::1]:13103 [::1]:44388 TIME_WAIT
  • Practical Reason 4 – It’s tedious to use. Compare the HttpWebRequest code with the HttpClient code in the sections below.

In contrast, HttpClient solves all of these problems. It’s simple to use, and it was specifically built to make multiple concurrent requests. You only need to create one HttpClient instance and use it repeatedly. It reuses open connections, and is therefore way better for performance and avoids the port exhaustion problem.

Code that uses HttpWebRequest

The following code uses HttpWebRequest to POST a message to an endpoint. The endpoint returns a response message.

This shows typical usage of HttpWebRequest. It creates the HttpWebRequest object, sets some headers, writes the request content stream, then parses the response content stream.

I put comments for each distinct step that it’s doing, and put the same comments in the equivalent steps in the refactored code that uses HttpClient. This should make it easier to see the difference between using HttpWebRequest and HttpClient.

public class MessageSender { string url = "https://localhost:44388/message"; public async Task<string> PostMessage(string jsonMessage) { //Create the request sender object var request = WebRequest.Create(url) as HttpWebRequest; //Initialize the request content var contentBuffer = Encoding.UTF8.GetBytes(jsonMessage); request.ContentType = "application/json"; request.ContentLength = contentBuffer.Length; //Set the headers request.UserAgent = "MessageService/3.1"; //Send the POST request.Method = "POST"; using (var requestStream = request.GetRequestStream()) { requestStream.Write(contentBuffer, 0, contentBuffer.Length); requestStream.Flush(); requestStream.Close(); } //Get the response status and content using (var httpResponse = await request.GetResponseAsync() as HttpWebResponse) { //Check for error status if (httpResponse.StatusCode != HttpStatusCode.OK) { throw new HttpRequestException(httpResponse.StatusDescription); } using (var responseStream = httpResponse.GetResponseStream()) { using (var streamReader = new StreamReader(responseStream)) { return streamReader.ReadToEnd(); } } } } }

Refactored code that uses HttpClient instead of HttpWebRequest

Here is refactored code that uses HttpClient.

As you can see, HttpClient abstracts away a lot of the tedious stuff you had to do with HttpWebRequest.

using System.Net.Http; public class MessageSender : IDisposable { private readonly HttpClient httpClient; string url = "https://localhost:44388/message"; public MessageSender() { //Create the request sender object httpClient = new HttpClient(); //Set the headers httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("MessageService/3.1"); } public async Task<string> PostMessage(string jsonMessage) { /* Initialize the request content and Send the POST */ var response = await httpClient.PostAsync(url, new StringContent(jsonMessage, Encoding.UTF8, "application/json")); //Check for error status response.EnsureSuccessStatusCode(); //Get the response content return await response.Content.ReadAsStringAsync(); } public void Dispose() { httpClient?.Dispose(); } }

I left the comments from the original code so you can easily compare the equivalent distinct steps when using HttpClient compared with HttpWebRequest.

Let’s go through each comment.

  • Create the request sender object

The first major difference is that you only need to create one HttpClient object and reuse it repeatedly.

It implements IDisposable, but you only need to dispose it after you are completely done sending requests. Do not dispose the HttpClient instance if you intend on sending more requests. Do not use HttpClient in a using block.

  • Set the headers

As you can see in the code, I am sending the same header (UserAgent) with the same value every time. It makes sense to simply set the httpClient.DefaultRequestHeaders once in the constructor. Of course you can set the headers in every request if they change every time. I didn’t need to do that in my use case, so that’s not shown here.

  • Initialize the request content

This is simply creating a StringContent object, specifying that it’s using UTF8 encoding, and setting the content type to “application/json”.

  • Send the POST

This is a one liner. It’s simply calling await httpClient.PostAsync(url, content). In fact, initializing the content and sending POST have been reduced to a single line. Compare that with the HttpWebRequest code and you see that it’s much simpler and less tedious.

  • Check for error status

This, too, has been reduced to a one liner. Just call response.EnsureSuccessStatusCode(). This’ll throw an HttpRequestException if the status code isn’t a 200 (OK).

  • Get the response content

This has also been reduced to a one liner. It simply calls await response.Content.ReadAsStringAsync(). This greatly reduces the tediousness of parsing the response.

Leave a Comment