# Extending HttpClient With Delegating Handlers in ASP.NET Core

> Delegating handlers are like ASP.NET Core middleware. Except they work with the HttpClient. I'll show you how to work with delegating handlers

Published: 2024-01-13. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore

Delegating handlers are like ASP.NET Core middleware for outgoing HTTP requests.
You inherit from the `DelegatingHandler` base class and override `SendAsync` to add behavior before or after an `HttpClient` sends a request.
This is useful for cross-cutting concerns like logging, resiliency, and authentication.

[Delegating handlers](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=net-8.0) are like [ASP.NET Core middleware](https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core).
Except they work with the [`HttpClient`](https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet).
The ASP.NET Core request pipeline allows you to introduce custom behavior with [middleware.](https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core)
You can solve many cross-cutting concerns using middleware — logging, tracing, validation, authentication, authorization, etc.

But, an important aspect here is that middleware works with incoming HTTP requests to your API.
Delegating handlers work with outgoing requests.

[`HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-8.0) is my preferred way of sending HTTP requests in ASP.NET Core.
It's straightforward to use and solves most of my use cases.
You can use delegating handlers to extend the `HttpClient` with behavior before or after sending an HTTP request.

Today, I want to show you how to use a [`DelegatingHandler`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=net-8.0) to introduce:

- Logging
- Resiliency
- Authentication

## Configuring an HttpClient

Here's a very simple application that:

- Configures the `GitHubService` class as a typed HTTP client
- Sets the `HttpClient.BaseAddress` to point to the GitHub API
- Exposes an endpoint that retrieves a GitHub user by their username

We're going to extend the `GitHubService` behavior using delegating handlers.

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
});

var app = builder.Build();

app.MapGet("api/users/{username}", async (
    string username,
    GitHubService gitHubService) =>
{
    var content = await gitHubService.GetByUsernameAsync(username);

    return Results.Ok(content);
});

app.Run();
```

The `GitHubService` class is a [typed client](https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet#replacing-named-clients-with-typed-clients) implementation.
Typed clients allow you to expose a strongly typed API and hide the `HttpClient`.
The runtime takes care of providing a configured `HttpClient` instance through dependency injection.
You also don't have to think about disposing of the `HttpClient`.
It's resolved from an underlying **`IHttpClientFactory`** that manages the `HttpClient` lifetime.

```csharp
public class GitHubService(HttpClient client)
{
    public async Task<GitHubUser?> GetByUsernameAsync(string username)
    {
        var url = $"users/{username}";

        return await client.GetFromJsonAsync<GitHubUser>(url);
    }
}
```

## Logging HTTP Requests Using Delegating Handlers

Let's start with a simple example.
We will add logging before and after sending an HTTP request.
For this, we will to create a custom delegating handler - `LoggingDelegatingHandler`.

The custom delegating handler implements the `DelegatingHandler` base class.
Then, you can override the `SendAsync` method to introduce additional behavior.

```csharp
public class LoggingDelegatingHandler(ILogger<LoggingDelegatingHandler> logger)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        try
        {
            logger.LogInformation("Before HTTP request");

            var result = await base.SendAsync(request, cancellationToken);

            result.EnsureSuccessStatusCode();

            logger.LogInformation("After HTTP request");

            return result;
        }
        catch (Exception e)
        {
            logger.LogError(e, "HTTP request failed");

            throw;
        }
    }
}
```

You also need to register the `LoggingDelegatingHandler` with dependency injection.
Delegating handlers must be registered as **transient** services.

The `AddHttpMessageHandler` method adds the `LoggingDelegatingHandler` as a delegating handler for the `GitHubService`.
Any HTTP request sent using the `GitHubService` will first go through the `LoggingDelegatingHandler`.

```csharp {1,7}
builder.Services.AddTransient<LoggingDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>();
```

Let's see what else we can do.

## Adding Resiliency With Delegating Handlers

Building [resilient](https://learn.microsoft.com/en-us/dotnet/core/resilience/?tabs=dotnet-cli) applications is an important requirement for cloud development.

The `RetryDelegatingHandler` class uses [Polly](https://github.com/App-vNext/Polly) to create an `AsyncRetryPolicy`.
The **retry policy** wraps the HTTP request and retries it in case of a transient failure.

```csharp
public class RetryDelegatingHandler : DelegatingHandler
{
    private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy =
        Policy<HttpResponseMessage>
            .Handle<HttpRequestException>()
            .RetryAsync(2);

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var policyResult = await _retryPolicy.ExecuteAndCaptureAsync(
            () => base.SendAsync(request, cancellationToken));

        if (policyResult.Outcome == OutcomeType.Failure)
        {
            throw new HttpRequestException(
                "Something went wrong",
                policyResult.FinalException);
        }

        return policyResult.Result;
    }
}
```

You also need to register the `RetryDelegatingHandler` with dependency injection.
Also, remember to configure it as a message handler.
In this example, I'm chaining two delegating handlers together, and they will run one after another.

```csharp {1,8}
builder.Services.AddTransient<RetryDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddHttpMessageHandler<RetryDelegatingHandler>();
```

## Solving Authentication With Delegating Handlers

Authentication is a cross-cutting concern you will have to solve in any microservices application.
A common use case for delegating handlers is adding the `Authorization` header before sending an HTTP request.

For example, the GitHub API requires an access token to be present for authenticating incoming requests.
The `AuthenticationDelegatingHandler` class adds the `Authorization` header value from the `GitHubOptions`.
Another requirement is specifying the `User-Agent` header, which is set from the app configuration.

```csharp
public class AuthenticationDelegatingHandler(IOptions<GitHubOptions> options)
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        request.Headers.Add("Authorization", options.Value.AccessToken);
        request.Headers.Add("User-Agent", options.Value.UserAgent);

        return base.SendAsync(request, cancellationToken);
    }
}
```

Don't forget to configure the `AuthenticationDelegatingHandler` with the `GitHubService`:

```csharp {1,9}
builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddHttpMessageHandler<RetryDelegatingHandler>()
.AddHttpMessageHandler<AuthenticationDelegatingHandler>();
```

Here's a more involved authentication example using the `KeyCloakAuthorizationDelegatingHandler`.
This is a delegating handler that acquires the access token from [Keycloak](https://www.keycloak.org/).
Keycloak is an open-source identity and access management service.

I used Keycloak as the identity provider in my [Pragmatic Clean Architecture](https://milanjovanovic.tech/pragmatic-clean-architecture) course.

The delegating handler in this example uses an [OAuth 2.0](https://oauth.net/2/) [client credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant flow to obtain an access token.
This grant is used when applications request an access token to access their own resources, not on behalf of a user.

```csharp
public class KeyCloakAuthorizationDelegatingHandler(
    IOptions<KeycloakOptions> keycloakOptions)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var authToken = await GetAccessTokenAsync();

        request.Headers.Authorization = new AuthenticationHeaderValue(
            JwtBearerDefaults.AuthenticationScheme,
            authToken.AccessToken);

        var httpResponseMessage = await base.SendAsync(
            request,
            cancellationToken);

        httpResponseMessage.EnsureSuccessStatusCode();

        return httpResponseMessage;
    }

    private async Task<AuthToken> GetAccessTokenAsync()
    {
        var params = new KeyValuePair<string, string>[]
        {
            new("client_id", _keycloakOptions.Value.AdminClientId),
            new("client_secret", _keycloakOptions.Value.AdminClientSecret),
            new("scope", "openid email"),
            new("grant_type", "client_credentials")
        };

        var content = new FormUrlEncodedContent(params);

        var authRequest = new HttpRequestMessage(
            HttpMethod.Post,
            new Uri(_keycloakOptions.TokenUrl))
        {
            Content = content
        };

        var response = await base.SendAsync(authRequest, cancellationToken);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<AuthToken>() ??
               throw new ApplicationException();
    }
}
```

## Takeaway

Delegating handlers give you a powerful mechanism to extend the behavior when sending requests with an `HttpClient`.
You can use delegating handlers to solve cross-cutting concerns, similar to how you would use middleware.

Here are a few ideas on how you could use delegating handlers:

- Logging before and after sending HTTP requests
- Introducing resilience policies (retry, fallback)
- Validating the HTTP request content
- Authenticating with an external API

I'm sure you can come up with a few use cases yourself.

I made a video showing how to [implement delegating handlers](https://youtu.be/_u6v4D6qgDI), and you can [watch it here.](https://youtu.be/_u6v4D6qgDI)

Thanks for reading, and stay awesome!

---

## Frequently asked questions

### What is a delegating handler in .NET?

A delegating handler is like ASP.NET Core middleware for outgoing HTTP requests. You inherit from the DelegatingHandler base class and override SendAsync to add behavior before or after an HttpClient sends a request, which is useful for logging, resiliency, and authentication.

### What is the difference between middleware and delegating handlers?

Middleware works with incoming HTTP requests arriving at your API, while delegating handlers work with outgoing requests sent through an HttpClient. Both solve cross-cutting concerns like logging, validation, and authentication, just on opposite sides of the HTTP call.

### How do you add a delegating handler to an HttpClient?

Register the handler with dependency injection as a transient service, then call AddHttpMessageHandler on the AddHttpClient registration. You can chain multiple handlers, and they run one after another for every request the client sends.

### How do you add retry logic to HttpClient requests?

Create a delegating handler that wraps base.SendAsync in a Polly AsyncRetryPolicy. The policy retries the HTTP request when a transient failure like an HttpRequestException occurs, and you can surface a failure if the final outcome is still unsuccessful.

### How do you add an authentication header to every HttpClient request?

Use a delegating handler that sets the Authorization header in SendAsync before the request goes out. It can read a static token from configuration, or acquire one from an identity provider like Keycloak using the OAuth 2.0 client credentials flow.

### What is a typed HttpClient in ASP.NET Core?

A typed client is a class that receives a configured HttpClient through dependency injection and exposes a strongly typed API around it. You don't manage disposal yourself, because the client is resolved from an IHttpClientFactory that manages the HttpClient lifetime.
