C# 面临模拟IServiceProvider扩展方法的问题

C# 面临模拟IServiceProvider扩展方法的问题,c#,unit-testing,dependency-injection,moq,C#,Unit Testing,Dependency Injection,Moq,我知道这个问题可能会重复,但我面临的问题是,我无法模拟非静态方法,因为调用非静态方法的是静态方法 我的控制器逻辑调用静态方法ServiceProviderServiceExtensions方法GetServices(此IServiceProvider提供程序),该方法似乎反过来调用非静态方法provider.GetService(serviceType) 基本上,我有依赖注入,其中一个接口有两个实现 services.AddSingleton<IProvider, CustomProvid

我知道这个问题可能会重复,但我面临的问题是,我无法模拟非静态方法,因为调用非静态方法的是静态方法

我的控制器逻辑调用静态方法
ServiceProviderServiceExtensions
方法
GetServices(此IServiceProvider提供程序)
,该方法似乎反过来调用非静态方法
provider.GetService(serviceType)

基本上,我有依赖注入,其中一个接口有两个实现

services.AddSingleton<IProvider, CustomProvider1>();
services.AddSingleton<IProvider, CustomProvider2>();
在控制器中,我将依赖项解析为

Controller1.cs

provider = serviceProvider.GetServices<IProvider>()
                .FirstOrDefault(lp => lp.GetType() == typeof(CustomeProvider1));
            and

Controller2.cs
provider = serviceProvider.GetServices<IProvider>()
                .FirstOrDefault(lp => lp.GetType() == typeof(CustomeProvider1));
