​到目前为止,我们一直直接使用HttpClient。在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置。这会导致了重复代码。在这篇文章中,我们将学习如何通过使用HttpClientFactory来改善它。当然,这并不是使用HttpClientFactory的唯一优势。我们将学习HttpClientFactory如何防止HttpClient可能导致的其他问题。此外,我们将展示如何使用HttpClientFactory创建命名和类型化客户端。

HttpClient问题

HttpClient类实现了IDisposable接口。看到这一点,尝试在using指令中使用我们的HttpClient实例,从而在它超出作用域时释放。但是,这并不是一个好的做法。如果我们丢弃了HttpClient,也将丢弃底层的HttpClientHandler。现在,这意味着对于每个新请求,必须创建一个新的HttpClient实例,从而也创建一个处理程序。当然,这就是问题所在。重新打开连接可能会导致性能变慢,因为在使用HttpClient时,这些连接和HttpClientHandler非常昂贵。

此外,还有另一个问题。通过创建太多的连接,可能会面临套接字耗尽,因为过快地使用了太多的套接字,而且没有套接字来创建新的连接。

因此,考虑到所有这些,我们不应该丢弃HttpClient,而是在整个请求中共享它。这就是我们在以前的文章中对静态HttpClient实例所做的事情。这也允许重用底层连接。

但是,我们必须注意,使用静态实例并不是最终的解决方案。当重用实例时,我们也重用连接,直到套接字关闭。为了帮助我们解决这些问题,我们可以使用HttpClientFactory来创建HttpClient实例。

HttpClientFactory如何帮助我们解决上述问题?

HttpClientFactory不仅可以创建和管理新的HttpClient实例,而且还可以与底层处理程序一起工作。当创建新的HttpClient实例时,它不会重新创建消息处理器,而是从池中获取一个。然后,它使用该消息处理程序将请求发送到API。处理程序的默认生存期设置为两分钟,在这段时间内,对新HttpClient的任何请求都可以重用现有的消息处理程序和连接。这意味着我们不必为每个请求创建一个新的消息处理程序,也不必打开一个新的连接,从而防止套接字耗尽问题。

除了解决这些问题,使用HttpClientHandler,我们还可以集中HttpClient的配置。如果你阅读本系列的前几篇文章,会发现我们必须在每个服务类中重复相同的配置。有了HttpClientHandler,我们可以改善这个问题。让我们看看如何使用HttpClientFactory。

添加HttpClientFactory

为了能够在我们的应用程序中使用HttpClientFactory,必须安装 Microsoft.Extensions.Http。

Install-Package Microsoft.Extensions.Http -Version 5.0.0

然后,我们必须使用Program 类中通过AddHttpClient方法将IHttpClientFactory和其他服务添加到服务集合中:

private static void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient(); //services.AddScoped<IHttpClientServiceImplementation, HttpClientCrudService>();
//services.AddScoped<IHttpClientServiceImplementation, HttpClientPatchService>();
//services.AddScoped<IHttpClientServiceImplementation, HttpClientStreamService>();
//services.AddScoped<IHttpClientServiceImplementation, HttpClientCancellationService>();
}

我们很快就会用额外的配置来扩展这个方法。现在,让我们创建一个新的服务类,就像我们在前面的文章中所做的那样:

public class HttpClientFactoryService : IHttpClientServiceImplementation
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _options; public HttpClientFactoryService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory; _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
} public async Task Execute()
{
throw new NotImplementedException();
}
}

为了能够在我们的HttpClientFactoryService类中使用HttpClientFactory,我们必须通过依赖注入来注入它。此外,我们还为JSON序列化配置选项。我们不想在这里添加取消逻辑,所以没有像上一篇文章中那样使用CancellationTokenSource。

现在,让我们添加一个新方法来从API中获取公司数据:

private async Task GetCompaniesWithHttpClientFactory()
{
var httpClient = _httpClientFactory.CreateClient(); using (var response = await httpClient.GetAsync("https://localhost:5001/api/companies", HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); var stream = await response.Content.ReadAsStreamAsync(); var companies = await JsonSerializer.DeserializeAsync<List<CompanyDto>>(stream, _options);
}
}

在这段代码中,我们唯一不熟悉的部分是使用HttpClientFactory中的CreateClient方法来使用默认配置创建一个新的HttpClient。本系列前面的文章中已经解释了其他所有内容。另外,由于没有提供自定义配置,我们必须在GetAsync方法中使用完整的URI。

在此之后,我们可以修改Execute方法:

public async Task Execute(){ await GetCompaniesWithHttpClientFactory();}

同样,让我们在Program类中注册这个服务:

private static void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient(); ...
services.AddScoped<IHttpClientServiceImplementation, HttpClientFactoryService>();
}

在新方法中放置断点并启动两个应用程序:

