Castle Windsor IOC: Passing constructor parameters to child components
Asked Answered
C

3

6

The following code is for demo purposes only.

Lets say i have 2 components (businessService, and dataService), and a UI class.

UI class needs a business service, businessService needs a dataService, and dataService relies on a connectionString.

Form the UI class i need to resolve the business service, so i am writing the below code:

var service = container.Resolve<BusinessService>(new { dependancy = "con string 123" }));

notice that dependancy is the connectionString constructor parameter.

But the above code is not working, saying that dataService expecting dependancy which was not satisified.

Can't create component 'dataService' as it has dependencies to be satisfied. dataService is waiting for the following dependencies:

Keys (components with specific keys) - dependancy which was not registered.

So as a workaround i am doing this:

var service = container.Resolve<BusinessService>(new { dataService = container.Resolve<IDataService>(new { dependancy = "123" }) });

But from design, coding style and many perspectives this is not a good way of doing it.

So please if you can advise why its not working in the simple way or you have a better workaround please share.

Contention answered 11/10, 2010 at 9:23 Comment(4)
Can you please provide a short but complete definition for BusinessService, DataService, and dependancy/connectionString that will fail? Strip out any code that doesn't directly contribute to reproducing the problem.Papilloma
public class BusinessService { public BusinessService(DataService dataService) {} }Contention
public class DataService { public DataService(string dependency) {} }Contention
Windsor Dynamic Parameters : kozmic.net/2009/12/10/…Consolidation
E
5

The behavior you see is by design.

There are a couple of ways to approach the problem, depending on how dynamic the value you want to pass down is.

The documentation does a pretty good job detailing it, so I won't reiterate that here.

Update

To clarity - Windsor does not pass inline arguments down the resolution pipeline. The reason for that is simple - doing so would break an abstraction. Calling code would have to implicitly know that your BusinessService depends on DataService which depends on connection string.

If you absolutely have to do it, than do make this explicit. That is do pretty much what you're doing - resolve the DataService with its dependency on connection string explicitly, and explicitly resolve BusinessService passing the DataService as dependency.

To make things really explicit (and nicer to use as well) I'd suggest using Typed Factory for that instead of directly calling the container

public interface IFactory
{
   IDataService ResolveDataService(string connectionString);
   IBussinessService ResolveBussinessService(IDataService dataService);
   // possibly release method for IBussinessService as well
}
Explorer answered 11/10, 2010 at 10:49 Comment(6)
checked your link, but seriously i cant understand wht they are saying, can we char or sthng to have simple answerContention
@moutasema My simple question is, when i resolve the parent component -but- sending parameters for its child, would that work ? @moutasema Can container automatically pass the params to the child component ? @kkozmic no, inline arguments are not propagated down the resolution pipelineContention
@Moutasem al-awa: in a nutshell, you're trying to do something that in 99% of the cases is bad practice. Avoid calling Resolve() directly (since you're doing service location and not dependency injection) and you'll see the correct way to do it.Zither
Hi mauricio, what other way of code pattern code be followed woulld you include a simple sample ?Contention
@Moutasem al-awa: I'd just introduce a class or delegate to provide the connection string and then register it with the container before doing the resolution. That way, you can depend on a more specific type than String.Cortege
nice catch @Nate-Wilkins :) FixedExplorer
M
2

I have needed to do this when creating transient components that require a context object. The solution I used was to override the DefaultDependencyResolver class so that it does pass inline arguments down the resolution pipeline.

public class ArgumentPassingDependencyResolver : DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(
        CreationContext current, Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            // this behaviour copied from base class
            return current;
        }

        // the difference in the following line is that "true" is passed
        // instead of "false" as the third parameter
        return new CreationContext(parameterType, current, true);
    }
}

An instance of this class needs to be passed in when the container is created (other classes also need to be passed in because there's no convenient constructor that only takes a dependency resolver):

var container = new WindsorContainer(
    new DefaultKernel(
        new ArgumentPassingDependencyResolver(),
        new NotSupportedProxyFactory()),
    new DefaultComponentInstaller());
Mornings answered 7/12, 2010 at 19:51 Comment(0)
A
0

Yes, what you're requesting is possible, but you should be using abstract factories through the Typed Factory Facility instead of requesting your service directly through the container.

With typed factories, all you need to do is define the factory interfaces and Windsor will take care of the implementation for you.

public interface IBusinessServiceFactory {
  IBusinessService CreateBusinessService(string connString);
}

public interface IDataServiceFactory {
  IDataService CreateDataService(string connString);
}

You add the facility and register your factories interface as follows:

container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IDataServiceFactory>().AsFactory());
container.Register(Component.For<IBusinessServiceFactory>().AsFactory());

Your can now manually define how your runtime parameter gets passed down the object graph by defining a Dynamic Parameter in your BusinessServiceregistration.

container.Register(Component.For<IBusinessService, BusinessService>()
    .LifestyleTransient()
    .DynamicParameters((k, d) => {
        d["dataService"] = k.Resolve<IDataServiceFactory>.CreateDataService((string)d["connString"]);
    }));

Keep in mind that the dictionary keys have to match the parameter names in the CreateBusinessService method and BusinessService constructor.

You should also make it LifestyleTransient if you intend to create a new instance each time the factory method is called. (The default is singleton)

Alitaalitha answered 19/1, 2018 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.