AugusteVN.HttpClient.Delegator 1.1.0

dotnet add package AugusteVN.HttpClient.Delegator --version 1.1.0
                    
NuGet\Install-Package AugusteVN.HttpClient.Delegator -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AugusteVN.HttpClient.Delegator" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AugusteVN.HttpClient.Delegator" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="AugusteVN.HttpClient.Delegator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AugusteVN.HttpClient.Delegator --version 1.1.0
                    
#r "nuget: AugusteVN.HttpClient.Delegator, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AugusteVN.HttpClient.Delegator@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AugusteVN.HttpClient.Delegator&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=AugusteVN.HttpClient.Delegator&version=1.1.0
                    
Install as a Cake Tool

Delegating Handler

Adds a custom HttpClient message handler / delegating handler that can add headers to the request. The delegator gets those values from a keyed singleton and registers a resolver for it.

This way, any caller of that HttpClient can access or alter those header values on the 'shared' singleton. It holds a Local set and a Global set of headers. The local values are only applicable to the HttpClient you added the delegator to. The global values are applied on all HttpClients. Local values can override global values, not the other way around.

The interface of the resolver 'IHttpClientFactory<T>' implements the original 'IHttpClientFactory', hides its methods but uses those under the hood. So, you can use the .CreateClient but also have the singleton values available by just one dependency injection instead of two.

It contains what was covered in this video: Authorize HttpClient with Delegating Handler C# .NET

Sandbox, work in progress

builder.Services.AddHttpClient(nameof(TUtils), client => client.BaseAddress = new Uri("https://localhost:3000"))
    .AddDelegator<TUtils>();

builder.Services.AddHttpClient(nameof(TPetService), client => client.BaseAddress = new Uri("https://localhost:3005"))
    .AddDelegator<TPetService>();

builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<PetService>();

var httpClientName = nameof(TUtils);
/*
 * Adds another resolver that can resolve the correct named HttpClient and its 'state' singleton.
 * To be able to inject 'IHttpClientFactory<T>' where you can't access 'TUtils' e.g. in a higher-level, stand-alone module.
 */
builder.Services.AddHttpClientStateResolver<ModuleService>(httpClientName);
builder.Services.AddScoped<ModuleService>();

var app = builder.Build();

app.MapGet("/", (AuthService auth, ModuleService module, PetService pet) =>
{
    auth.AuthorizeClient("<access-token>");
    auth.PrintLocalState(); // Auth Local, client name: [TUtils], headers: [[x-module, bar]]
    auth.PrintGlobalState(); // Auth Global, client names: [TPetService,TUtils], headers: [[x-module, foo],[Authorization, Bearer <access-token>]]
    
    module.PrintLocalState(); // Module Local, client name: [TUtils], headers: [[x-module, bar]]
    module.PrintGlobalState(); // Module Global, client names: [TPetService,TUtils], headers: [[x-module, foo],[Authorization, Bearer <access-token>]]

    pet.PrintLocalState(); // Pet Local, client name: [TPetService], headers: [[x-pet, foo]]
    pet.PrintGlobalState(); // Pet Global, client names: [TPetService,TUtils], headers: [[x-module, foo],[Authorization, Bearer <access-token>]]
    Results.Ok();
});

app.Run();

public record TUtils; // Only used as client name.
public record TPetService; // Only used as client name.

public class AuthService
{
    private readonly HttpClient _httpClient;
    private readonly IHttpClientFactory<TUtils> _httpState;
    public AuthService(IHttpClientFactory<TUtils> httpState)
    {
        _httpClient = httpState.CreateClient();
        _httpState = httpState;
    }

    public void AuthorizeClient(string? token)
    {
        _httpState.Global.AddHeader("Authorization", $"Bearer {token}");
    }

    public void PrintLocalState()
    {
        Console.WriteLine($"Auth Local, client name: [{_httpState.Local.ClientName}], headers: [{string.Join(',', _httpState.Local.Headers)}]");
    }

    public void PrintGlobalState()
    {
        Console.WriteLine($"Auth Global, client names: [{string.Join(',', _httpState.Global.ClientNames)}], headers: [{string.Join(',', _httpState.Global.Headers)}]");
    }
}

public class PetService
{
    private readonly HttpClient _httpClient;
    private readonly IHttpClientFactory<TPetService> _httpState;
    public PetService(IHttpClientFactory<TPetService> httpState)
    {
        _httpClient = httpState.CreateClient();
        _httpState = httpState;
        _httpState.Local.AddHeader("x-pet", "foo");
    }

    public void PrintLocalState()
    {
        Console.WriteLine($"Pet Local, client name: [{_httpState.Local.ClientName}], headers: [{string.Join(',', _httpState.Local.Headers)}]");
    }

    public void PrintGlobalState()
    {
        Console.WriteLine($"Pet Global, client names: [{string.Join(',', _httpState.Global.ClientNames)}], headers: [{string.Join(',', _httpState.Global.Headers)}]");
    }
}

public class ModuleService
{
    private readonly HttpClient _httpClient;
    private readonly IHttpClientFactory<ModuleService> _httpState;
    public ModuleService(IHttpClientFactory<ModuleService> httpState)
    {
        _httpClient = httpState.CreateClient();
        _httpState = httpState;
        _httpState.Local.AddHeader("x-module", "bar");
        _httpState.Global.AddHeader("x-module", "foo");
    }

    public void PrintLocalState()
    {
        Console.WriteLine($"Module Local, client name: [{_httpState.Local.ClientName}], headers: [{string.Join(',', _httpState.Local.Headers)}]");
    }

    public void PrintGlobalState()
    {
        Console.WriteLine($"Module Global, client names: [{string.Join(',', _httpState.Global.ClientNames)}], headers: [{string.Join(',', _httpState.Global.Headers)}]");
    }
}
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 46 7/9/2026
1.0.3 769 11/27/2023
1.0.2 1,557 11/26/2023

Upgraded to .NET 10.