欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-09 19:49:12
原文:https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore 发表于:2018年1月 原文:https://www.stevejgordon.co.uk/httpclientfactory-nam ......

原文: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>委托允许我们在为我们构造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)的另一种模式。