By a show of a hands, I'd like to see how many of us have been here before: We've been tasked to solve some new technical problem. We know the technologies exists to help us implement a working solution and we don't want to re-invent the wheel so what do we do? Open up our browser to our favorite search engine and enter those keywords we know will guide us in the right direction. Our first hit takes us to a solution and we try it out and bingo problem solved!

The only problem however is sometimes the first solution is not the best solution. We tend to take the path of least resistance. But the problem is sometimes this only takes us so far or only partially solves the issue. Let's take the simple example of making an HTTP request in .Net core.

Simple Web Request

Here is a simple example of how we can create an HttpClient to make a simple HTTP request to download the content as a string:

 using (HttpClient client = new HttpClient())
        using (HttpResponseMessage response = await client.GetAsync("http://www.keithmsmith.com"))
        using (HttpContent content = response.Content)
        {
            string result = await content.ReadAsStringAsync();

            Console.WriteLine(result);
        }
    }

At the outset you might think there is nothing wrong with this code. You used a using statement so it should call dispose when the action is complete, it's also asynchronous so it won't block the thread as it's being processed. This might standup in development with no real issues, but when thrown into the real world we're going to start encountering a few variables that could shut it down.

High Traffic

As you start pumping pushing some serious volume to this code you're quickly going to run into problems. Even though the using statement will dispose of the object, the underlying stream is not released right away. After some usage you'll start encountering a serious issue termed 'Socket exhaustion'.

The reason is because the HttpClient is meant to be instantiated once and reused throughout the applications lifetime. However, one negative side effect of this approach is that it doesn't respect DNS changes that are made.

To help resolve these issues, starting in .Net core 2.1 they introduced a new HttpClientFactory to help us get around these issues. You will need to setup dependency injection within your application and run the AddHttpClient method during the configureServices process. For more information on setting up dependency injection in your .Net Core 3.0 application, go here.

var builder = new HostBuilder()
  .ConfigureServices((hostContext, services) =>
  {
     services.AddHttpClient();
  
  }).UseConsoleLifetime();

After the HttpClient has been registered, you can inject the IHttpClientFactory interface into your classes.

public class Service : IService
{
    private readonly IHttpClientFactory _clientFactory;

    public MyService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<string> GetContent()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
               "https://www.keithmsmith.com");
               var client = _clientFactory.CreateClient();
               var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            return await response.Content.ReadAsStringAsync();
        }
    }
}

Add a Single Throttling Exception

At this point we've resolved our socket exhaustion and DNS issues and we're doing much better. However, a second reality is we're going to run into transient errors.

Can you imagine taking a cross country road trip without expecting to ever hit a red light or a little bumper to bumper traffic? Of course not! Those types of delays should be expected. We need to expect delays in our external services - Keith Smith

We need to be prepared to encounter some issues along the way including transient exceptions. Fortunately we have a solution, Polly. By their definition, Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. Polly targets .NET 4.0, .NET 4.5 and .NET Standard 1.1.

This policy driven configuration provides tremendous power to help us overcome common transient error handling and implementing a retry policy.

To get started, install the following Nuget package:

Install-Package Polly.Extensions.Http

Then add these policy handlers to your HttpClient registration:

var retryPolicy = HttpPolicyExtensions
  .HandleTransientHttpError()
  .Or<TimeoutRejectedException>() // thrown by Polly's TimeoutPolicy if the inner execution times out
  .RetryAsync(3);

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);  

serviceCollection.AddHttpClient()
  .AddPolicyHandler(retryPolicy)
  .AddPolicyHandler(timeoutPolicy);

Photo by Ondřej Neduchal on Unsplash