You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化C#库中自定义HttpClient的DI配置与替换逻辑?

自定义C#第三方API包装库的HttpClient配置优化方案

问题背景

我正在为自己编写的第三方API调用C#包装库寻找合适的自定义HttpClient配置方案,已在仓库的单独分支完成了配置库的扩展方法及相关DI实现,核心需求是:

  • 允许消费者为不同服务指定不同的HttpClient配置(例如TextService用默认配置,ChatService用带代理的自定义配置)
  • 简化自定义类型化HttpClient的配置流程
  • 避免先调用默认注册后再自定义时,旧客户端实例残留在DI容器中的问题

现有实现

默认使用方式

builder.Services.Configure<GeminiHttpClientOptions>(builder.Configuration.GetSection("Gemini"));
builder.Services.AddGemini();

核心扩展方法实现

public static class GeminiExtensions
{
    public static IServiceCollection AddGemini(this IServiceCollection services)
    {
        services.AddTransient<GeminiAuthHandler<GeminiHttpClientOptions>>();

        services.AddHttpClient<GeminiClient>((sp, client) =>
        {
            var options = sp.GetRequiredService<IOptions<GeminiHttpClientOptions>>().Value;
            client.BaseAddress = options.Url;
        })
        .AddHttpMessageHandler<GeminiAuthHandler<GeminiHttpClientOptions>>();

        services.AddTransient<ITextService, TextService>();
        services.AddTransient<IVisionService, VisionService>();
        services.AddTransient<IChatService, ChatService>();
        services.AddTransient<IEmbeddingService, EmbeddingService>();
        services.AddTransient<IModelInfoService, ModelInfoService>();

        return services;
    }
}

类型化HttpClient实现

public class GeminiClient
{
    private readonly HttpClient _httpClient;

    public GeminiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<TResponse> GetAsync<TResponse>(string endpoint)
    {
        var response = await _httpClient.GetAsync(endpoint);
        return await HandleResponse<TResponse>(response)
               ?? throw new GeminiException("The API has returned a null response.");
    }

    public async Task<TResponse> PostAsync<TRequest, TResponse>(string endpoint, TRequest data)
    {
        var serializedContent = JsonSerializer.Serialize(data, options: new JsonSerializerOptions
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        });
        var jsonContent = new StringContent(serializedContent, Encoding.UTF8, "application/json");
        var response = await _httpClient.PostAsync(endpoint, jsonContent);
        return await HandleResponse<TResponse>(response)
               ?? throw new GeminiException("The API has returned a null response.");
    }

    private static async Task<T> HandleResponse<T>(HttpResponseMessage response)
    {
        var content = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            return JsonSerializer.Deserialize<T>(content)
                   ?? throw new GeminiException("The API has returned a null response.");
        }
        else
        {
            var geminiError = JsonSerializer.Deserialize<ApiErrorResponse>(content)
                              ?? throw new GeminiException("The API has returned a null response.");
            throw new GeminiException(geminiError, geminiError.error.message);
        }
    }
 }

当前自定义痛点

现在要实现自定义HttpClient,需要以下繁琐步骤:

  1. 创建GeminiClient的子类
  2. 重复编写HttpClient注册逻辑
  3. 手动注册对应服务
  4. 若先调用AddGemini再自定义,旧的客户端实例会残留在DI容器中

优化方案

1. 扩展AddGemini方法,支持直接配置默认HttpClient

修改扩展方法,增加接受HttpClient配置委托的重载,让用户无需创建子类即可自定义默认客户端:

public static class GeminiExtensions
{
    public static IServiceCollection AddGemini(this IServiceCollection services, Action<IHttpClientBuilder>? configureHttpClient = null)
    {
        services.AddTransient<GeminiAuthHandler<GeminiHttpClientOptions>>();

        // 先移除已有的GeminiClient注册,避免残留
        var existingClientReg = services.FirstOrDefault(sd => sd.ServiceType == typeof(GeminiClient));
        if (existingClientReg != null)
        {
            services.Remove(existingClientReg);
        }

        // 注册并配置默认HttpClient
        var clientBuilder = services.AddHttpClient<GeminiClient>((sp, client) =>
        {
            var options = sp.GetRequiredService<IOptions<GeminiHttpClientOptions>>().Value;
            client.BaseAddress = options.Url;
        })
        .AddHttpMessageHandler<GeminiAuthHandler<GeminiHttpClientOptions>>();

        // 执行用户自定义配置
        configureHttpClient?.Invoke(clientBuilder);

        // 注册业务服务
        RegisterServices(services);

        return services;
    }

    // 抽离服务注册逻辑,方便复用
    private static void RegisterServices(IServiceCollection services)
    {
        services.AddTransient<ITextService, TextService>();
        services.AddTransient<IVisionService, VisionService>();
        services.AddTransient<IChatService, ChatService>();
        services.AddTransient<IEmbeddingService, EmbeddingService>();
        services.AddTransient<IModelInfoService, ModelInfoService>();
    }
}

用户使用示例:

builder.Services.Configure<GeminiHttpClientOptions>(builder.Configuration.GetSection("Gemini"));
builder.Services.AddGemini(clientBuilder =>
{
    clientBuilder.ConfigurePrimaryHttpMessageHandler(() =>
    {
        var proxy = new WebProxy
        {
            Address = new Uri("http://localhost:1080/")
        };
        var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true };
        // 仅用于测试,生产环境请勿使用
        handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
        return handler;
    });
});

2. 支持多命名HttpClient,实现服务级别的配置隔离

扩展方法,允许注册命名HttpClient,并为指定服务绑定该客户端:

public static class GeminiExtensions
{
    // 注册命名HttpClient
    public static IHttpClientBuilder AddGeminiNamedClient(this IServiceCollection services, string clientName)
    {
        services.AddTransient<GeminiAuthHandler<GeminiHttpClientOptions>>();

        return services.AddHttpClient(clientName, (sp, client) =>
        {
            var options = sp.GetRequiredService<IOptions<GeminiHttpClientOptions>>().Value;
            client.BaseAddress = options.Url;
        })
        .AddHttpMessageHandler<GeminiAuthHandler<GeminiHttpClientOptions>>();
    }

    // 为ChatService绑定指定命名客户端
    public static IServiceCollection AddGeminiChatServiceWithNamedClient(this IServiceCollection services, string clientName)
    {
        services.AddTransient<IChatService>(sp =>
        {
            var factory = sp.GetRequiredService<IHttpClientFactory>();
            var httpClient = factory.CreateClient(clientName);
            return new ChatService(new GeminiClient(httpClient));
        });
        return services;
    }
}

用户使用示例:

// 注册默认客户端及所有服务
builder.Services.AddGemini();

// 注册带代理的命名客户端
builder.Services.AddGeminiNamedClient("ProxyGeminiClient")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var proxy = new WebProxy("http://localhost:1080/");
        return new HttpClientHandler { Proxy = proxy, UseProxy = true };
    });

// 让ChatService使用带代理的客户端
builder.Services.AddGeminiChatServiceWithNamedClient("ProxyGeminiClient");

3. 强制覆盖DI容器中的旧注册

在扩展方法中,使用Replace方法确保新注册覆盖旧实例:

// 替换已有的GeminiClient注册
services.Replace(ServiceDescriptor.Transient<GeminiClient>(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    var httpClient = factory.CreateClient("CustomGeminiClient");
    return new GeminiClient(httpClient);
}));

内容的提问来源于stack exchange,提问作者Junaid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:20:31