AugusteVN.HttpClient.Delegator 1.0.3

dotnet add package AugusteVN.HttpClient.Delegator --version 1.0.3
NuGet\Install-Package AugusteVN.HttpClient.Delegator -Version 1.0.3
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.0.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add AugusteVN.HttpClient.Delegator --version 1.0.3
#r "nuget: AugusteVN.HttpClient.Delegator, 1.0.3"
#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.
// Install AugusteVN.HttpClient.Delegator as a Cake Addin
#addin nuget:?package=AugusteVN.HttpClient.Delegator&version=1.0.3

// Install AugusteVN.HttpClient.Delegator as a Cake Tool
#tool nuget:?package=AugusteVN.HttpClient.Delegator&version=1.0.3

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 that keyed singleton.

This way, any caller of that HttpClient can access or alter those header values on the 'shared' singleton. It holds a Local and a Global set of those 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.AddHttpClientStateResolver<NewsletterSubscribersHttpClient>(nameof(TUtils));

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

var app = builder.Build();

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

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

app.Run();

public record TUtils;
public record TPetService;

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($"AuthService Local: client name: [{_httpState.Local.ClientName}], headers: [{string.Join(',', _httpState.Local.Headers)}]");
    }

    public void PrintGlobalState()
    {
        Console.WriteLine($"AuthService 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-service", "foo");
    }

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

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

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

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

    public void PrintGlobalState()
    {
        Console.WriteLine($"Newsletter Global: client names: [{string.Join(',', _httpState.Global.ClientNames)}], headers: [{string.Join(',', _httpState.Global.Headers)}]");
    }
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.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.0.3 511 11/27/2023
1.0.2 830 11/26/2023

Update README.