• 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory


    ​到目前为止,我们一直直接使用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/

  • 相关阅读:
    【转载】HBase 数据库检索性能优化策略
    C语言结构
    c语言之sizeof总结
    C语言之Static
    转帖子:测试专家10年软件测试工作总结
    Word恢复文本转换器-修复损坏的WORD文件
    蓝桥杯 格子刷油漆
    小结博弈
    牛客寒假算法基础集训营6 E 海啸
    牛客寒假算法基础集训营4 F Applese 的大奖
  • 原文地址:https://www.cnblogs.com/hhhnicvscs/p/14592167.html
Copyright © 2020-2023  润新知