ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

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

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

I
Ian Kemp

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% 确定问题出在代码中的其他地方。
K
Kévin Chalet

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 个参数的那个。


谢谢,这对于连接我无法控制的 3rd 方库很有用。
那我该如何对控制器进行单元测试呢?我想通过构造函数注入依赖项。
尽管上面对稍后添加了对多个构造函数的支持的评论,但它在今天不适用于 .Net 5.0 ....而且似乎没有一种方法来指定...请注意实际的 ASP。 NET 代码工作 100#,只有在集成测试中才会出现问题。
M
Muhammad Rehan Saeed

ASP.NET Core 1.0 答案

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

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


B
Bernard Vander Beken

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] 公共类 MyControllerTests { Service.Controllers.MyController 控制器;模拟 _serviceStub; [TestInitialize] public void Initialize() { _serviceStub = new Mock();控制器 = 新的 Service.Controllers.MyController(null);控制器.Service = _serviceStub.Object; } }

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

希望能帮助到你