1.HttpClient类使用存在的问题

HttpClient类的使用所存在的问题,百度搜索的文章一大堆,好多都是单纯文字描述,让人感觉不太好理解,为了更好理解HttpClient使用存在的问题,下面让我们通过代码跟示例来描述。

  1. using(var client = new HttpClient())

传统关闭连接方法如上述代码所示,但当使用using语句释放HttpClient对象的时候,套接字(socket)也不会立即释放,下面我们通过请求aspnetmonsters站点的示例来验证下:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Console.WriteLine("Starting connections");
  6. var g = GetAsync();
  7. g.Wait();
  8. Console.WriteLine("Connections done");
  9. Console.ReadKey();
  10. }
  11. static async Task GetAsync()
  12. {
  13. for (int i = ; i < ; i++)
  14. {
  15. using (var client = new HttpClient())
  16. {
  17. var result = await client.GetAsync("http://aspnetmonsters.com/");
  18. Console.WriteLine(result.StatusCode);
  19. }
  20. }
  21. }
  22. }

输出结果:

控制台打印出五条请求站点返回状态的信息,下面我们通过netstat工具打印出五个请求连接套接字状态:

应用程序已经运行结束了(结束连接),但是打印结果显示连接状态仍然是TIME_WAIT,也就是说在此状态期间仍然在观察是否有数据包进入连接(如果连接等待中有任何数据包仍然会通过),因为它们可能在某个地方被网络延迟,这是我从tcpstate窃取的TCP / IP状态图。

Windows将在此状态下保持连接240秒(由其设置[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])。Windows可以快速打开新套接字的速度有限,因此如果您耗尽连接池,那么您可能会看到如下错误:

而怎么做才可以减少套接字的浪费呢?我们在上述代码中把每次循环中创建的HttpClient对象拉到Main外定义为一个共享的静态实例:

  1. class Program
  2. {
  3. private static HttpClient client = new HttpClient();
  4. static void Main(string[] args)
  5. {
  6. Console.WriteLine("Starting connections");
  7. var g = GetAsync();
  8. g.Wait();
  9. Console.WriteLine("Connections done");
  10. Console.ReadKey();
  11. }
  12. static async Task GetAsync()
  13. {
  14. for (int i = ; i < ; i++)
  15. {
  16. var result = await client.GetAsync("http://aspnetmonsters.com/");
  17. Console.WriteLine(result.StatusCode);
  18. }
  19. }
  20. }

应用程序运动完毕之后,我们再通过netstat工具打印出五个请求连接套接字状态,这时候会看到信息如下:

通过共享一个实例,减少了套接字的浪费,实际上由于套接字重用而传输快一点。
总结:
●在创建HttpClient实例的时候,最好是静态(static )实例。
●不要用using包装HttpClient对象。
在.NET Core 2.1版本之后引入的 HttpClientFactory解决了HttpClient的所有痛点。有了 HttpClientFactory,我们不需要关心如何创建HttpClient,又如何释放它。通过它可以创建具有特定业务的HttpClient,而且可以很友好的和 DI 容器结合使用,更为灵活。下面以 ASP.NET Core为例介绍HttpClientFactory的四种使用方式。

2.HttpClientFactory 的多种使用方式

可以通过多种使用方式在应用程序中使用HttpClientFactory。

2.1使用基本用法

