HTTP Message Handlers

HTTP message handlers are a fundamental building block in .NET for sending and receiving HTTP requests and responses. They allow you to create flexible and extensible HTTP communication pipelines.

Core Concepts

The primary classes involved are:

  • HttpMessageHandler: The base abstract class for all HTTP message handlers.
  • HttpClient: A high-level class for sending HTTP requests. It uses a pipeline of HttpMessageHandler instances to process requests and responses.

HttpMessageHandler Pipeline

HttpClient creates a pipeline of HttpMessageHandlers. Each handler in the pipeline can inspect, modify, or generate HTTP messages. The pipeline is established by setting the HttpClient.MessageHandler property or by using a HttpClientFactory.

Common Built-in Handlers

  • HttpClientHandler: The default handler that manages cookies, handles redirects, and applies proxy settings.
  • HttpRequestValidationHandler: Used in ASP.NET to validate incoming HTTP requests.
  • DelegatingHandler: A base class for creating custom handlers that wrap other handlers, allowing you to add cross-cutting concerns like logging, authentication, or retry logic.

Creating Custom Handlers

To create a custom message handler, you typically inherit from DelegatingHandler. You override the SendAsync method to intercept the request and/or response.

Here's a basic example of a logging handler:


public class LoggingHandler : DelegatingHandler
{
    public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
    {
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Log the outgoing request
        Console.WriteLine($"Request: {request.Method} {request.RequestUri}");

        // Send the request to the inner handler
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // Log the incoming response
        Console.WriteLine($"Response: {response.StatusCode}");

        return response;
    }
}
                

Using Handlers with HttpClient

You can compose handlers to build a powerful request pipeline. For example, to use the LoggingHandler with a default HttpClientHandler:


var loggingHandler = new LoggingHandler(new HttpClientHandler());
var httpClient = new HttpClient(loggingHandler);

var response = await httpClient.GetAsync("https://example.com");
Console.WriteLine($"Final Status: {response.StatusCode}");
                

HttpClientFactory

For managing the lifecycle of HttpClient instances and configuring handlers, especially in ASP.NET Core applications, IHttpClientFactory is the recommended approach. It provides:

  • Centralized configuration of handlers.
  • Automatic management of HttpClient lifetimes.
  • Resilience policies (e.g., retry policies) through Polly integration.

Example using HttpClientFactory:


// In Startup.cs (or Program.cs for .NET 6+)
services.AddHttpClient("MyApiClient")
    .AddHttpMessageHandler<LoggingHandler>();

// In your service
public class MyService
{
    private readonly HttpClient _httpClient;

    public MyService(IHttpClientFactory clientFactory)
    {
        _httpClient = clientFactory.CreateClient("MyApiClient");
    }

    public async Task DoSomethingAsync()
    {
        var response = await _httpClient.GetAsync("https://api.example.com/data");
        response.EnsureSuccessStatusCode();
        // ... process response
    }
}
                

Key Scenarios for Message Handlers

  • Authentication: Adding authentication headers.
  • Logging: Recording request and response details.
  • Retry Logic: Automatically retrying failed requests.
  • Caching: Implementing caching for responses.
  • Request/Response Transformation: Modifying request bodies or response headers.
  • Proxying: Configuring custom proxy behavior.
Important: Always dispose of HttpClient and its associated handlers properly to avoid resource leaks. HttpClientFactory handles this automatically.

Further Reading