如何配置windsor通过依赖树传递依赖作为参数?

时间:2011-12-14 07:41:14

标签: c# castle-windsor

我有以下组件组成:

public interface IJob {
    ILogger Logger { get; set; }
}

public class JobC : IJob
{
    public ILogger Logger { get; set; }
    private ServiceA serviceA;
    private ServiceB serviceB;

    public JobC(ServiceA serviceA, ServiceB serviceB)
    {
        this.serviceA = serviceA;
        this.serviceB = serviceB;
    }
}

public class ServiceB
{
    public ILogger Logger { get; set; }
}

public class ServiceA
{
    public ILogger Logger { get; set; }
}

正如您所看到的,周围都有Logger属性。事实是,我需要在解析过程中传递该属性值(不同的工作需要不同的配置记录器)。 因此,如果只有顶级组件需要这个,那就像

一样简单
var childLogger = Logger.CreateChildLogger(jobGroupName);
var job = windsorContainer.Resolve(jobType);
job.Logger = childLogger;

但是我需要将childLogger传递给树,并且该树非常复杂,我不希望手动将logger实例传递给每个组件,这需要它,想知道Windsor是否可以帮助我吗?

更新:可能这有助于更好地理解问题: 在wiki中注意到:

  

内联依赖项不会传播   无论您传递给Resolve方法的参数是什么,都只能用于根组件
  你试图解决它的拦截器。所有组件进一步下降(root的   依赖项及其依赖项等)将无法访问它们。

为什么会这样,是否有解决方法?

更新2: 如果我能添加真实情况,可能会有所帮助。

因此,我们有应用程序,它从/向各种销售渠道发送/接收数据。每个销售渠道都有相应的工作集合,例如发送更新的产品信息,接收订单等(每个工作可能包含更小的工作)。所以合乎逻辑的是,我们需要将每个通道的日志信息与其他通道分开,但是单通道的作业日志应该转到单个监听器,我们可以看到正在发生的事情的顺序(如果每个作业和子作业都有自己的日志监听器) ,我们需要按时间合并日志以了解发生了什么。在编译时不知道某些通道及其作业集(假设有通道A,我们可以通过简单地将该国家添加到DB来启动特定国家/地区的单独通道,具体取决于我们可以切换同步方法的负载等)。

这意味着我们可能拥有UpdateProductsForChannelAJob,它将用于两个不同的渠道(ChannelA US和ChannelA UK),因此它的记录器将取决于它所依赖的渠道。

所以我们现在正在做的是,我们为每个通道创建子记录器,并在解析Job实例作为参数时传递它。这有用,但有一件烦人的事情 - 我们必须在job中手动将logger实例传递给每个依赖项(和依赖项依赖项),这可能是记录了一些东西。

更新3:

我在Windsor文档功能中找到了这听起来像我需要的东西:

  

有时您需要提供依赖项,在组件创建时才会知道该依赖项。例如,假设您需要为您的服务创建时间戳。您知道如何在注册时获取它,但您不知道它的具体值是什么(实际上每次创建新实例时它都会有所不同)。在这种情况下,您使用DynamicParameters方法。

你在DynamicParameters委托中得到两个参数,其中一个是字典和

  

现在,您可以使用依赖项来填充该字典,该依赖项将进一步传递到解析管道

鉴于此,我认为这样可行:

public interface IService
{
}

public class ServiceWithLogger : IService
{
    public ILogger Logger { get; set; }
}

public class ServiceComposition
{
    public ILogger Logger { get; set; }

    public IService Service { get; set; }

    public ServiceComposition(IService service)
    {
        Service = service;
    }
} 

public class NameService
{
    public NameService(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
}

public class NameServiceConsumer
{       
    public NameService NameService { get; set; }
}

public class NameServiceConsumerComposition
{       
    public NameService NameService { get; set; }
    public NameServiceConsumer NameServiceConsumer { get; set; }
}

[TestFixture]
public class Tests
{
    [Test]
    public void GivenDynamicParamtersConfigurationContainerShouldPassLoggerDownTheTree()
    {
        var container = new WindsorContainer();
        container.AddFacility<LoggingFacility>();
        container.Register(
            Component.For<IService>().ImplementedBy<ServiceWithLogger>().LifestyleTransient(),
            Component.For<ServiceComposition>().DynamicParameters((k, d) =>
            {
                d["Logger"] = k.Resolve<ILogger>().CreateChildLogger(d["name"].ToString());
            }).LifestyleTransient()
            );

        var service = container.Resolve<ServiceComposition>(new { name = "my child" });
        var childLogger = ((ServiceWithLogger) service.Service).Logger;
        Assert.IsTrue(((ConsoleLogger)childLogger).Name.Contains("my child"));
    }

    [Test]
    public void GivenDynamicParamtersConfigurationContainerShouldPassNameDownTheTree()
    {
        var container = new WindsorContainer();
        container.AddFacility<LoggingFacility>();
        container.Register(
            Component.For<NameService>().LifestyleTransient().DependsOn(new {name = "default"}),
            Component.For<NameServiceConsumer>().LifestyleTransient(),
            Component.For<NameServiceConsumerComposition>().DynamicParameters((k, d) =>
            {
                d["nameService"] = k.Resolve<NameService>(d["nameParam"]);
            }).LifestyleTransient()
            );

        var service = container.Resolve<NameServiceConsumerComposition>(new { nameParam = "my child" });
        Console.WriteLine(service.NameServiceConsumer.NameService.Name);
        Assert.IsTrue(service.NameServiceConsumer.NameService.Name.Contains("my child"));
    }
}

但事实并非如此。

2 个答案:

答案 0 :(得分:2)

解析后请勿手动传递记录器。让温莎为你做。使用logging facility

答案 1 :(得分:1)

我认为我终于明白为什么这个相对容易实现,但是在Windsor中没有实现(我认为任何其他容器)。 假设我们有以下配置:

public class TransientA
{
    public SingletonC SingletonC { get; set; }
    public ILogger Logger { get; set; }
}

public class TransientB
{
    public SingletonC SingletonC { get; set; }
    public ILogger Logger { get; set; }
}

public class SingletonC
{
    public ILogger Logger { get; set; }
}

类名反映了他们的生活方式,所以如果你要在Resolve for TransientA上进行递归属性注入,你也会改变TransientB.SingletonC.Logger属性!

你可以在传播时跳过单例属性注入,但是a)会增加混乱b)无论如何都不会解决初始问题(一些日志记录将转到singlenton的记录器)。

因此,在使用递归属性注入的情况下,您需要添加限制,该组件不应该在其依赖关系层次结构中具有单独依赖关系(PerWebRequest / PerThread),这是非常有限的。