ASP.NET Core 2.1 中的 HttpClientFactory (Part 2) 定义命名化和类型化的客户端

Stella981
• 阅读 443

原文:https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore 发表于:2018年1月

      上一篇文章《HttpClientFactory简介》我解释了创建该功能的原因。我们知道了它可以解决的问题,然后例举了一个非常基本的示例展示了如何在WebAPI应用程序中使用它。在这篇文章中,我想深入探讨另外两种可以使用它的方法:命名化客户端(named clients)和类型化客户端(typed clients)。

命名化客户端(Name Clients)

      在第一篇文章中,我演示了如何使用HttpClientFactory来获取基本的HttpClient实例。当您只需要从单一位置发出快速请求时,这很好。但通常,您可能希望从代码中的多个位置向同一服务发出多个请求。
      通过命名化客户端的概念,HttpClientFactory使这一点变得更容易。使用命名化客户端,您可以创建一个注册,其中包含在创建HttpClient时的一些特定配置。您可以注册多个命名化客户端,每个客户端都可以预先配置不同的设置。
      为了让这个概念更具体一些,让我们看一个例子。在我的Startup.ConfigureServices方法中,使用AddHttpClient的不同重载方法,该方法接受两个附加参数。把一个名称和一个Action委托“告诉”HttpClient。ConfigureServices代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("GitHubClient", client =>
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
    });

    services.AddMvc();
}

      第一个字符串参数是用于此客户端注册的名称。Action 委托允许我们在为我们构造HttpClient时配置它们。这非常方便,因为我们可以预先定义一个基地址和一些已知的请求头。当我们请求命名化客户端时,会为我们创建一个新客户端,并且每次都会应用此配置。
      使用的时候,CreateClient根据名称来请求一个客户端:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient("GitHubClient");
        var result = await client.GetStringAsync("/");

        return Ok(result);
    }
}

      在这个例子中,我们创建的HttpClient实例已经有基本地址集(base address set),所以我们的GetStringAsync方法传入对应的URI即可。

      这种命名化的方式使我们能够控制应用于HttpClient的配置。我不是“魔力字符串”的忠实粉丝,所以如果我使用命名客户端,我可能会有一个静态类,其中包含客户端名称的字符串常量。像这样:

public static class NamedHttpClients
{
    public const string GitHubClient = "GitHubClient";
}

      注册(或请求)客户端时,我们可以使用静态类值,而不是“魔力字符串”:

services.AddHttpClient(NamedHttpClients.GitHubClient, client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
});

      这非常好,但我们可以更进一步,来看看如何使用自定义的类型化客户端。

类型化客户端(Typed Clients)

      类型化客户端允许我们定义一个通过构造函数注入HttpClient的自定义类。这样我们可以使用IHttpClientBuilder的扩展方法链接DI系统,或者使用泛型AddHttpClient方法来接收自定义类型。一旦我们有了自定义类,我们就可以直接公开HttpClient,也可以将Http calls封装在特定方法中,从而更好地定义外部服务的使用。这种方法也意味着我们不再需要“魔术字符串”,并且看起来更加合理。
      让我们看一个自定义类型化客户端的基本例子:

public class MyGitHubClient
{
    public MyGitHubClient(HttpClient client)
    {
        Client = client;
    }

    public HttpClient Client { get; }
}

      这个类需要在构造函数中接受作为参数的HttpClient。现在,我们已经为HttpClient的实例设置了一个公共属性。
      然后,我们需要在ConfigureServices中注册:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<MyGitHubClient>(client =>
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
    });

    services.AddMvc();
}

      我们将MyGitHubClient作为泛型参数传递给AddHttpClient。它在DI系统中被注册为transient scope。由于我们的自定义类接受HttpClient,因此相关联的“工厂”会创建一个适当配置的HttpClient实例,并注入它。现在可以更新控制器以接受我们的类型化客户端而不是IHttpClientFactory:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly MyGitHubClient _gitHubClient;

    public ValuesController(MyGitHubClient gitGitHubClient)
    {
        _gitHubClient = gitGitHubClient;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var result = await _gitHubClient.Client.GetStringAsync("/");
        return Ok(result);
    }
}

      由于我们自定义的类型化客户端通过属性公开了HttpClient,因此我们可以直接使用它进行HTTP调用。

封装HttpClient(Encapsulating the HttpClient)

      最后一个例子是我们想要完全封装HttpClient的情况。当我们想要定义处理对端点的特定调用的方法时,最有可能使用此方法。此时,我们还可以在每个方法中封装响应和反序列化的验证,以便在单一位置处理它。

public interface IMyGitHubClient
{
    Task<int> GetRootDataLength();
}

public class MyGitHubClient : IMyGitHubClient
{
    private readonly HttpClient _client;

    public MyGitHubClient(HttpClient client)
    {
        _client = client;
    }

    public async Task<int> GetRootDataLength()
    {
        var data = await _client.GetStringAsync("/");
        return data.Length;
    }
}

      这种情况下,我们通过private readonly字段存储了在构造中注入的HttpClient。与直接通过此类(class)获得HttpClient不同,我们提供了一个GetRootDataLength方法来执行Http调用并返回请求长度。一个简单的例子,但你应该已经明白了!
      我们现在可以更新控制器以接受和使用接口,如下所示:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IMyGitHubClient _gitHubClient;

    public ValuesController(IMyGitHubClient gitHubClient)
    {
        _gitHubClient = gitHubClient;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var result = await _gitHubClient.GetRootDataLength();
        return Ok(result);
    }
}

      我们现在可以调用接口上定义的GetRootDataLength方法,而无需直接与HttpClient交互。这对测试非常有用,现在可以在我们想要测试这个控制器时轻松模拟IMyGitHubClient。过去测试HttpClient有点痛苦,按照我通常习惯的方式会有更多代码。
      在DI容器中注册,ConfigureServices变为:

services.AddHttpClient<IMyGitHubClient, MyGitHubClient>(client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting");
});

      AddHttpClient有一个接受两个泛型参数的签名,对应DI中的签名。

总结

      在这篇文章中,我们探讨了HttpClientFactory一些更高级的方法,它允许我们使用特定的命名配置创建不同的HttpClient实例。然后我们讨论了使用类型化客户端,通过扩展实现了我们自己的类,它接受HttpClient实例。我们可以直接公开HttpClient,也可以将调用封装到此类中来访问远程端点。
      下一篇文章,我们将讨论使用DelegatingHandlers来实现“传出请求中间件”( outgoing request middleware)的另一种模式。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这