使用命名的HttpClient实例

在Program类中,我们使用AddHttpClient方法注册IHttpClientFactory,而不需要额外的配置。这意味着用CreateClient方法创建的每个HttpClient实例都将具有相同的配置。但通常,这是不够的,因为我们的客户端应用程序在与一个或多个api通信时经常需要不同的HttpClient实例。为了支持这一点,我们可以使用命名的HttpClient实例。

在之前的文章中,我们在每个服务中使用了相同的配置来设置基址、超时和清除默认请求头。现在,我们也可以这样做,但只有一个地方:

private static void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("CompaniesClient", config =>
{
config.BaseAddress = new Uri("https://localhost:5001/api/");
config.Timeout = new TimeSpan(0, 0, 30);
config.DefaultRequestHeaders.Clear();
}); ...
services.AddScoped<IHttpClientServiceImplementation, HttpClientFactoryService>();
}

通过这些修改,AddHttpClient方法将IHttpClientFactory添加到服务集合中,并配置一个已命名的HttpClient实例。我们为实例提供一个名称和一个默认配置。在此之后,可以在我们的新服务中修改方法:

private async Task GetCompaniesWithHttpClientFactory()
{
var httpClient = _httpClientFactory.CreateClient("CompaniesClient"); using (var response = await httpClient.GetAsync("companies", HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); var stream = await response.Content.ReadAsStreamAsync(); var companies = await JsonSerializer.DeserializeAsync<List<CompanyDto>>(stream, _options);
}
}

我们将name参数传递给CreateClient方法,而且不必在GetAsync方法中使用完整的URI。由于使用的是客户机的名称,因此应用与此名称对应的配置。

一旦我们启动这两个应用程序,将得到和之前一样的结果:

使用类型化HttpClient实例

使用类型化实例,我们可以实现与命名实例相同的功能,但在注册过程中不必使用字符串——可以使用类型。首先在客户端应用程序中创建一个新的Clients文件夹,并在该文件夹中创建一个新的CompaniesClient类:

public class CompaniesClient
{
public HttpClient Client { get; } public CompaniesClient(HttpClient client)
{
Client = client; Client.BaseAddress = new Uri("https://localhost:5001/api/");
Client.Timeout = new TimeSpan(0, 0, 30);
Client.DefaultRequestHeaders.Clear();
}
}

这是我们使用默认配置的类型化客户端类,我们可以通过在ConfigureServices方法中再次调用AddHttpClient来在程序类中注册它:

services.AddHttpClient<CompaniesClient>();

因此,我们使用的不是客户端的名称,而是客户端的类型。

现在,在我们的HttpClientFactoryService中,必须注入新的客户端:

private readonly IHttpClientFactory _httpClientFactory;
private readonly CompaniesClient _companiesClient;
private readonly JsonSerializerOptions _options; public HttpClientFactoryService(IHttpClientFactory httpClientFactory, CompaniesClient companiesClient)
{
_httpClientFactory = httpClientFactory;
_companiesClient = companiesClient; _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
}

然后,我们将创建一个新方法来使用类型化客户端:

private async Task GetCompaniesWithTypedClient()
{
using (var response = await _companiesClient.Client.GetAsync("companies", HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); var stream = await response.Content.ReadAsStreamAsync(); var companies = await JsonSerializer.DeserializeAsync<List<CompanyDto>>(stream, _options);
}
}

我们不是通过使用CreateClient方法来创建一个新的客户端实例。这一次,我们只使用注入类型的客户机及其client属性。最后,执行这个方法:

public async Task Execute()
{
//await GetCompaniesWithHttpClientFactory();
await GetCompaniesWithTypedClient();
}

现在让我们看看如何将相关的逻辑提取到CompaniesClient 类。

封装与类型化客户端相关的逻辑

因为我们已经有了类型化的客户端类,所以我们可以将服务中的所有相关逻辑提取到这个类中。为此,我们将修改CompaniesClient 类:

public class CompaniesClient
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _options; public CompaniesClient(HttpClient client)
{
_client = client; _client.BaseAddress = new Uri("https://localhost:5001/api/");
_client.Timeout = new TimeSpan(0, 0, 30);
_client.DefaultRequestHeaders.Clear(); _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
}
}

我们有一个私有的只读变量,将在该类中使用它来执行HttpClient的逻辑。此外,我们还添加了JsonSerializerOptions配置。现在,可以添加一个新方法:

public async Task<List<CompanyDto>> GetCompanies()
{
using (var response = await _client.GetAsync("companies", HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); var stream = await response.Content.ReadAsStreamAsync(); var companies = await JsonSerializer.DeserializeAsync<List<CompanyDto>>(stream, _options); return companies;
}
}

使用这个方法,我们从API中获取公司数据并返回结果。最后,可以修改服务类中的GetCompaniesWithTypedClient方法:

