具有多个构造函数的 ASP.NET Core 依赖注入

Posted

技术标签:

【中文标题】具有多个构造函数的 ASP.NET Core 依赖注入【英文标题】:ASP.NET Core Dependency Injection with Multiple Constructors 【发布时间】:2016-01-01 02:42:39 【问题描述】:

我的 ASP.NET Core 应用程序中有一个带有多个构造函数的标签助手。当 ASP.NET 5 尝试解析类型时,这会在运行时导致以下错误:

InvalidOperationException:在类型“MyNameSpace.MyTagHelper”中发现了多个接受所有给定参数类型的构造函数。应该只有一个适用的构造函数。

其中一个构造函数是无参数的,另一个有一些参数不是注册类型的参数。我希望它使用无参数构造函数。

有没有办法让 ASP.NET 5 依赖注入框架选择特定的构造函数?通常这是通过使用属性来完成的,但我找不到任何东西。

我的用例是我正在尝试创建一个既是 TagHelper 又是 html 助手的类,如果解决了这个问题,这是完全可能的。

【问题讨论】:

您的注射剂应该只有一个构造函数。有multiple constructors is an anti-pattern. 是的,这并不理想。我会想出一个变通办法。 如果您完整阅读the article,您会发现答案很简单。如果您控制的类型:删除一个构造函数。如果此 MyTagHelper 是第三方或框架类型,请使用工厂委托注册它并调用该委托中的特定构造函数。 好奇是否有办法针对库中所有控制器的此错误进行单元测试。 【参考方案1】:

ActivatorUtilitiesConstructorAttribute 应用于您希望DI 使用的构造函数:

[ActivatorUtilitiesConstructor]
public MyClass(ICustomDependency d)


这需要使用ActivatorUtilities 类来创建您的MyClass。从 .NET Core 3.1 开始,Microsoft 依赖注入框架在内部使用 ActivatorUtilities;在旧版本中,您需要手动使用它:

services.AddScoped(sp => ActivatorUtilities.CreateInstance<MyClass>(sp));

【讨论】:

这太棒了,因为我可以有 2 个构造函数。一种是让 DI 注入 IConfiguration,另一种是在我的集成测试中使用,我可以在其中指定连接字符串。 像魅力一样工作,应该被检查为接受的答案。 这是正确答案。欢迎所有抱怨它不起作用的人发布问题,我 99% 确定问题出在您代码的其他地方。【参考方案2】:

Illya 是对的:内置解析器不支持公开多个构造函数的类型...但没有什么能阻止您注册委托以支持这种情况:

services.AddScoped<IService>(provider => 
    var dependency = provider.GetRequiredService<IDependency>();

    // You can select the constructor you want here.
    return new Service(dependency, "my string parameter");
);

注意: 在后续版本中添加了对多个构造函数的支持,如其他答案所示。现在,DI 堆栈将愉快地选择具有最多可解析参数的构造函数。例如,如果您有 2 个构造函数 - 一个具有 3 个指向服务的参数,另一个具有 4 个参数 - 它会更喜欢具有 4 个参数的构造函数。

【讨论】:

谢谢,这对于连接我无法控制的第 3 方库很有用。 那我该如何对控制器进行单元测试呢?我想通过构造函数注入依赖。 尽管上面的 cmets 稍后会添加对多个构造函数的支持,但截至目前,它不适用于 .Net 5.0 .... 还有 not 似乎作为一种指定方式...请注意,实际的 ASP.NET 代码工作 100#,只有在集成测试中才会出现问题。【参考方案3】:

ASP.NET Core 1.0 答案

对于无参数构造函数,其他答案仍然正确,即如果您有一个具有无参数构造函数的类和一个带参数的构造函数,则会抛出问题中的异常。

如果您有两个带参数的构造函数,则行为是使用已知参数的第一个匹配构造函数。你可以查看ConstructorMatcher类的源代码了解详情here。

【讨论】:

【参考方案4】:

ASP.NET Core 答案

在他们修复/改进此问题之前,我已采用以下解决方法。

首先,在你的控制器中只声明一个构造函数(只传递你需要的配置设置),考虑到构造函数中传递的设置对象可以为空(如果你在 Startup 方法中配置它们,.NET Core 将自动注入它们) :

public class MyController : Controller

    public IDependencyService Service  get; set; 

    public MyController(IOptions<MySettings> settings)
    
        if (settings!= null && settings.Value != null)
        
            Service = new DependencyServiceImpl(settings.Value);
        
    

然后,在您的测试方法中,您可以通过两种方式实例化控制器:

    在构造测试对象时模拟 IOptions 对象 构造在所有参数中传递 null,然后存根您将在测试中使用的依赖项。举个例子:
[TestClass]
    public class MyControllerTests
    
        Service.Controllers.MyController controller;
        Mock<IDependencyService> _serviceStub;

        [TestInitialize]
        public void Initialize()
        
            _serviceStub = new Mock<IDependencyService>();
            controller = new Service.Controllers.MyController(null);
            controller.Service = _serviceStub.Object;
        
    

从这一点开始,您可以在 .NET Core 中使用依赖注入和模拟进行全面测试。

希望对你有帮助

【讨论】:

以上是关于具有多个构造函数的 ASP.NET Core 依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET CORE 中使用具有依赖注入的动作过滤器?

ASP.NET Core Web 应用程序系列- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)

ASP.NET Core Web 应用程序系列- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)

ASP.NET Core MVC 之依赖注入 Controller

ASP.NET Core 依赖注入最佳实践——提示与技巧

[Asp.Net Core]Autofac单抽象多实现构造函数注入