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

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

      上一篇文章《HttpClientFactory简介》我解释了建立该功能的缘由。咱们知道了它能够解决的问题,而后例举了一个很是基本的示例展现了如何在WebAPI应用程序中使用它。在这篇文章中,我想深刻探讨另外两种可使用它的方法:命名化客户端(named clients)和类型化客户端(typed clients)。github

命名化客户端(Name Clients)

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

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>委托容许咱们在为咱们构造HttpClient时配置它们。这很是方便,由于咱们能够预先定义一个基地址和一些已知的请求头。当咱们请求命名化客户端时,会为咱们建立一个新客户端,而且每次都会应用此配置。
      使用的时候,CreateClient根据名称来请求一个客户端:api

[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便可。app

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

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封装在特定方法中,从而更好地定义外部服务的使用。这种方法也意味着咱们再也不须要“魔术字符串”,而且看起来更加合理。
      让咱们看一个自定义类型化客户端的基本例子:ui

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

    public HttpClient Client { get; }
}

      这个类须要在构造函数中接受做为参数的HttpClient。如今,咱们已经为HttpClient的实例设置了一个公共属性。
      而后,咱们须要在ConfigureServices中注册:spa

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)的另外一种模式。

相关文章
相关标签/搜索