如何通过IHttpClientFactory在启动时配置API密钥作为查询字符串?
我需要调用一个外部API,要求将API密钥作为查询字符串传递,该API包含GET和POST端点,目前这两个端点没有其他查询参数。示例请求地址如下:https://example.com/service?key={key value}
我不想在所有调用API的地方手动添加这个查询参数,希望在项目启动阶段统一配置。我见过把查询字符串作为HTTP客户端路由一部分的示例,用问号分隔路由和查询参数。
有没有无需编写复杂扩展/逻辑,就能在启动时注入作为查询字符串的密钥的方法?
我的依赖注入配置如下:
builder.Services.AddHttpClient(); builder.Services.AddScoped<IMyService, MyService>();
消费代码如下:
public class MyService : IMyService { private readonly IHttpClientFactory _httpClientFactory; public MyService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } private HttpClient CreateClient() => _httpClientFactory.CreateClient(); public async Task<RouteDetails> GetRouteDetails(ICollection<Point> points) { var httpClient = CreateClient(); var response = await httpClient.PostAsync("/api/poi", new JsonContent(points)); // ...后续处理 } }
我希望方案简洁易读、代码量少。这类用查询字符串传密钥的API很常见,应该有我没注意到的开箱即用方案。我也可以用Refit来实现。
编辑说明:之前的注入代码有误,但不影响问题本质,正如@PeterCsala建议,类型化客户端不应注入IHttpClientFactory,而应直接注入HttpClient,我已更新为研究时使用的可用代码。
方案1:配置带查询参数的基地址(最简单)
直接在注册HttpClient时,把包含API密钥的完整基地址配置好,后续所有请求会自动带上该查询参数。
修改DI配置:
var apiKey = "你的密钥值"; builder.Services.AddHttpClient("ExternalApiClient", client => { client.BaseAddress = new Uri($"https://example.com/service?key={apiKey}"); });
修改消费代码,指定创建命名客户端:
private HttpClient CreateClient() => _httpClientFactory.CreateClient("ExternalApiClient");
调用PostAsync("/api/poi", ...)时,实际请求地址会自动拼接为https://example.com/service/api/poi?key={apiKey}。
方案2:用HttpClient消息处理程序(更灵活)
如果需要动态修改密钥,或不想将密钥写在基地址中,可以自定义简单的消息处理程序,自动给每个请求添加查询参数。
定义消息处理程序:
public class ApiKeyQueryHandler : DelegatingHandler { private readonly string _apiKey; public ApiKeyQueryHandler(string apiKey) { _apiKey = apiKey; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var uriBuilder = new UriBuilder(request.RequestUri); var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query); query["key"] = _apiKey; uriBuilder.Query = query.ToString(); request.RequestUri = uriBuilder.Uri; return await base.SendAsync(request, cancellationToken); } }
在DI中注册:
var apiKey = "你的密钥值"; builder.Services.AddHttpClient("ExternalApiClient") .AddHttpMessageHandler(() => new ApiKeyQueryHandler(apiKey)) .ConfigureHttpClient(client => { client.BaseAddress = new Uri("https://example.com/service/"); });
消费代码同方案1,使用命名客户端即可。
方案3:用Refit实现(声明式更简洁)
如果使用Refit,可以直接在接口定义中指定查询参数,无需手动处理请求。
首先安装Refit包:
Install-Package Refit
定义Refit接口:
public interface IExternalApi { [Post("/api/poi")] Task<RouteDetails> GetRouteDetails([Body] ICollection<Point> points, [Query] string key); }
在DI中注册时统一传入密钥:
var apiKey = "你的密钥值"; builder.Services.AddRefitClient<IExternalApi>() .ConfigureHttpClient(client => { client.BaseAddress = new Uri("https://example.com/service/"); }) .AddQueryStringParameters(new { key = apiKey });
修改消费代码,直接注入IExternalApi:
public class MyService : IMyService { private readonly IExternalApi _externalApi; public MyService(IExternalApi externalApi) { _externalApi = externalApi; } public async Task<RouteDetails> GetRouteDetails(ICollection<Point> points) { return await _externalApi.GetRouteDetails(points); } }
调用时Refit会自动将密钥作为查询参数添加到请求中。
内容的提问来源于stack exchange,提问作者Mykola

