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 for GitHub client:
/// <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
:
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;
}
}
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:
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.
Top comments (0)