使用Unity,如何将命名依赖项注入到构造函数中?

75

在以下代码中,我已经使用名称两次注册了IRespository:

// Setup the Client Repository
IOC.Container.RegisterType<ClientEntities>(new InjectionConstructor());
IOC.Container.RegisterType<IRepository, GenericRepository>
    ("Client", new InjectionConstructor(typeof(ClientEntities)));

// Setup the Customer Repository
IOC.Container.RegisterType<CustomerEntities>(new InjectionConstructor());
IOC.Container.RegisterType<IRepository, GenericRepository>
    ("Customer", new InjectionConstructor(typeof(CustomerEntities)));

IOC.Container.RegisterType<IClientModel, ClientModel>();
IOC.Container.RegisterType<ICustomerModel, CustomerModel>();

但是当我想要解决这个问题(使用 IRepository)时,我必须手动进行解析,如下所示:

public ClientModel(IUnityContainer container)
{
   this.dataAccess = container.Resolve<IRepository>(Client);

   .....
}
我想做的是在构造函数中解决它(就像IUnityContainer一样)。我需要某种方式来指定要解析的命名类型。
类似这样:(注意:不是真正的代码)
public ClientModel([NamedDependancy("Client")] IRepository dataAccess)
{
   this.dataAccess = dataAccess;

   .....
}

有没有办法让我的虚假代码运行?

4个回答

103

您可以在API、属性或通过配置文件中使用命名或未命名的方式配置依赖项。由于您之前没有提到XML,因此我将假设您正在使用API。

为了告诉容器解析一个命名的依赖项,您需要使用一个InjectionParameter对象。对于您的ClientModel示例,请按照以下操作进行:

container.RegisterType<IClientModel, ClientModel>(
    new InjectionConstructor(                        // Explicitly specify a constructor
        new ResolvedParameter<IRepository>("Client") // Resolve parameter of type IRepository using name "Client"
    )
);

这个指示告诉容器:“在解析ClientModel时,调用一个带有单一的IRepository参数的构造函数。解析该参数时,除了类型之外,还要使用名称“Client”。”

如果您想使用属性,您的示例几乎可以工作,您只需要更改属性名称:

public ClientModel([Dependency("Client")] IRepository dataAccess)
{
   this.dataAccess = dataAccess;

   .....
}

有没有可能在不使用依赖属性的情况下使其工作? 例如,IRepository< Customer >("Customer") 应该被注入到另一个 ClientModel 实例中。 - Legends
1
@Legends 当然,使用API。为每种注入样式的变化创建一个单独命名的ClientModel注册,然后解决对IClientModel的依赖关系。与上面的IRepository一样工作。 - Chris Tavares

29

虽然回复晚了,但这个问题仍然出现在谷歌搜索中。

无论如何,5年之后...

我的方法相当简单。通常情况下,当您需要使用“命名依赖项”时,是因为您正在尝试实现某种策略模式。在这种情况下,我会在Unity和我的代码之间创建一个间接层,称为StrategyResolver,以避免直接依赖于Unity。

public class StrategyResolver : IStrategyResolver
{
    private IUnityContainer container;

    public StrategyResolver(IUnityContainer unityContainer)
    {
        this.container = unityContainer;
    }

    public T Resolve<T>(string namedStrategy)
    {
        return this.container.Resolve<T>(namedStrategy);
    }
}

使用方法:

public class SomeClass: ISomeInterface
{
    private IStrategyResolver strategyResolver;

    public SomeClass(IStrategyResolver stratResolver)
    {
        this.strategyResolver = stratResolver;
    }

    public void Process(SomeDto dto)
    {
        IActionHandler actionHanlder = this.strategyResolver.Resolve<IActionHandler>(dto.SomeProperty);
        actionHanlder.Handle(dto);
    }
}

注册:

container.RegisterType<IActionHandler, ActionOne>("One");
container.RegisterType<IActionHandler, ActionTwo>("Two");
container.RegisterType<IStrategyResolver, StrategyResolver>();
container.RegisterType<ISomeInterface, SomeClass>();

现在,这件事的好处是,当我将来添加新策略时,我永远不必再去触碰StrategyResolver。
这很简单。非常干净,而且我将对Unity的依赖保持到最低限度。唯一需要触碰StrategyResolver的时间是如果我决定更改容器技术,这种情况非常不可能发生。
希望这可以帮助你!
编辑:我并不真的喜欢接受的答案,因为当你在服务的构造函数中使用Dependency属性时,你实际上对Unity有了一个硬依赖。Dependency属性是Unity库的一部分。此时,你也可以在任何地方传递IUnityContainer依赖。
我更喜欢我的服务类依赖于我完全拥有的对象,而不是在各个地方都有对外部库的强依赖。而且使用Dependency属性会使构造函数的签名变得不那么清晰和简单。
此外,这种技术允许在运行时解析命名依赖项,而无需在构造函数中硬编码命名依赖项,在应用程序配置文件中使用它们或使用InjectionParameter,这些方法都需要在设计时知道要使用哪个命名依赖项。
编辑(2016-09-19): 对于那些可能会想知道的人,当您请求IUnityContainer作为依赖项时,容器将知道传递自己,如StrategyResolver构造函数签名所示。
编辑(2018-10-20): 这里是另一种方法,简单地使用工厂:
public class SomeStrategyFactory : ISomeStrategyFactory
{
    private IStrategy _stratA;
    private IStrategy _stratB;

    public SomeFactory(IStrategyA stratA, IStrategyB stratB)
    {
        _stratA = stratA;
        _stratB = stratB;
    }

    public IStrategy GetStrategy(string namedStrategy){
        if (namedStrategy == "A") return _stratA;
        if (namedStrategy == "B") return _stratB;
    }
}

public interface IStrategy {
    void Execute();
}

public interface IStrategyA : IStrategy {}

public interface IStrategyB : IStrategy {}

public class StrategyA : IStrategyA {
    public void Execute(){}
}

public class StrategyB : IStrategyB {
    public void Execute() {}
}

使用方法:

public class SomeClass : ISomeClass
{
    public SomeClass(ISomeStrategyFactory strategyFactory){

        IStrategy strat = strategyFactory.GetStrategy("HelloStrategy");
        strat.Execute();

    }
}

注册:

container.RegisterType<ISomeStrategyFactory, SomeStrategyFactory>();
container.RegisterType<IStrategyA, StrategyA>();
container.RegisterType<IStrategyB, StrategyB>();
container.RegisterType<ISomeClass, SomeClass>();

这第二个建议与第一个相同,只是使用了工厂设计模式。
希望对您有所帮助!

1
我喜欢这种模式比InjectionConstructor更好,但StrategyResolver背后是否隐藏了Service Locator? - MistyK
1
看起来像某种形式的服务定位器,但我认为它不是。你只是使用StrategyResolver作为实现策略模式的帮助程序,就这样。StrategyResolver不是你的依赖注入容器。如果你开始像加载所有命名依赖项并使用StrategyResolver查找它们一样地做一些反常的事情,那么在这种情况下,你真的只是在使用这个作为服务定位器反模式。如果你不滥用并坚持其目的,那么它可以使你的代码美丽,而不依赖于你的DI技术。 - TchiYuan
StrategyResolver已在您的容器中注册为依赖项。该依赖项通过构造函数进行注入。如果您当前的类已在容器中注册,则容器将能够通过查看您的类构造函数参数来注入所需的依赖项。 - TchiYuan
2
为了限制滥用,对此进行改进的方法是限制IStrategyResolver接口。基本上,您可以只有一个方法IActionHandler ResolveMyStrategy(string strategyName)来解决任何T - macwier
3
我的个人看法是,我已经按照这个思路实现了一些东西,但是我没有将 IUnityContainer 传递给解析器类型,而是使用了一个构造函数参数 Func<string, ISomeInterface> resolver。因此,您可以将解析委托给 DI 项目模块,从而对所使用的容器保持不可知状态。注册代码如下:container.RegisterInstance<IStrategyResolver>(new StrategyResolver(key => container.Resolve<ISomeInteface>(key)))。此外,这种方法对可能的服务定位器滥用更加严格。 - Miguel A. Arilla
显示剩余3条评论

4

您应该能够使用ParameterOverrides

var repository = IOC.Container.Resolve<IRepository>("Client");
var clientModel = IOC.Container.Resolve<ClientModel>(new ParameterOverrides<ClientModel> { {"dataAccess", repository } } );

编辑: 我不确定为什么你要传递UnityContainer - 就个人而言,我们直接将依赖项注入到构造函数中(从我所见的情况来看,这是“正常”的做法)。但无论如何,您可以在RegisterType和Resolve方法中指定名称。

IOC.Container.RegisterType<IRepository, GenericRepository>("Client");
IOC.Container.Resolve<IRepository>("Client");

它将为您提供您注册的名称的类型。

那样做是可行的,但这意味着知道clientModel的层级也需要知道仓库是什么样子的。我需要将仓库从知道'clientModel'的层级中抽象出来。(事实上,clientModel的整个目的就是使我的服务层对仓库进行抽象化处理。) - Vaccano
1
我想直接注入到构造函数中。(这就是这个问题的重点。)我只有两个IRepository映射。我正在寻找一种帮助Unity区分它们的方法。 - Vaccano
@Kyle - 在resolve调用中指定名称不是传递性的 - 解析<ClientModel,“Client”>不会自动解析<IRepository,“Client”>。容器中需要配置以使用正确的名称。 - Chris Tavares

3
不要这样做-只需创建一个class ClientRepository : GenericRepository { }并利用类型系统即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接