Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用特定的HttpMessageHandler注入单实例HttpClient_C#_Asp.net Core_Asp.net Core Mvc_.net Core_Dotnet Httpclient - Fatal编程技术网

C# 使用特定的HttpMessageHandler注入单实例HttpClient

C# 使用特定的HttpMessageHandler注入单实例HttpClient,c#,asp.net-core,asp.net-core-mvc,.net-core,dotnet-httpclient,C#,Asp.net Core,Asp.net Core Mvc,.net Core,Dotnet Httpclient,作为我正在从事的ASP.Net核心项目的一部分,我需要从我的WebApi中与许多不同的基于Rest的API端点进行通信。为了实现这一点,我使用了许多服务类,每个服务类实例化一个静态的HttpClient。实际上,我为WebApi连接到的每个基于Rest的端点都有一个服务类 下面可以看到如何在每个服务类中实例化staticHttpClient private static HttpClient _client = new HttpClient() { BaseAddress = new U

作为我正在从事的ASP.Net核心项目的一部分,我需要从我的WebApi中与许多不同的基于Rest的API端点进行通信。为了实现这一点,我使用了许多服务类,每个服务类实例化一个静态的
HttpClient
。实际上,我为WebApi连接到的每个基于Rest的端点都有一个服务类

下面可以看到如何在每个服务类中实例化static
HttpClient

private static HttpClient _client = new HttpClient()
{
    BaseAddress = new Uri("http://endpointurlexample"),            
};
尽管上述方法运行良好,但它不允许对使用
HttpClient
的服务类进行有效的单元测试。为了使我能够执行单元测试,我有一个假的
HttpMessageHandler
,我想在我的单元测试中用于
HttpClient
,而
HttpClient
如上实例化,但是我无法将假的
HttpMessageHandler
作为单元测试的一部分

服务类中的
HttpClient
在整个应用程序中保持单个实例(每个端点一个实例)但允许在单元测试期间应用不同的
HttpMessageHandler
的最佳方式是什么

我想到的一种方法是不使用静态字段在服务类中保存
HttpClient
,而是允许使用单例生命周期通过构造函数注入来注入它,这将允许我在单元测试期间使用所需的
HttpMessageHandler
指定
HttpClient
,我想到的另一个选项是使用
HttpClient
工厂类,该类在静态字段中实例化了
HttpClient
s,然后可以通过将
HttpClient
工厂注入服务类中来检索这些字段,再次允许在单元测试中返回具有相关
HttpMessageHandler
的不同实现。但是,上面的这些感觉都不是特别干净,而且感觉一定有更好的方法吗


如果有任何问题,请告诉我。

你觉得很复杂。您所需要的只是一个带有
HttpClient
属性的HttpClient工厂或访问器,并以ASP.NET内核允许注入
HttpContext
的方式使用它

public interface IHttpClientAccessor 
{
    HttpClient Client { get; }
}

public class DefaultHttpClientAccessor : IHttpClientAccessor
{
    public HttpClient Client { get; }

    public DefaultHttpClientAccessor()
    {
        Client = new HttpClient();
    }
}
并将其注入到您的服务中

public class MyRestClient : IRestClient
{
    private readonly HttpClient client;

    public MyRestClient(IHttpClientAccessor httpClientAccessor)
    {
        client = httpClientAccessor.Client;
    }
}
在Startup.cs中注册:

services.AddSingleton<IHttpClientAccessor, DefaultHttpClientAccessor>();
services.AddSingleton();
对于单元测试,只需模拟它

// Moq-esque

// Arrange
var httpClientAccessor = new Mock<IHttpClientAccessor>();
var httpHandler = new HttpMessageHandler(..) { ... };
var httpContext = new HttpContext(httpHandler);

httpClientAccessor.SetupGet(a => a.Client).Returns(httpContext);

// Act
var restClient = new MyRestClient(httpClientAccessor.Object);
var result = await restClient.GetSomethingAsync(...);

// Assert
...
//Moq-esque
//安排
var httpClientAccessor=new Mock();
var httpHandler=newhttpmessagehandler(..){…};
var httpContext=新的httpContext(httpHandler);
SetupGet(a=>a.Client).Returns(httpContext);
//表演
var restClient=newmyrestclient(httpClientAccessor.Object);
var result=await restClient.GetSomethingAsync(…);
//断言
...

从评论中添加到对话中看起来需要一个
HttpClient
工厂

public interface IHttpClientFactory {
    HttpClient Create(string endpoint);
}
核心功能的实现可以是这样的

public类DefaultHttpClientFactory:IHttpClientFactory,IDisposable
{
私有只读ConcurrentDictionary\u HttpClient;
公共默认HttpClientFactory()
{
这是._httpClients=新的ConcurrentDictionary();
}
公共HttpClient创建(字符串端点)
{
if(this.\u httpClients.TryGetValue(端点,out var client))
{
返回客户;
}
client=新的HttpClient
{
BaseAddress=新Uri(端点),
};
这是.u httpClients.TryAdd(端点,客户端);
返回客户;
}
公共空间处置()
{
这个。处置(真实);
总干事(本);
}
受保护的虚拟void Dispose(bool disposing)
{
foreach(此中的var httpClient.\u httpClients)
{
httpClient.Value.Dispose();
}
}
}
也就是说,如果您对上述设计不是特别满意。您可以抽象掉服务背后的
HttpClient
依赖关系,这样客户端就不会成为实现细节


服务的消费者不需要确切地知道数据是如何检索的。

我可能会迟到,但我已经创建了一个Helper nuget包,允许您在单元测试中测试HttpClient端点

NuGet:
安装程序包worlddomation.HttpClient.Helpers

回购:

基本思想是创建伪响应负载,并将a
FakeHttpMessageHandler
实例传递给代码,其中包括该伪响应负载。然后,当您的代码试图实际命中URI端点时,它不会。。。而只是返回假响应。魔法

这里有一个非常简单的例子:

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

我目前的偏好是
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
设置为singleton,并为
HttpMessageHandler
添加一个注册,该注册是瞬态的,因为每个http客户端类型只创建一次。使用此模式,我不需要为
HttpClient
或智能工厂进行多个复杂的注册,就可以基于目标主机名构建它们

任何需要与example.com对话的内容都应该在
ExampleHttpClient
上建立构造函数依赖关系,然后它们都共享同一个实例,您就可以按照设计得到连接池

这边
public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
{
    private string Roles;
    private static readonly HttpClient _httpClient = new HttpClient();