如何在 Typed HttpClient 示例中实现 IHttpFactoryClient?

How to implement IHttpFactoryClient in examples of Typed HttpClient?

在我见过的每个示例中,包括 Microsoft 的 here and here,作者都解释了 IHttpClientFactory 相对于 HttpClient 所做的改进,并举例说明了如何简单地使用它开箱即用或命名形式。但是他们似乎都提到使用类型化表单确实是最好的,因为它的结构、可用性等等。原因对我们的用例很有意义。

尽管像上面提供的链接一样,在创建类型化 HttpClient(或作为客户端的服务)时,没有一行代码实例化、注入或使用 IHttpClientFactory ).您创建类型化客户端:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {

然后在某些模型或控制器中使用它:

public TypedClientModel(GitHubService gitHubService)
{
    _gitHubService = gitHubService;
}

public async Task OnGet()
{
    try
    {
        LatestIssues = await _gitHubService.GetAspNetDocsIssues();
    }

我非常困惑。我的团队最初遇到了障碍,试图模拟(使用最小起订量)类型化的客户端进行单元测试,而我们在获得许多重要资源后得出的结论是,使用 IHttpClientFactory 模拟变得更加容易。但是我还没有找到一个明确将 IHttpClientFactory 与类型化客户端一起使用的示例。

该框架将使用 ITypedHttpClientFactory 创建 HttpClient 以注入类型化客户端。当类型化的客户端配置如下时,这是在幕后发生的:

services.AddHttpClient<ICatalogService, CatalogService>()

如果我们查看 AddHttpClient,我们可以看到它将尝试创建一个名为 ITypedHttpClientFactory

的临时版本 IHttpClientFactory
services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>), typeof(DefaultTypedHttpClientFactory<>)));

类型化客户端也允许抽象客户端

public class GitHubService :IGitHubService { // <-- NOTE THE INTERFACE 
    HttpClient client

    public GitHubService(HttpClient client) {
        this.client = client;
    }

    //...
    

使用 AddHttpClient

注册接口及其实现的地方
services.AddHttpClient<IGitHubService, GitHubService>();

并相应地使用

//...

private readonly IGitHubService gitHubService;

public TypedClientModel(IGitHubService gitHubService) {
    this.gitHubService = gitHubService;
}

public async Task OnGet() {
    try {
        LatestIssues = await gitHubService.GetAspNetDocsIssues();
    }

//...

这里的优点是您可以从第 3 方依赖项(框架问题)中分离出来,因为您是控制类型化客户端及其抽象的人。

这将允许在隔离测试时更轻松地模拟类型化客户端抽象。

IHttpClientFactory 你有三个选择:

IHttpClientFactory

用法

public class SampleController
{
    private readonly IHttpClientFactory _clientFactory;
    public SampleController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }
}

嘲讽

//Arrange
var mockClientFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(expectedResponseMessage);

var client = new HttpClient(mockMessageHandler.Object);
mockClientFactory
    .Setup(_ => _.CreateClient(It.IsAny<string>()))
    .Returns(client);

指定客户

用法

public class SampleController
{
    private readonly HttpClient _client;
    public SampleController(IHttpClientFactory clientFactory)
    {
        _client = clientFactory.CreateClient("SampleProxy");
    }
}

嘲讽

作为 alternative 我们可以通过使用自定义 HttpMessageHandler

来避免使用 Moq.Protected
public class FakeMessageHandler: HttpMessageHandler
{
    public virtual HttpResponseMessage Send(HttpRequestMessage request)
    {
        throw new NotImplementedException();
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(Send(request));
    }
}
//Arrange
var mockClientFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<FakeMessageHandler> { CallBase = true };
mockMessageHandler
    .Setup(handler => handler.Send(It.IsAny<HttpRequestMessage>()))
    .Returns(expectedResponseMessage);

var client = new HttpClient(mockMessageHandler.Object);
mockClientFactory
    .Setup(_ => _.CreateClient("SampleProxy"))
    .Returns(client);

类型化客户端

用法

public class SampleController
{
    private readonly ISampleClient _client;
    public SampleController(ISampleClient client)
    {
        _client = client;
    }
}

嘲讽

//Arrange
var clientMock = new Mock<ISampleClient>();
clientMock
    .Setup(client => client.GetXYZ(It.IsAny<SampleRequest>()))
    .ReturnsAsync(expectedSampleResponse);

var SUT = new SampleController(clientMock.Object);