【问题标题】:How to configure windsor for passing dependency as argument through dependency tree?如何配置 Windsor 以通过依赖树将依赖项作为参数传递?
【发布时间】:2011-12-14 07:41:13
【问题描述】:

我有以下组件组成:

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 向下传递,而且该树非常复杂,我不希望手动将记录器实例传递给每个需要它的组件,想知道 Windsor 是否可以帮助我?

更新:这可能有助于更好地理解问题: 在wiki 有通知:

内联依赖不会被传播 无论您传递给 Resolve 方法的任何参数都将仅对根组件可用
您正在尝试解决,以及它的拦截器。所有组件进一步向下(根的 依赖项,以及它们的依赖项等)将无法访问它们。

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

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

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

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

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

更新 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"));
    }
}

但事实并非如此。

【问题讨论】:

  • 重新“寻找来自可靠和/或官方来源的答案。”我并不是要吹嘘或排除其他答案,但我目前是 Castle-windsor 标签stackoverflow.com/tags/castle-windsor/topusers 的最佳回答者,也是 Castle 团队的成员:castleproject.org/community/team.html
  • Mauricio,我毫不怀疑您的知识(实际上我正在使用 solrnet,它是一个极好的库),我的意思是,我确信应该有解决方案或至少有明确的解释我做错了什么。您的答案专门针对 ILogger,但我的问题是如何在解决时刻将具体实例传递给组合。对不起,如果这看起来像我不认为你是可信的或smth。我已经更新了最让我困惑的部分。
  • 啊,现在更清楚了。之所以这样,是因为您实际上不应该将深层内联依赖项传递给 Resolve。您应该注册您的组件并让 Windsor 管理它们。问题是,为什么你不想注册你的组件?此外,如果这不是关于日志记录,请使用更合适的类比(或仅介绍您的具体案例)。
  • 您能提供更多有关您的问题的信息吗?或者你能解决它吗?
  • 用例子更新了问题,还没有解决。

标签: c# castle-windsor


【解决方案1】:

解析后不要手动传递您的记录器。让温莎为你做这件事。使用logging facility

【讨论】:

  • 事情是,要传递的记录器实例取决于组,分配给哪个作业,我认为记录工具只能注入值,这是在注册期间已知的,如果没有,你能提供一个例子?
  • @Giedrius :对不起,我不明白这个问题。日志记录工具会将适当的记录器传递给每个组件,并使用适当的记录器名称来标识日志中的组件。注册时不知道什么?
  • 我想我需要查看日志记录工具的来源以了解它是如何工作的,因为现在我看不到它如何在注册时传递正确的记录器名称(我的意思是我不需要每个JobC 的组件要有自己的 logger 名称,即使 JobC 也不应该有自己的 logger 名称,它应该带有组名,作业在运行时被分组,每组作业都有自己的 logging listener)。
  • @Giedrius :如果您需要自定义记录器并将组名添加到记录器名称中,那么是的,我会尝试自定义记录工具...
【解决方案2】:

我想我终于明白了为什么这相对容易实现,但在 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 上为 TransientA 进行递归属性注入,您也将更改 TransientB.SingletonC.Logger 属性!

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-05
    • 2018-04-18
    • 2023-03-09
    • 2016-05-30
    • 2023-02-16
    • 2021-07-28
    相关资源
    最近更新 更多