如何在Typed HttpClient的示例中实现IHttpFactoryClient? 用法嘲笑用法嘲笑用法嘲笑

问题描述

在我看到的每个示例中,包括Microsoft的herehere,作者将解释IHttpClientFactoryHttpClient的改进,并举例说明可以直接使用它,也可以以命名形式使用它。但是随后所有人似乎都提到,使用Typed表单确实对它的结构,可用性等而言是最好的。原因对我们的用例来说很有意义。

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

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();
    }

我非常困惑。我的团队最初遇到了障碍,试图使用Moq对类型化的客户进行模拟以进行单元测试,而得出的结论是,在获得大量宝贵资源后,我们发现IHttpClientFactory使得模拟变得更容易了。但是我还没有找到一个示例,该示例明确将IHttpClientFactory与类型客户端一起使用。

解决方法

框架将使用ITypedHttpClientFactory创建HttpClient,以注入到类型化的客户端中。当像这样配置类型化的客户端时,这是在后台进行的:

services.AddHttpClient<ICatalogService,CatalogService>()

如果我们窥视AddHttpClient,我们会发现它会尝试创建名为IHttpClientFactory的{​​{1}}的临时版本

ITypedHttpClientFactory

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

services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>),typeof(DefaultTypedHttpClientFactory<>)));

使用AddHttpClient

在接口及其实现中注册的位置
public class GitHubService :IGitHubService { // <-- NOTE THE INTERFACE 
    HttpClient client

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

    //...
    

并相应地使用

services.AddHttpClient<IGitHubService,GitHubService>();

这样做的好处是,您可以脱离第三方的依赖关系(框架问题),因为您可以控制类型化的客户端及其抽象。

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

,

使用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);