ASP.NET Core中HttpClient调用SendAsync前如何执行异步方法
官方原生支持方案
.NET 从.NET Core 2.1 引入IHttpClientFactory开始就提供了原生能力支持这类异步操作需求,不需要任何第三方库,也不需要魔改框架逻辑,最推荐使用DelegatingHandler 请求管道拦截的方案实现,完全适配异步获取令牌、自动注入请求头的场景。
方案1:自定义DelegatingHandler(推荐,在SendAsync环节执行逻辑)
这个方案是官方专门为HttpClient请求前置/后置拦截设计的机制,DelegatingHandler的核心方法SendAsync本身就是异步签名,天然支持await执行异步的令牌获取、缓存读写逻辑,不会有同步阻塞的问题。
实现步骤
- 编写自定义令牌注入Handler
using System.Net.Http; using System.Net.Http.Headers; public class BearerTokenDelegatingHandler : DelegatingHandler { // 直接通过构造函数注入你需要的服务,不管是缓存服务还是OAuth令牌请求服务都可以 private readonly ITokenCache _tokenCache; private readonly IOAuthTokenClient _oAuthTokenClient; public BearerTokenDelegatingHandler(ITokenCache tokenCache, IOAuthTokenClient oAuthTokenClient) { _tokenCache = tokenCache; _oAuthTokenClient = oAuthTokenClient; } protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // 先读缓存中的有效令牌,缓存 miss 则异步调用OAuth端点获取新令牌 var accessToken = await _tokenCache.GetValidAccessTokenAsync(cancellationToken); if (string.IsNullOrWhiteSpace(accessToken)) { var tokenResponse = await _oAuthTokenClient.GetTokenAsync(cancellationToken); accessToken = tokenResponse.AccessToken; // 把新令牌写入缓存,过期时间按OAuth返回的expires_in配置即可 await _tokenCache.SetTokenAsync(accessToken, tokenResponse.ExpiresIn, cancellationToken); } // 注意:只给当前请求对象加Authorization头,不要修改HttpClient的DefaultRequestHeaders // 因为IHttpClientFactory会池化复用HttpClient实例,修改默认头会导致多请求串令牌、令牌过期不更新的问题 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // 执行后续请求管道逻辑 return await base.SendAsync(request, cancellationToken); } }
- 注册服务到DI容器
// 注意:挂到HttpClient管道的DelegatingHandler必须注册为Transient生命周期 services.AddTransient<BearerTokenDelegatingHandler>(); // 注册命名HttpClient时,把自定义Handler加入请求管道 services.AddHttpClient("YourBusinessApiClient") .AddHttpMessageHandler<BearerTokenDelegatingHandler>();
方案优势
- 完全原生实现,无额外依赖
- 业务层使用时完全无感知,和普通用IHttpClientFactory创建Client的写法完全一致,不需要写任何重复的令牌处理代码
- 每次发请求都会自动检查令牌有效性,自动处理过期刷新逻辑
- 完美适配HttpClient池化机制,不会出现实例复用导致的头污染、令牌过期问题
方案2:类型化客户端封装(适配客户端获取环节的定制需求)
如果你需要在获取客户端实例的环节就完成相关配置,可以使用官方提供的类型化客户端(Typed Client)能力做一层封装。注意IHttpClientFactory.CreateClient本身是同步方法,不要在同步流程里阻塞等待异步逻辑,把令牌注入逻辑放到类型化客户端的异步请求方法里即可。
简单实现示例
public class BusinessApiClient { private readonly HttpClient _httpClient; private readonly ITokenCache _tokenCache; private readonly IOAuthTokenClient _oAuthTokenClient; // IHttpClientFactory会自动按照配置注入对应的HttpClient实例 public BusinessApiClient( HttpClient httpClient, ITokenCache tokenCache, IOAuthTokenClient oAuthTokenClient) { _httpClient = httpClient; _tokenCache = tokenCache; _oAuthTokenClient = oAuthTokenClient; } // 封装统一的请求发送方法,内部自动处理令牌注入 public async Task<HttpResponseMessage> SendAsync( HttpMethod method, string requestUrl, HttpContent? content = null, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(method, requestUrl) { Content = content }; // 令牌获取逻辑和DelegatingHandler里的逻辑完全一致 var accessToken = await _tokenCache.GetValidAccessTokenAsync(cancellationToken); if (string.IsNullOrWhiteSpace(accessToken)) { var tokenResponse = await _oAuthTokenClient.GetTokenAsync(cancellationToken); accessToken = tokenResponse.AccessToken; await _tokenCache.SetTokenAsync(accessToken, tokenResponse.ExpiresIn, cancellationToken); } request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); return await _httpClient.SendAsync(request, cancellationToken); } }
注册代码非常简单:
// 直接注册类型化客户端即可,内部自动走IHttpClientFactory的池化逻辑 services.AddHttpClient<BusinessApiClient>();
业务使用时直接注入BusinessApiClient调用对应方法即可,不需要手动创建HttpClient。
避坑提醒
- 不要在
AddHttpClient的同步配置委托(Action<IServiceProvider, HttpClient>)里用.Result、.Wait()这类方式阻塞等待异步令牌获取,不仅会造成线程池浪费,还很容易引发死锁;另外HttpClient实例会被池化长期复用,创建时写入的令牌过期后不会自动更新,会导致所有请求鉴权失败。 - 注册DelegatingHandler时不要用Scoped、Singleton生命周期,必须用Transient,否则会出现生命周期不匹配的问题。
- 不要把动态变化的令牌写入
HttpClient.DefaultRequestHeaders,这个属性是HttpClient实例级别的,池化复用时会被所有请求共享,很容易出现令牌串用、过期的问题,令牌应该加到每次发送的HttpRequestMessage的Headers上。
内容的提问来源于stack exchange,提问作者wertzui
相关产品推荐
相关产品推荐

