【问题标题】:With Unity how do I inject a named dependency into a constructor?使用 Unity 如何将命名依赖项注入构造函数?
【发布时间】:2011-08-12 21:58:20
【问题描述】:

我在以下代码中注册了两次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;

   .....
}

有没有办法让我的假代码工作?

【问题讨论】:

    标签: c# inversion-of-control unity-container


    【解决方案1】:

    您可以在 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&gt;("Customer") 应该注入到另一个 ClientModel 实例中
    • @Legends 当然,使用 API。为您需要的每种注入样式变体为 ClientModel 创建一个单独的命名注册,然后解决对 IClientModel 的依赖。与上述 IRepository 的工作方式相同。
    【解决方案2】:

    这是一个很晚的回复,但问题仍然出现在 Google 中。

    所以无论如何,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>();
    

    第二条建议是相同的,但使用工厂设计模式。

    希望这会有所帮助!

    【讨论】:

    • 我比 InjectionConstructor 更喜欢这种模式,但它不是隐藏在 StrategyResolver 后面的 Service Locator 吗?
    • 它看起来确实像某种形式的服务定位器,但我又认为它不是。您只是将 StrategyResolver 用作实现策略模式故事结尾的助手。 StrategyResolver 不是您的依赖注入容器。如果您开始做一些不正当的事情,例如将所有依赖项加载为命名依赖项并使用 StrategyResolver 查找它们,那么是的,在这种情况下,您实际上只是将其用作服务定位器反模式。如果您不滥用并坚持其目的,那么它会使您的代码更漂亮,而不依赖于您的 DI 技术。
    • StrategyResolver 在您的容器中注册为依赖项。依赖是通过构造函数注入的。如果您当前的类已在您的容器中注册,那么您的容器将能够通过查看您的类构造函数参数来注入您需要的依赖项。
    • 对此的改进(为了限制滥用)可能是限制 IStrategyResolver 接口。基本上,除了能够解决任何 T 之外,您可以只使用一个方法 IActionHandler ResolveMyStrategy(string strategyName).
    • 我的两分钱......我已经按照这些思路实现了一些东西,但是我没有将IUnityContainer 传递给解析器类型,而是使用Func&lt;string, ISomeInterface&gt; resolver 作为构造函数参数。因此,您可以将解决方案委托给 DI 项目模块,因此与所使用的容器无关。注册:container.RegisterInstance&lt;IStrategyResolver&gt;(new StrategyResolver(key =&gt; container.Resolve&lt;ISomeInteface&gt;(key)))。此外,这种方法对可能的服务定位器滥用更具限制性。
    【解决方案3】:

    你应该可以使用 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 的全部意义在于使存储库抽象到我的服务层。
    • 我想直接注入到构造函数中。 (这就是这个问题的重点。)。我只有 2 个 IRepository 映射。我正在寻找一种方法来帮助统一区分它们。
    • @Kyle - 在解析调用中指定名称不是传递的 - 解析 不会自动解析 。需要在容器中进行配置才能使用正确的名称。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-08
      • 2019-04-20
      • 2018-04-27
      相关资源
      最近更新 更多