1

在我看到的每个示例中,包括 Microsoft 的herehereIHttpClientFactory ,作者都解释了over所做的改进,HttpClient并举例说明了如何简单地开箱即用或以命名形式使用它。但是他们似乎都提到,使用 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)Typed 客户端进行单元测试时遇到了障碍,我们在获得了许多重要资源后得出的结论是,使用IHttpClientFactory. 但我还没有找到一个明确使用IHttpClientFactoryTyped 客户端的示例。

4

2 回答 2

2

该框架将用于ITypedHttpClientFactory创建HttpClient要注入到类型化客户端的。当类型化客户端配置如下时,这种情况发生在幕后:

services.AddHttpClient<ICatalogService, CatalogService>()

如果我们查看AddHttpClient,我们可以看到它会尝试创建IHttpClientFactory被调用的临时版本ITypedHttpClientFactory

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 方依赖项(框架问题)中解耦,因为您是控制类型化客户端及其抽象的人。

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

于 2020-11-10T03:26:20.930 回答
0

你有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");
    }
}

嘲笑

作为替代方案,我们可以通过使用自定义来避免使用Moq.ProtectedHttpMessageHandler

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);
于 2020-11-10T08:26:58.347 回答