Robust use of HTTP Client
Tags: programming, csharp, aspnet, tutorial
Published: 2023-08-19 05:21:49

In this blog post, I will show you how to use `HttpClient` and `DelegatingHandler` in ASP.NET Core to create custom HTTP clients that can handle cross-cutting concerns such as logging, authentication, caching, etc. These classes are part of the `System.Net.Http` namespace and provide a flexible and extensible way to interact with HTTP services.

`HttpClient` is a class that provides a high-level abstraction for sending HTTP requests and receiving HTTP responses. It supports asynchronous operations, cancellation tokens, headers, content, etc. You can use `HttpClient` to consume any HTTP service, such as REST APIs, SOAP services, etc.

`DelegatingHandler` is an abstract class that represents a handler for HTTP requests and responses. It can act as a middleware in the HTTP pipeline, intercepting and modifying the requests and responses before they reach the final destination. You can create your own subclasses of DelegatingHandler to implement specific logic for your HTTP clients.

`IHttpClientFactory` is an interface that defines a factory for creating HttpClient instances. It also manages the lifetime and pooling of the underlying `HttpMessageHandler` instances, which are used by HttpClient to send HTTP requests. You can register your own implementations of `IHttpClientFactory` in the dependency injection container of ASP.NET Core and use them to create `HttpClient` instances with different configurations and behaviours.

In ASP.NET Core, you can use the `AddHttpClient` extension method to register your `IHttpClientFactory` implementations and configure your `HttpClient` instances. You can also use the `AddHttpMessageHandler` extension method to add your `DelegatingHandler` instances to the HTTP pipeline of your `HttpClient` instances. This way, you can create custom HTTP clients that can handle various cross-cutting concerns in a consistent and reusable way.

For example, I have created a [typed clients](https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#typed-clients) for GitHub client:

```csharp
/// <summary>
/// Typed clients using of IHttpClientFactory for GithubClient
/// </summary>
public sealed class GithubClient : IDisposable
{
    private readonly HttpClient _httpClient;

    public GithubClient(HttpClient httpClient) => _httpClient = httpClient;

    public async Task<string> GetHomePageAsync() => await _httpClient.GetStringAsync("https://github.com");

    public void Dispose() => _httpClient?.Dispose();
}
```

and for logging and exception handling I have created two more classes that inherit from `DelegatingHandler`:

```csharp
public sealed class LoggingHandler : DelegatingHandler
{
    private readonly ILogger<LoggingHandler> _logger;

    public LoggingHandler(ILogger<LoggingHandler> logger) => _logger = logger;

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Sending request!");

        var response = base.SendAsync(request, cancellationToken);

        _logger.LogInformation("Request was successful!");

        return response;
    }
}
```

```csharp
public sealed class HttpClientExceptionHandler : DelegatingHandler
{
    private readonly ILogger<HttpClientExceptionHandler> _logger;

    public HttpClientExceptionHandler(ILogger<HttpClientExceptionHandler> logger) => _logger = logger;

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            return await base.SendAsync(request, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex?.Message);

            throw;
        }
    }
}
```

and registered them in the `Program.cs` file:

```csharp
builder.Services.AddScoped<LoggingHandler>();
builder.Services.AddScoped<HttpClientExceptionHandler>();

builder.Services.AddHttpClient<GithubClient>()
    .AddHttpMessageHandler<LoggingHandler>()
    .AddHttpMessageHandler<HttpClientExceptionHandler>();
```

The main advantage of this approach is that instead of putting all logging and exception handling codes into one file (**GithubClient**) I have separated them into different files for each responsibility and just used `.AddHttpMessageHandler` to use them for intercepting.