作为我正在开发的ASP.Net核心项目的一部分,我需要从WebApi中与许多不同的基于Rest的API端点进行通信。为了实现这一点,我使用了许多服务类,每个服务类都实例化一个静态HttpClient
。基本上,我为WebApi连接的每个基于Rest的端点都有一个服务类。
下面将介绍如何在每个服务类中实例化静态HttpClient
的示例。
private static HttpClient _client = new HttpClient()
{
BaseAddress = new Uri("http://endpointurlexample"),
};
虽然上述方法运行良好,但它不允许对使用HttpClient
的服务类进行有效的单元测试。为了让我能够进行单元测试,我想在我的单元测试中使用HttpMessageHandler
假HttpClient
,而HttpClient
如上所述实例化,但我无法将假HttpMessageHandler
作为单元测试的一部分。
服务类中HttpClient
在整个应用程序中保留单个实例(每个端点一个实例)的最佳方法是什么,但允许在单元期间应用不同的HttpMessageHandler
测试
我想到的一种方法是不使用静态字段来保存服务类中的HttpClient
,而是允许通过使用单例生命周期的构造函数注入来注入它,这将允许我在单元测试期间指定具有所需HttpClient
的{{1}},我想到的另一个选项是使用在静态字段中实例化HttpMessageHandler
的{{1}}工厂类然后可以通过将HttpClient
工厂注入服务类来检索,再次允许在单元测试中返回与相关HttpClient
不同的实现。以上都没有感觉特别干净,感觉必须有更好的方法吗?
有任何问题,请告诉我。
答案 0 :(得分:16)
从评论中添加对话似乎需要.span-legend-item {
background-color: #666;
color: #fff;
}
工厂
HttpClient
并且核心功能的实现看起来像这样。
public interface IHttpClientFactory {
HttpClient Create(string endpoint);
}
那就是说,如果你对上述设计不是特别满意的话。您可以抽象出服务背后的static IDictionary<string, HttpClient> cache = new Dictionary<string, HttpClient>();
public HttpClient Create(string endpoint) {
HttpClient client = null;
if(cache.TryGetValue(endpoint, out client)) {
return client;
}
client = new HttpClient {
BaseAddress = new Uri(endpoint),
};
cache[endpoint] = client;
return client;
}
依赖关系,以便客户端不会成为实现细节。
该服务的消费者无需确切知道如何检索数据。
答案 1 :(得分:9)
$post_id = array_map(function($item){
return $item->{'post_id'};
},$post_id);
属性的HttpClient工厂或访问器,并以与ASP.NET Core允许HttpClient
注入相同的方式使用它
HttpContext
并将其注入您的服务
public IHttpClientAccessor
{
HttpClient HttpClient { get; }
}
public DefaultHttpClientAccessor : IHttpClientAccessor
{
public HttpClient Client { get; }
public DefaultHttpClientAccessor()
{
Client = new HttpClient();
}
}
在Startup.cs中注册:
public class MyRestClient : IRestClient
{
private readonly HttpClient client;
public MyRestClient(IHttpClientAccessor httpClientAccessor)
{
client = httpClientAccessor.HttpClient;
}
}
对于单元测试,只需模拟它
services.AddSingleton<IHttpClientAccessor, DefaultHttpClientAccessor>();
答案 2 :(得分:3)
我目前的偏好是从每个目标端点域1>一次HttpClient
派生,并使用依赖注入使其成为单身,而不是直接使用HttpClient
。
假设我正在向example.com发出HTTP请求,我会有一个ExampleHttpClient
继承自HttpClient
并且具有与HttpClient
相同的构造签名,允许您传递和模拟HttpMessageHandler
正常。
public class ExampleHttpClient : HttpClient
{
public ExampleHttpClient(HttpMessageHandler handler) : base(handler)
{
BaseAddress = new Uri("http://example.com");
// set default headers here: content type, authentication, etc
}
}
然后我在我的依赖注入注册中将ExampleHttpClient
设置为单例,并将HttpMessageHandler
的注册添加为瞬态,因为每个http客户端类型只创建一次。使用此模式,我不需要为HttpClient
或智能工厂进行多个复杂的注册,以根据目标主机名构建它们。
任何需要与example.com交谈的事情都应该依赖ExampleHttpClient
的构造函数依赖,然后它们共享同一个实例,并按设计获得连接池。
这种方式还为您提供了一个更好的位置来放置默认标头,内容类型,授权,基地址等内容,并有助于防止一个服务泄露到另一个服务的http配置。
答案 3 :(得分:1)
我可能迟到了,但我已经创建了一个Helper nuget包,允许你在单元测试中测试HttpClient端点。
NuGet:install-package WorldDomination.HttpClient.Helpers
回复:https://github.com/PureKrome/HttpClient.Helpers
基本思想是你创建虚假的响应有效负载,并将a FakeHttpMessageHandler
实例传递给您的代码,其中包含伪响应有效负载。然后,当您的代码尝试实际HIT该URI端点时,它不会...而只是返回虚假响应。 MAGIC!
这是一个非常简单的例子:
[Fact]
public async Task GivenSomeValidHttpRequests_GetSomeDataAsync_ReturnsAFoo()
{
// Arrange.
// Fake response.
const string responseData = "{ \"Id\":69, \"Name\":\"Jane\" }";
var messageResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData);
// Prepare our 'options' with all of the above fake stuff.
var options = new HttpMessageOptions
{
RequestUri = MyService.GetFooEndPoint,
HttpResponseMessage = messageResponse
};
// 3. Use the fake response if that url is attempted.
var messageHandler = new FakeHttpMessageHandler(options);
var myService = new MyService(messageHandler);
// Act.
// NOTE: network traffic will not leave your computer because you've faked the response, above.
var result = await myService.GetSomeFooDataAsync();
// Assert.
result.Id.ShouldBe(69); // Returned from GetSomeFooDataAsync.
result.Baa.ShouldBeNull();
options.NumberOfTimesCalled.ShouldBe(1);
}
答案 4 :(得分:-1)
内部使用HttpClient:
public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
{
private string Roles;
private static readonly HttpClient _httpClient = new HttpClient();