我收到一个错误,没有注册类型
System.Collections.Generic.IEnumerable
[IProvider]
的服务。
我不能直接模仿
GetServicesmethod`因为它是静态的

有什么线索可以让我的测试被嘲笑吗? 谢谢

我的控制器逻辑调用静态ServiceProviderServiceExtensions方法GetServices(此IServiceProvider提供程序)

这就是事情开始出错的地方。从控制器内调用
GetServices
是的应用程序。你所有的麻烦都源于这种误用

相反,你应该:

  • 单独使用构造函数注入
  • 切勿将表示容器的另一个抽象的
    IServiceProvider
    注入到您的外部任何类的构造函数中
  • 这意味着您应该有一个构造函数,如下所示:

    public Controller1(IProvider provider)
    
    services.AddSingleton<CustomProvider1>();
    services.AddTransient<Controller1>(c => new Controller1(
        c.GetRequiredService<CustomProvider1>()));
    
    services.AddSingleton<CustomProvider2>();
    services.AddTransient<Controller2>(c => new Controller2(
        c.GetRequiredService<CustomProvider2>()));
    
    您的
    IProvider
    是不明确的,但是让我们暂时假设这是可以的。然而,让应用程序代码处理这种模糊性是不好的。相反,您应该在组合根中单独处理这种模糊性,具体操作如下:

    public Controller1(IProvider provider)
    
    services.AddSingleton<CustomProvider1>();
    services.AddTransient<Controller1>(c => new Controller1(
        c.GetRequiredService<CustomProvider1>()));
    
    services.AddSingleton<CustomProvider2>();
    services.AddTransient<Controller2>(c => new Controller2(
        c.GetRequiredService<CustomProvider2>()));
    
    上述注册仅在这些控制器只有一个依赖项的情况下有效,因为此方法有效地禁用了自动布线。如果类具有更多依赖项,则更易于维护的构造如下所示:

    services.AddTransient<Controller1>(c =>
        ActivatorUtilities.CreateInstance<Controller1>(
            c,
            c.GetRequiredService<CustomProvider1>()));
    
    services.AddTransient<Controller2>(c =>
        ActivatorUtilities.CreateInstance<Controller2>(
            c,
            c.GetRequiredService<CustomProvider1>()));
    
    这两个接口是否具有相同的签名无关紧要,因为LSP冲突表明这两个接口的行为实际上非常不同,因为交换实现会破坏它们的客户机

    但是,请注意,即使直接使用者继续工作,也可能意味着在交换实现时应用程序开始表现不正确。这并不意味着你违反了SRP。例如,当provider1登录到磁盘,provider2登录到数据库时,预期的行为是对controller1的调用将导致追加磁盘上的日志。因此,以另一种方式使用它并不是您想要实现的,而是您希望在组合根目录中配置的内容。不表示LSP违规;在这种情况下,合同仍然按照消费者的期望行事

    如果交换实现对他们的消费者没有显著影响,那么您就没有违反LSP,这意味着给定的注册是可行的。或者,您可以通过向我们提供一个“真正的”DI容器来简化事情;-)

    我的控制器逻辑调用静态ServiceProviderServiceExtensions方法GetServices(此IServiceProvider提供程序)

    这就是事情开始出错的地方。从控制器内调用
    GetServices
    是的应用程序。你所有的麻烦都源于这种误用

    相反,你应该:

  • 单独使用构造函数注入
  • 切勿将表示容器的另一个抽象的
    IServiceProvider
    注入到您的外部任何类的构造函数中
  • 这意味着您应该有一个构造函数,如下所示:

    public Controller1(IProvider provider)
    
    services.AddSingleton<CustomProvider1>();
    services.AddTransient<Controller1>(c => new Controller1(
        c.GetRequiredService<CustomProvider1>()));
    
    services.AddSingleton<CustomProvider2>();
    services.AddTransient<Controller2>(c => new Controller2(
        c.GetRequiredService<CustomProvider2>()));
    
    您的
    IProvider
    是不明确的,但是让我们暂时假设这是可以的。然而,让应用程序代码处理这种模糊性是不好的。相反,您应该在组合根中单独处理这种模糊性,具体操作如下:

    public Controller1(IProvider provider)
    
    services.AddSingleton<CustomProvider1>();
    services.AddTransient<Controller1>(c => new Controller1(
        c.GetRequiredService<CustomProvider1>()));
    
    services.AddSingleton<CustomProvider2>();
    services.AddTransient<Controller2>(c => new Controller2(
        c.GetRequiredService<CustomProvider2>()));
    
    上述注册仅在这些控制器只有一个依赖项的情况下有效,因为此方法有效地禁用了自动布线。如果类具有更多依赖项,则更易于维护的构造如下所示:

    services.AddTransient<Controller1>(c =>
        ActivatorUtilities.CreateInstance<Controller1>(
            c,
            c.GetRequiredService<CustomProvider1>()));
    
    services.AddTransient<Controller2>(c =>
        ActivatorUtilities.CreateInstance<Controller2>(
            c,
            c.GetRequiredService<CustomProvider1>()));
    
    这两个接口是否具有相同的签名无关紧要,因为LSP冲突表明这两个接口的行为实际上非常不同,因为交换实现会破坏它们的客户机

    但是,请注意,即使直接使用者继续工作,也可能意味着在交换实现时应用程序开始表现不正确。这并不意味着你违反了SRP。例如,当provider1登录到磁盘,provider2登录到数据库时,预期的行为是对controller1的调用将导致追加磁盘上的日志。因此,以另一种方式使用它并不是您想要实现的,而是您希望在组合根目录中配置的内容。不表示LSP违规;在这种情况下,合同仍然按照消费者的期望行事


    如果交换实现对他们的消费者没有显著影响,那么您就没有违反LSP,这意味着给定的注册是可行的。或者,您可以通过向我们提供一个“真正的”DI容器来简化事情;-)

    感谢您的详细回答@Steven。当我尝试实现services.AddTransient(c=>ActivatorUtilities.CreateInstance(c,c.GetRequiredService())时;我得到一个错误,因为我的控制器现在有两个参数化构造函数。错误是:在类型中找到多个接受所有给定参数类型的构造函数。我们能在一个构造函数本身中实现这一点吗?它似乎接受了所有必需的参数。你的组件应该只有一个构造函数。好吧,我试着把两个构造函数合并成一个大构造函数。现在我的控制器开了