简体   繁体   中英

How to configure windsor for passing dependency as argument through dependency tree?

I have following component composition:

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; }
}

As you can see, there's Logger property all around. Thing is, that I need to pass that property value during resolving (different jobs are requiring different configuration loggers). So if only top component would need this, it would be as simple as

var childLogger = Logger.CreateChildLogger(jobGroupName);
var job = windsorContainer.Resolve(jobType);
job.Logger = childLogger;

But I need to pass childLogger down the tree and that tree is quite complex, I don't wish to manually pass logger instance to each component, which needs it, wondering if Windsor could help me at this?

Update: May be this will help better understand problem: In wiki there's notice:

Inline dependencies don't get propagated Whatever arguments you pass to Resolve method will only be available to the root component
you're trying to resolve, and its Interceptors. All the components further down (root's dependencies, and their dependencies and so on) will not have access to them.

Why is it like so and is there any workaround?

Update 2: May be it will help, if I'll add real situation.

So, we have Application, which sends/receives data from/to various sales channels. Each sales channel has corresponding collection of jobs, like send updated product information, receive orders, etc (each job may contain smaller jobs inside). So it is logical, that we need to keep each channel's log information separate from other channel's, but single channel's jobs logs should go to single listener, that we could see sequence of what is happening (if each job and subjob would have own logging listener, we would need to merge logs by time to understand what is going on). Some channels and their job sets are not known at compile time (let's say there's channel A, we can start separate channel for a specific country by simple adding that country to DB, depending on load we can switch synchronization method, etc.).

What all that means, that we may have UpdateProductsForChannelAJob, which will be used in two different channels (ChannelA US and ChannelA UK), so it's logger will depend on which channel it depends to.

So what we are doing now, is we create child logger for each channel and we pass it when resolving Job instance as a parameter. That works, but has one annoying thing - we have to pass logger instance manually inside job to each dependency (and dependencies dependency), that may be logging something.

Update 3:

I've found in Windsor documentation feature, that sounds like what I need:

There are times where you need to supply a dependency, which will not be known until the creation time of the component. For example, say you need a creation timestamp for your service. You know how to obtain it at the time of registration, but you don't know what its specific value will be (and indeed it will be different each time you create a new instance). In this scenarios you use DynamicParameters method.

And you get two parameters in DynamicParameters delegate, one of them is dictionary and

It is that dictionary that you can now populate with dependencies which will be passed further to the resolution pipeline

Given that, I thought this will work:

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

But it does not.

Don't pass your logger manually after resolving. Let Windsor do it for you. Use the logging facility .

I think I finally got the aha moment why this is relatively easy to implement, but is not implemented in Windsor (and I think any other container). Let's say we have following configuration:

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; }
}

class names reflect their lifestyles, so if you would do recursive property injection on Resolve for TransientA, you would change TransientB.SingletonC.Logger property also!

You could skip singletons property injection on propagation, but that a) would add confusion b) would not solve initial problem anyway (some of the logging would go to singlenton's logger).

So in case to use recursive property injection you would need to add limitation, that component should not have singleton dependencies (PerWebRequest/PerThread also) in it's dependency hierarchy, which is quite limiting.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM