Dependency injection resolving by name

2019-01-14 17:32发布

问题:

How I can inject different implementation of object for specific class?

For example in unity I can: Define two implementation of IRepository

container.RegisterType<IRepository, TestSuiteRepositor("TestSuiteRepository");
container.RegisterType<IRepository, BaseRepository>(); 

and call needed implementation

public BaselineManager([Dependency("TestSuiteRepository")]IRepository repository)

回答1:

As @Tseng pointed, there is no built-in solution for named binding. However using factory method may be helpful for your case. Example should be something like below:

Create a repository resolver:

public interface IRepositoryResolver
{
    IRepository GetRepositoryByName(string name);
}

public class RepositoryResolver : IRepositoryResolver 
{
    private readonly IServiceProvider _serviceProvider;
    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public IRepository GetRepositoryByName(string name)
    {
         if(name == "TestSuiteRepository") 
           return _serviceProvider.GetService<TestSuiteRepositor>();
         //... other condition
         else
           return _serviceProvider.GetService<BaseRepositor>();
    }

}

Register needed services in ConfigureServices.cs

services.AddSingleton<IRepositoryResolver, RepositoryResolver>();
services.AddTransient<TestSuiteRepository>();
services.AddTransient<BaseRepository>(); 

Finally use it in any class:

public class BaselineManager
{
    private readonly IRepository _repository;

    public BaselineManager(IRepositoryResolver repositoryResolver)
    {
        _repository = repositoryResolver.GetRepositoryByName("TestSuiteRepository");
    }
}


回答2:

In addition to @adem-caglin answer I'd like to post here some reusable code I've created for name-based registrations.

UPDATE Now it's available as nuget package.

In order to register your services you'll need to add following code to your Startup class:

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();
        services.AddByName<IService>()
            .Add<ServiceA>("key1")
            .Add<ServiceB>("key2")
            .Add<ServiceC>("key3")
            .Build();

Then you can use it via IServiceByNameFactory interface:

public AccountController(IServiceByNameFactory<IService> factory) {
    _service = factory.GetByName("key2");
}

Full code of the extension is in github.



回答3:

You can't with the built-in ASP.NET Core IoC container.

This is by design. The built-in container is intentionally kept simple and easy extensible, so you can plug-in 3rd party containers in if you need more features.

You have to use a 3rd party container to do this, like Autofac (see docs).

public BaselineManager([WithKey("TestSuiteRepository")]IRepository repository)


回答4:

After having read the official documentation for dependency injection, I don't think you can do it in this way.

But the question I have is: do you need these two implementations at the same time? Because if you don't, you can create multiple environments through environment variables and have specific functionality in the Startup class based on the current environment. or even create multiple Startup{EnvironmentName}.

When an ASP.NET Core application starts, the Startup class is used to bootstrap the application, load its configuration settings, etc. (learn more about ASP.NET startup). However, if a class exists named Startup{EnvironmentName} (for example StartupDevelopment), and the ASPNETCORE_ENVIRONMENT environment variable matches that name, then that Startup class is used instead. Thus, you could configure Startup for development, but have a separate StartupProduction that would be used when the app is run in production. Or vice versa.

I also wrote an article about injecting dependencies from a JSON file so you don't have to recompile the entire application every time you want to switch between implementations. Basically, you keep a JSON array with services like this:

"services": [
    {
        "serviceType": "ITest",
        "implementationType": "Test",
        "lifetime": "Transient"
    }
]

Then you can modify the desired implementation in this file and not have to recompile or change environment variables.

Hope this helps!