【问题标题】:IHttpClientFactory in .net core.net 核心中的 IHttpClientFactory
【发布时间】:2019-11-15 18:22:12
【问题描述】:

我想在我的 .NET Core 项目中使用 IHttpClientFactory。问题是我需要使用大量 API。那么我应该对所有 API 使用单个 Typed Client 还是应该将它们分叉?所有 API 请求都发往同一个源。

public interface IStudentClient
{

}

public class StudentClient : IStudentClient
{

}

services.AddHttpClient<IStudentClient, StudentClient>();

我已遵循上述结构,并计划将所有 API 包含在 IStudentClient 中,并在 StudentClient 中实现它们。现在我的问题是,当只在一个类中包含所有 API 的实现时,这是否会使 StudentClient 类更复杂。

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

    在我看来,为对特定远程服务的所有访问编写一个大型类型的客户端是正确的方法。这正是 Microsoft 为类型化 http 客户端设想的使用模式。

    同时我理解你的担忧,但情况并没有你想象的那么绝望。

    首先你会得到一个巨大的接口,因此,一个巨大的实现类,但它们的职责是明确的:类型化客户端有责任定义一个代理来访问远程 Web 服务(学生服务在你的例子)。

    类型化的客户端类实际上并不复杂:它可以是巨大的,当然,但它是无状态的,并且只是公开了访问远程 Web 服务端点的方法。每个方法都有明确且定义明确的职责:访问远程 Web 服务上的特定端点;这样的代码很少复杂。

    唯一需要考虑的是从控制器或服务中使用接口IStudentClient。接口很大,所以如果你将它作为依赖注入到消费者类中,你将违反interface segregation principle。这个问题的一个可能的解决方案是对更小的接口进行建模,以满足消费者类的特定需求。

    想象一下,您的远程 Web 服务公开的一个端点可以让您获取单个学生的详细信息(可能类似于 GET /students/{studentId})。这意味着IStudentClient 公开的方法之一将是GetStudentById(Guid studentId),它将GET 请求包装到/students/{studentId}

    此时你可以定义一个更小的接口,称为IStudentProvider,形状如下:

    public interface IStudentProvider
    {
      StudentContract GetstudentById(Guid studentId);
    }
    

    现在您可以将较小的接口IStudentProvider 注入您的消费者类(例如,您在应用程序中定义的 MVC 控制器或服务类)。

    要实现接口IStudentProvider,您可以执行以下操作:

    public class HttpStudentProvider : IStudentProvider 
    {
      private readonly IStudentClient client;
    
      public HttpStudentProvider(IStudentClient client)
      {
        this.client = client;
      }
    
      public StudentContract GetstudentById(Guid studentId) 
      {
        return this.client.GetStudentById(studentId);
      }
    }
    

    重要免责声明:为了让讨论简单,我没有在接口上使用Task 类,但当然所有方法都应该返回Task&lt;T&gt; 并接受一个实例CancellationToken 作为参数,因为 http 调用是自然的异步操作,并且您确实希望使用您的 http 客户端执行阻塞调用。

    如何在 DI 容器上注册这些类

    Microsoft DI 容器将为您提供一些扩展方法来注册类型化客户端。该服务将注册为临时依赖项,因此依赖于它的每个其他服务也必须注册为临时依赖项(以避免captive dependency issue)。

    这是您应该注册服务的方式:

    public void ConfigureServices(IServiceCollection services)
    {
       services.AddHttpClient<IStudentClient, StudentClient>();
       services.AddTransient<IStudentProvider, HttpStudentProvider>();
    }
    

    【讨论】:

    • 好的。那么对于每个不同的模块,我应该创建一个不同的界面吗?然后将它们注入到 startup.cs 文件中,例如 services.AddHttpClient();和 services.AddHttpClient();
    • @Sunny 我编辑了我的回复,并为故事的注册部分添加了一些指南
    • 那么当我需要像私有只读 IStudentProvider _studentProvider 一样调用 GetstudentById() 时,是否需要在主控制器中初始化 IStudentProvider ;公共 HomeController(IStudentProvider studentProvider) { _studentProvider= studentProvider ; } 还是只初始化 IStudentClient 就足够了?
    • @Sunny 是的,您的类应该依赖于接口IStudentProvider,因此您应该在控制器构造函数中注入IStudentProvider 的实例。
    猜你喜欢
    • 1970-01-01
    • 2020-10-11
    • 2017-10-23
    • 2017-08-05
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 2018-06-30
    相关资源
    最近更新 更多