如何在 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;
}
//...
注册接口及其实现的地方
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
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);