在类库项目的基类中定义 HttpClient 实例的最佳方法是啥?

Posted

技术标签:

【中文标题】在类库项目的基类中定义 HttpClient 实例的最佳方法是啥?【英文标题】:What is the best approach to define an HttpClient instance in a base class of a class library project?在类库项目的基类中定义 HttpClient 实例的最佳方法是什么? 【发布时间】:2019-07-31 12:29:54 【问题描述】:

有一个应用程序有 3 个接口,想要使用这个应用程序的人需要实现这些接口。我创建了一个类库项目,它具有这些接口实现,我从同一个基类继承了所有这些接口实现,以便能够拥有一个 HttpClient。这是我到目前为止所做的:

    public class BaseProxy
    
        protected static readonly HttpClient Client;

        static BaseProxy()
        
            Client = new HttpClient();
        
    

我在所有派生类中都使用了这个客户端来发出 GetAsync 和 PostAsync 请求,如下所示:

    public class XProxyImplementation
    
        var response = Client.GetAsync(BaseUrl + "XXXApi/GetClientSettings/").Result;
        response.EnsureSuccessStatusCode();
    

顺便说一句,Web API 中的方法都不是异步的,我选择了单例解决方案,因为我不想对每个请求都使用 using 块。我的问题是我应该选择 DI 解决方案还是该代码足以用于内部使用的应用程序?欢迎提出任何改进建议。

我已经阅读了很多关于使用 DI 容器的答案,但这只是一个带有代理实现的类库。

我的另一个问题是即使我想使用 DI,目前我无法在我的构造函数类中引入 DI,因为使用我的实现的其他应用程序正在寻找一个空的构造函数。当我尝试将 HttpClient 参数传递给构造函数时,出现以下错误:

当前类型 System.Net.Http.HttpMessageHandler 是一个抽象 类,无法构造

使用我的 dll 的应用程序不允许我将任何参数传递给使用任何抽象类的构造函数。我猜这个应用程序使用 Unity 进行握手,并以某种方式寻找一个空的构造函数。一旦我尝试进行以下更改,我就会收到错误消息:

        public BaseProxy() : this(Service.HttpClient)
        
        

        public XProxyImplementation(HttpClient client) : base(client)
        
         

这就是为什么我实际上更喜欢单例实例而不是 DI 实现。

【问题讨论】:

【参考方案1】:

DI?是的

DI 将使您的代理类具有可测试性,而您当前的实现无法进行单元测试。它还将改进关注点分离:remove the responsibility 控制来自代理的 HttpClient 生命周期。

通常,您会这样做:

public abstract class BaseProxy

    protected readonly HttpClient Client;

    protected BaseProxy(HttpClient client)
    
        Client = client;
    

    // ... other members


public class XProxyImplementation : BaseProxy

    public XProxyImplementation(HttpClient client) : base(client)
    
    

    // ... other members

    public Task SendRequest() // for example
     
         return Client.GetAsync("....");
    

在测试期间,您将初始化HttpClient 的不同实例,注入HttpMessageHandler 的测试友好实现:

// you implement TestHttpMessageHandler that aids your tests
var httpClient = new HttpClient(new TestHttpMessageHandler());
var proxyUnderTest = new XProxyImplementation(httpClient);

请参阅 this blog post 以了解使用 HttpClientHttpMessageHandler 进行单元测试的说明。

DI 容器?没有

既然我们在代码中引入了依赖注入,接下来的问题是,应该使用什么注入机制。

在您的具体情况下,我会投票反对耦合到任何特定的 DI 容器,因为您希望您的库被许多不同的应用程序使用,并且您不想膨胀它们的依赖项(应用程序可能已经在使用不同的 DI 容器)。

此外,由于您发布的代码非常简单,所以一个成熟的 DI 容器将是一个矫枉过正。在生产代码中,您可以将您的单例 HttpClient 移动到“服务定位器”:

public static class SingletonServices

    public static readonly HttpClient HttpClient;

    static SingletonServices()
    
        HttpClient = new HttpClient();
    

因此,当您在生产代码中实例化代理时,您可以这样做:

var proxy = new XProxyImplementation(SingletonServices.HttpClient);

【讨论】:

我猜我不太清楚,所以我刚刚更新了我的问题。我已经尝试将参数传递给构造函数,但是使用我的 dll 的应用程序阻止我传递 HttpClient 对象,因为它在作为抽象类的构造函数之一上具有 HttpMessageHandler 参数。我猜该项目使用 Unity 进行握手,它只是寻找一个空的构造函数【参考方案2】:

我肯定会使用 Microsoft.Extensions.DependencyInjection 包为此提供 DI 解决方案。 https://dzone.com/articles/dependency-injection-in-net-core-console-applicati

您还应该非常清楚如何使用 GetAsync 等异步方法。 使用 .Result 几乎永远不会得到想要的结果,最好使方法异步并使用 await 关键字,如下所示:

var response = await Client.GetAsync(BaseUrl + "XXXApi/GetClientSettings/");

https://montemagno.com/c-sharp-developers-stop-calling-dot-result/

是一个很好的资源,用于了解此最佳实践的原因和方法

【讨论】:

【参考方案3】:

DI 就是答案。如果您不想使用 ID,则可以实现一个 HttpClientFactory。 你可以在这里阅读更多 https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

【讨论】:

以上是关于在类库项目的基类中定义 HttpClient 实例的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的类不继承其基类中定义的方法?

使用派生类中的基类方法 - 错误

如何在 Typescript 类中创建 Angular 5 HttpClient 实例

项目中的单例如何管理

虚函数

PHP能在类中实例化一个类吗