ChatGPT解决这个技术问题 Extra ChatGPT

ASP.NET Core Dependency Injection with Multiple Constructors

I have a tag helper with multiple constructors in my ASP.NET Core application. This causes the following error at runtime when ASP.NET 5 tries to resolve the type:

InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'MyNameSpace.MyTagHelper'. There should only be one applicable constructor.

One of the constructors is parameterless and the other has some arguments whose parameters are not registered types. I would like it to use the parameterless constructor.

Is there some way to get the ASP.NET 5 dependency injection framework to select a particular constructor? Usually this is done through the use of an attribute but I can't find anything.

My use case is that I'm trying to create a single class that is both a TagHelper, as well as a HTML helper which is totally possible if this problem is solved.

Your injectables should only have one constructor. Having multiple constructors is an anti-pattern.
Yes, it's not ideal. I'll come up with a work-around.
If you read the article completely, you'll see the answer is simple. In case a type you control: remove one constructor. If this MyTagHelper is a third party or framework type, register it using a factory delegate and call the specific constructor inside that delegate.
Curious if there is a way to unit test against this error for all controllers in a library.

I
Ian Kemp

Apply the ActivatorUtilitiesConstructorAttribute to the constructor that you want to be used by DI:

[ActivatorUtilitiesConstructor]
public MyClass(ICustomDependency d)
{
}

This requires using the ActivatorUtilities class to create your MyClass. As of .NET Core 3.1 the Microsoft dependency injection framework internally uses ActivatorUtilities; in older versions you need to manually use it:

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

This is awesome since I can have 2 constructors. One is for DI to inject IConfiguration, and the other one is used in my integration tests where I can specify the connection string.
Work like a charm, Should be Checked as Accepted Answer.
This is the correct answer. All the people complaining that it doesn't work are welcome to post a question, I'm 99% sure the problem lies somewhere else in your code.
K
Kévin Chalet

Illya is right: the built-in resolver doesn't support types exposing multiple constructors... but nothing prevents you from registering a delegate to support this scenario:

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

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

Note: support for multiple constructors was added in later versions, as indicated in the other answers. Now, the DI stack will happily choose the constructor with the most parameters it can resolve. For instance, if you have 2 constructors - one with 3 parameters pointing to services and another one with 4 - it will prefer the one with 4 parameters.


Thanks, this is useful for wiring up 3rd party libraries that I don't control.
How do I unit test controller then? I want to inject dependencies through constructor.
Despite comments above of support for multiple constructors being added later, it does not work as of today with .Net 5.0 .... Also there does not seem to be a way to specify... Note that the actual ASP.NET code works 100#, it is only within the integration tests that there is an issue.
M
Muhammad Rehan Saeed

ASP.NET Core 1.0 Answer

The other answers are still true for parameter-less constructors i.e. if you have a class with a parameter-less constructor and a constructor with arguments, the exception in the question will be thrown.

If you have two constructors with arguments, the behaviour is to use the first matching constructor where the parameters are known. You can look at the source code for the ConstructorMatcher class for details here.


B
Bernard Vander Beken

ASP.NET Core Answer

I've ended up with the following workaround until they fix/improve this.

First, declare only one constructor in your controller (passing your required configuration settings only), considering that the settings objects passed in the constructor can be null (.NET Core will inject them automatically if you configure them in the Startup method):

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);
        }
    }
}

Then, in your tests methods you can instantiate the controller in two ways:

Mocking the IOptions object when you construct the tested object Construct passing null in all parameters and then Stub the dependency that you will use in your tests. Following you have an example:

[TestClass] public class MyControllerTests { Service.Controllers.MyController controller; Mock _serviceStub; [TestInitialize] public void Initialize() { _serviceStub = new Mock(); controller = new Service.Controllers.MyController(null); controller.Service = _serviceStub.Object; } }

From this point you can have full testing with dependency injection and mocking ready in .NET Core.

Hope it helps