WPF应用中使用IHttpClientFactory创建HttpClient时如何访问ProgressMessageHandler
问题描述
我原本有一段能正常读取下载进度的HttpClient代码:
var handler = new HttpClientHandler() { AllowAutoRedirect = true }; var ph = new ProgressMessageHandler(handler); ph.HttpReceiveProgress += (_, args) => { // 处理下载进度的逻辑 }; var client = new HttpClient(ph); client.BaseAddress = new Uri("https://localhost:7145/"); HttpRequestMessage requestMessage = new(HttpMethod.Get, "DownloadFile"); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenConfig.AccessToken); var response = await client.SendAsync(requestMessage);
现在我想移除类内的HttpClient初始化逻辑,改用微软的IHttpClientFactory注入,已经在启动类中完成了如下配置:
services.AddTransient<ProgressMessageHandler>(); services.AddHttpClient("api", opt => { opt.BaseAddress = new Uri(configuration.GetValue<string>("HttpClientConfig:BaseUrl")); }) .AddHttpMessageHandler<ProgressMessageHandler>() .ConfigurePrimaryHttpMessageHandler(() => { return new HttpClientHandler() { AllowAutoRedirect = false, UseDefaultCredentials = true }; });
现在可以通过_httpClientFactory.CreateClient("api")获取HttpClient,但不知道该如何及在何处访问HttpReceiveProgress事件。
解决方案
方法一:自定义可订阅进度的Handler
通过创建ProgressMessageHandler的子类,暴露进度事件供外部订阅,是最稳定可靠的方案:
- 定义自定义Handler类:
public class SubscribableProgressMessageHandler : ProgressMessageHandler { // 暴露自定义的进度事件 public new event EventHandler<HttpProgressEventArgs>? HttpReceiveProgress; public SubscribableProgressMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler) { // 转发基类事件到自定义事件 base.HttpReceiveProgress += (sender, args) => HttpReceiveProgress?.Invoke(sender, args); } }
- 修改DI配置,替换原Handler为自定义类:
services.AddTransient<SubscribableProgressMessageHandler>(); services.AddHttpClient("api", opt => { opt.BaseAddress = new Uri(configuration.GetValue<string>("HttpClientConfig:BaseUrl")); }) .AddHttpMessageHandler<SubscribableProgressMessageHandler>() .ConfigurePrimaryHttpMessageHandler(() => { return new HttpClientHandler() { AllowAutoRedirect = false, UseDefaultCredentials = true }; });
- 使用时获取Handler并订阅事件:
var client = _httpClientFactory.CreateClient("api"); // 从HttpClient的Handler链中获取自定义进度Handler if (client.GetTypedClientHandler<SubscribableProgressMessageHandler>() is { } progressHandler) { progressHandler.HttpReceiveProgress += (_, args) => { // 处理下载进度逻辑,例如计算百分比 var progressPercent = args.BytesTransferred * 100 / args.TotalBytes; Console.WriteLine($"下载进度:{progressPercent}%"); }; } // 发起下载请求 var requestMessage = new HttpRequestMessage(HttpMethod.Get, "DownloadFile"); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenConfig.AccessToken); var response = await client.SendAsync(requestMessage);
方法二:直接从Handler链中获取原生ProgressMessageHandler
如果不想自定义Handler,可以通过遍历Handler链找到目标实例,但该方案依赖HttpClient内部实现,版本更新可能失效:
var client = _httpClientFactory.CreateClient("api"); var currentHandler = client.DefaultRequestHeaders.GetType().Assembly .GetType("System.Net.Http.HttpClient+HttpHandlerFilter")? .GetField("_innerHandler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? .GetValue(client.DefaultRequestHeaders) as HttpMessageHandler; // 遍历Handler链查找ProgressMessageHandler while (currentHandler != null) { if (currentHandler is ProgressMessageHandler progressHandler) { progressHandler.HttpReceiveProgress += (_, args) => { // 处理下载进度逻辑 Console.WriteLine($"已下载:{args.BytesTransferred} / {args.TotalBytes}"); }; break; } currentHandler = (currentHandler as DelegatingHandler)?.InnerHandler; } // 发起下载请求 var requestMessage = new HttpRequestMessage(HttpMethod.Get, "DownloadFile"); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenConfig.AccessToken); var response = await client.SendAsync(requestMessage);
注意:方法二使用反射,存在版本兼容性风险,优先推荐方法一。
内容的提问来源于stack exchange,提问作者niks
相关产品推荐
相关产品推荐