在Startup.ConfigureServices方法中,通过在IServiceCollection上调用AddHttpClient扩展方法可以注册IHttpClientFactory服务。
services.AddHttpClient();
注册服务后,我们新建BasicUsageModel类使用IHttpClientFactory创建HttpClient实例:

  1. public class BasicUsageModel
  2. {
  3. private readonly IHttpClientFactory _clientFactory;
  4. public IEnumerable<GitHubBranch> Branches { get; private set; }
  5. public bool GetBranchesError { get; private set; }
  6. public BasicUsageModel(IHttpClientFactory clientFactory)
  7. {
  8. _clientFactory = clientFactory;
  9. }
  10. public async Task OnGet()
  11. {
  12. var request = new HttpRequestMessage(HttpMethod.Get,
  13. "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
  14. request.Headers.Add("Accept", "application/vnd.github.v3+json");
  15. request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
  16. var client = _clientFactory.CreateClient();
  17. var response = await client.SendAsync(request);
  18. if (response.IsSuccessStatusCode)
  19. {
  20. Branches = await response.Content
  21. .ReadAsAsync<IEnumerable<GitHubBranch>>();
  22. }
  23. else
  24. {
  25. GetBranchesError = true;
  26. Branches = Array.Empty<GitHubBranch>();
  27. }
  28. }
  29. }
  30. public class GitHubBranch
  31. {
  32. public string name { get; set; }
  33. }

以这种方式直接在使用IHttpClientFactory的类中调用CreateClient方法创建HttpClient实例。然后在Controller中调用BasicUsageModel类:

  1. public class HomeController : Controller
  2. {
  3. private readonly IHttpClientFactory _clientFactory;
  4. public HomeController(IHttpClientFactory clientFactory)
  5. {
  6. _clientFactory = clientFactory;
  7. }
  8. public IActionResult Index()
  9. {
  10. BasicUsageModel model = new BasicUsageModel(_clientFactory);
  11. var task = model.OnGet();
  12. task.Wait();
  13. List<GitHubBranch> list = model.Branches.ToList();
  14. return View(list);
  15. }
  16. }

2.2使用命名客户端

如果应用程序需要有许多不同的HttpClient用法(每种用法的服务配置都不同),可以视情况使用命名客户端。可以在HttpClient中注册时指定命名Startup.ConfigureServices的配置。

  1. services.AddHttpClient("github", c =>
  2. {
  3. c.BaseAddress = new Uri("https://api.github.com/");
  4. // Github API versioning
  5. c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
  6. // Github requires a user-agent
  7. c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
  8. });

上面的代码调用AddHttpClient,同时提供名称“github”。此客户端应用了一些默认配置,也就是需要基址和两个标头来使用GitHub API。每次调用CreateClient时,都会创建HttpClient 的新实例,并调用配置操作。要使用命名客户端,可将字符串参数传递到CreateClient。指定要创建的客户端的名称:

  1. public class NamedClientModel : PageModel
  2. {
  3. private readonly IHttpClientFactory _clientFactory;
  4. public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
  5. public bool GetPullRequestsError { get; private set; }
  6. public bool HasPullRequests => PullRequests.Any();
  7. public NamedClientModel(IHttpClientFactory clientFactory)
  8. {
  9. _clientFactory = clientFactory;
  10. }
  11. public async Task OnGet()
  12. {
  13. var request = new HttpRequestMessage(HttpMethod.Get,
  14. "repos/aspnet/AspNetCore.Docs/pulls");
  15. var client = _clientFactory.CreateClient("github");
  16. var response = await client.SendAsync(request);
  17. if (response.IsSuccessStatusCode)
  18. {
  19. PullRequests = await response.Content
  20. .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
  21. }
  22. else
  23. {
  24. GetPullRequestsError = true;
  25. PullRequests = Array.Empty<GitHubPullRequest>();
  26. }
  27. }
  28. }
  29. public class GitHubPullRequest
  30. {
  31. public string url { get; set; }
  32. public int? id { get; set; }
  33. public string node_id { get; set; }
  34. }

在上述代码中,请求不需要指定主机名。可以仅传递路径,因为采用了为客户端配置的基址。在Controller中调用方法如上个示例。

2.3使用类型化客户端

什么是“类型化客户端”?它只是DefaultHttpClientFactory注入时配置的HttpClient。
下图显示了如何将类型化客户端与HttpClientFactory结合使用:

类型化客户端提供与命名客户端一样的功能,不需要将字符串用作密钥。它们提供单个地址来配置特定HttpClient并与其进行交互。例如,单个类型化客户端可能用于单个后端终结点,并封装此终结点的所有处理逻辑。另一个优势是它们使用 DI 且可以被注入到应用中需要的位置。
类型化客户端在构造函数中接收HttpClient参数:

  1. public class GitHubService
  2. {
  3. public HttpClient Client { get; }
  4. public GitHubService(HttpClient client)
  5. {
  6. client.BaseAddress = new Uri("https://api.github.com/");
  7. // GitHub API versioning
  8. client.DefaultRequestHeaders.Add("Accept",
  9. "application/vnd.github.v3+json");
  10. // GitHub requires a user-agent
  11. client.DefaultRequestHeaders.Add("User-Agent",
  12. "HttpClientFactory-Sample");
  13. Client = client;
  14. }
  15. public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
  16. {
  17. var response = await Client.GetAsync(
  18. "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
  19. response.EnsureSuccessStatusCode();
  20. var result = await response.Content
  21. .ReadAsAsync<IEnumerable<GitHubIssue>>();
  22. return result;
  23. }
  24. }
  25. public class GitHubIssue
  26. {
  27. public string url { get; set; }
  28. public int? id { get; set; }
  29. public string node_id { get; set; }
  30. }

在上述代码中,配置转移到了类型化客户端中。HttpClient对象公开为公共属性。可以定义公开HttpClient功能的特定于API的方法。GetAspNetDocsIssues方法从GitHub存储库封装查询和分析最新待解决问题所需的代码。
要注册类型化客户端,可在Startup.ConfigureServices中使用通用的AddHttpClient扩展方法,指定类型化客户端类:

  1. services.AddHttpClient<GitHubService>();

使用DI将类型客户端注册为暂时客户端。可以直接插入或使用类型化客户端:

  1. public class TypedClientModel : PageModel
  2. {
  3. private readonly GitHubService _gitHubService;
  4. public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
  5. public bool HasIssue => LatestIssues.Any();
  6. public bool GetIssuesError { get; private set; }
  7. public TypedClientModel(GitHubService gitHubService)
  8. {
  9. _gitHubService = gitHubService;
  10. }
  11. public async Task OnGet()
  12. {
  13. try
  14. {
  15. LatestIssues = await _gitHubService.GetAspNetDocsIssues();
  16. }
  17. catch (HttpRequestException)
  18. {
  19. GetIssuesError = true;
  20. LatestIssues = Array.Empty<GitHubIssue>();
  21. }
  22. }
  23. }

参考文献:
在ASP.NET Core中使用IHttpClientFactory发出HTTP请求
你正在以错误方式使用 HttpClient,这将导致软件受损

(6)ASP.NET Core 中使用IHttpClientFactory发出HTTP请求的更多相关文章

  1. 如何在ASP.NET Core 中使用IHttpClientFactory

    利用IHttpClientFactory可以无缝创建HttpClient实例,避免手动管理它们的生命周期. 当使用ASP.Net Core开发应用程序时,可能经常需要通过HttpClient调用Web ...

  2. 如何在ASP.NET Core中编写高效的控制器

    ​通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可 ...

  3. ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态

    原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...

  4. [译]在Asp.Net Core 中使用外部登陆(google、微博...)

    原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> 摘要:本文主要介绍了使用外部登陆提供程序登陆的流程,以及身份 ...

  5. ASP.NET Core中使用GraphQL - 最终章 Data Loader

    ASP.NET Core中使用GraphQL - 目录 ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间 ...

  6. 如何在ASP.NET Core中使用Azure Service Bus Queue

    原文:USING AZURE SERVICE BUS QUEUES WITH ASP.NET CORE SERVICES 作者:damienbod 译文:如何在ASP.NET Core中使用Azure ...

  7. ASP.NET Core中如何针对一个使用HttpClient对象的类编写单元测试

    原文地址: How to unit test a class that consumes an HttpClient with IHttpClientFactory in ASP.NET Core? ...

  8. [译]ASP.NET Core中使用MediatR实现命令和中介者模式

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何 ...

  9. ASP.NET Core 中读取 Request.Body 的正确姿势

    ASP.NET Core 中的 Request.Body 虽然是一个 Stream ,但它是一个与众不同的 Stream —— 不允许 Request.Body.Position=0 ,这就意味着只能 ...

随机推荐

  1. 【转】 Pro Android学习笔记(七七):服务(2):Local Service

    目录(?)[-] Local service代码 调用Local ServiceLocal Service client代码 AndroidManifestxml定义Serviceacitivty的l ...

  2. requests 的使用

    1.1.实例引入 # 引入Requests库 import requests   # 发起GET请求 response = requests.get('https://www.baidu.com/') ...

  3. openstackM版本安装

    部署期间常见问题:http://www.cnblogs.com/bfmq/p/6001233.html,问题跟对架构的理解永远比部署重要!你玩技术是绝对是要基于理论的 一.基本情况:物理设备:4台惠普 ...

  4. Python函数(十二)-迭代器

    字符串,列表,元组,字典,集合,生成器这些能通过for循环来遍历的数据类型都是可迭代对象 可通过isinstance判断是不是可迭代对象 >>> from collections i ...

  5. android activity生命周期的一张经典图片

    图片来自http://blog.csdn.net/android_tutor/article/details/5772285 onpause只有弹出的窗体是Activity的时候才会触发,并非是通过焦 ...

  6. Spring开发环境搭建

    ----------------siwuxie095 Spring 是运行在 Java 环境下的开发框架,因此在开发前需要 准备以下相关软件: JDK 7 或者以上版本 Eclipse 4 或以上版本 ...

  7. Angular14 Visual Studio Code作为Angular开发工具常用插件安装、json-server安装与使用、angular/cli安装失败问题、emmet安装

    前提准备: 搭建好Angular开发环境 1 安装Visual Studio Code 教程简单,不会的去问度娘 2 安装Chrome浏览器 教程简单,不会的趣闻度娘 3 Visual Studio ...

  8. 34、NCBI的子库名称

    转载:http://fhqdddddd.blog.163.com/blog/static/1869915420107188527933/ 参考资料: http://www.ncbi.nlm.nih.g ...

  9. hdu1099

    #include<iostream> using namespace std; __int64 gcd(__int64 a,__int64 b) { return b?gcd(b,a%b) ...

  10. Citrix 虚拟化 最小化后无法最大化

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Citrix\wfshell\TWI\创建DWORD类型键值:SeamlessFlags    ...