private async Task GetCompaniesWithTypedClient() => await _companiesClient.GetCompanies();

结论

综上所述,在本文中,我们了解到:

  • HttpClientFactory解决了哪些问题
  • 如何在我们的应用程序中使用HttpClientFactory
  • 使用命名和类型化实例的方法
  • 如何从服务中提取逻辑到客户端类

原文链接:https://code-maze.com/using-httpclientfactory-in-asp-net-core-applications/

在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory的更多相关文章

  1. 在ASP.NET Core中用HttpClient(一)——获取数据和内容

    在本文中,我们将学习如何在ASP.NET Core中集成和使用HttpClient.在学习不同HttpClient功能的同时使用Web API的资源.如何从Web API获取数据,以及如何直接使用Ht ...

  2. 在ASP.NET Core中用HttpClient(二)——发送POST, PUT和DELETE请求

    在上一篇文章中,我们已经学习了如何在ASP.NET Core中使用HttpClient从Web API获取数据.此外,我们还学习了如何使用GetAsync方法和HttpRequestMessage类发 ...

  3. 在ASP.NET Core中用HttpClient(三)——发送HTTP PATCH请求

    在前面的两篇文章中,我们讨论了很多关于使用HttpClient进行CRUD操作的基础知识.如果你已经读过它们,你就知道如何使用HttpClient从API中获取数据,并使用HttpClient发送PO ...

  4. 在ASP.NET Core中用HttpClient(四)——提高性能和优化内存

    到目前为止,我们一直在使用字符串创建请求体,并读取响应的内容.但是我们可以通过使用流提高性能和优化内存.因此,在本文中,我们将学习如何在请求和响应中使用HttpClient流. 什么是流 流是以文件. ...

  5. 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求

    ​用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...

  6. C# ASP.NET Core使用HttpClient的同步和异步请求

    引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...

  7. ASP.NET CORE系列【六】Entity Framework Core 之数据迁移

    原文:ASP.NET CORE系列[六]Entity Framework Core 之数据迁移 前言 最近打算用.NET Core写一份简单的后台系统,来练练手 然后又用到了Entity Framew ...

  8. ASP.NET Core 2.1 中的 HttpClientFactory (Part 1) HttpClientFactory介绍

    原文:https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore  发表于:2018年1月 ASP.NET ...

  9. ASP.NET MVC Model绑定(六)

    ASP.NET MVC Model绑定(六) 前言 前面的篇幅对于IValueProvider的使用做个基础的示例讲解,但是没并没有对 IValueProvider类型的实现做详细的介绍,然而MVC框 ...

随机推荐

  1. LeetCode Binary Search All In One

    LeetCode Binary Search All In One Binary Search 二分查找算法 https://leetcode-cn.com/problems/binary-searc ...

  2. PerformanceObserver API All In One

    PerformanceObserver API All In One 性能监控 https://developer.mozilla.org/en-US/docs/Web/API/Performance ...

  3. ES6 Class vs ES5 constructor function All In One

    ES6 Class vs ES5 constructor function All In One ES6 类 vs ES5 构造函数 https://developer.mozilla.org/en- ...

  4. empty Checker

    empty Checker "use strict"; /** * * @author xgqfrms * @license MIT * @copyright xgqfrms * ...

  5. macOS utils

    macOS utils dr.unarchiver https://dr-unarchiver.en.softonic.com/mac https://dr-unarchiver.en.softoni ...

  6. css-next & grid layout

    css-next & grid layout css3 demo https://alligator.io/ @media only screen and (max-width: 30em) ...

  7. qt 注册热键

    原文 将所需的库添加到您的qmake项目(.PRO文件) LIBS += \ -lUser32 2.在代码中包含所需的头文件. #include <windows.h> 在程序开始时注册热 ...

  8. RT-Thread学习笔记3-线程间通信 & 定时器

    目录 1. 事件集的使用 1.1 事件集控制块 1.2 事件集操作 2. 邮箱的使用 2.1 邮箱控制块 2.2 邮箱的操作 3. 消息队列 3.1 消息队列控制块 3.2 消息队列的操作 4. 软件 ...

  9. DeFi 热潮下,NGK将成为下一个财富密码

    区块链正在脱虚向实,处于大规模落地,赋能实体产业的前夜,而在这个关键的关口,一个万亿市场的蓝海正在缓缓生成,成为区块链落地的急先锋,这个先锋便是DeFi. DeFi,即Decentralized Fi ...

  10. OAuth:每次授权暗中保护你的那个“MAN”

    摘要:OAuth是一种授权协议,允许用户在不将账号口令泄露给第三方应用的前提下,使第三方应用可以获得用户在某个web服务上存放资源的访问权限. 背景 在传统模式下,用户的客户端在访问某个web服务提供 ...