Reputation: 23747
Using Castle Windsor, I'd like to create a class that records an integer. But I'd like to decorate it several times with other classes. I can see how this works if all concretes involved have dependencies that can be resolved, but that's not the case here. Consider this code:
public interface IRecorder
{
void Add(int value);
}
public class NotifyingRecorder : IRecorder
{
readonly IRecorder decoratedRecorder;
public NotifyingRecorder(IRecorder decoratedRecorder)
{
this.decoratedRecorder = decoratedRecorder;
}
public void Add(int value)
{
decoratedRecorder.Add(value);
System.Console.WriteLine("Added " + value);
}
}
public class ModelUpdatingRecorder : IRecorder
{
int seed;
public ModelUpdatingRecorder(int seed)
{
this.seed = seed;
}
public void Add(int value)
{
seed += value;
}
}
And registered with:
container.Register(Component.For<IRecorder>().ImplementedBy<NotifyingRecorder>());
container.Register(Component.For<IRecorder>().ImplementedBy<ModelUpdatingRecorder>());
Resolving an IRecorder
will never work here, since ModelUpdatingRecorder
has a non-optional dependency. I cannot use a static dependency since seed
is not known at compile-time.
Is there a way to specify the seed
parameter at runtime and have the decoration still work?
This code sample is a simplification of my scenario, but the idea is the same. I have decorators, and the lowest one relies on a specific value/instance to be provided to it.
Upvotes: 1
Views: 1524
Reputation: 23747
I've found a solution that I believe is the way this should be done. Down in the innards of Windsor the DefaultDependencyResolver
has a method it uses to resolve sub-dependencies (such as a decorated instance of IRecorder
above) called RebuildContextForParameter
. It calls this to create a new context to use when resolving the dependency (i.e. the parameter to the constructor). The method is:
/// <summary>This method rebuild the context for the parameter type. Naive implementation.</summary>
protected virtual CreationContext RebuildContextForParameter(CreationContext current, Type parameterType)
{
if (parameterType.ContainsGenericParameters)
{
return current;
}
return new CreationContext(parameterType, current, false);
}
The false
parameter in CreationContext
constructor is propagateInlineDependencies
, which when true will copy over the current
context's AdditionalArguments
, thereby passing down the parameters to the sub-dependencies.
To flip this false
to true
, create a new class that derives from DefaultDependencyResolver
:
public class DefaultDependencyResolverInheritContext : DefaultDependencyResolver
{
protected override CreationContext RebuildContextForParameter(CreationContext current, Type parameterType)
{
if (parameterType.ContainsGenericParameters)
{
return current;
}
return new CreationContext(parameterType, current, true);
}
}
Then use that when creating the Windsor container:
var kernel = new DefaultKernel(
new DefaultDependencyResolverInheritContext(),
new NotSupportedProxyFactory());
var container = new WindsorContainer(kernel, new DefaultComponentInstaller());
The NotSupportedProxyFactory
and DefaultComponentInstaller
are the defaults when using the parameter-less constructors for DefaultKernel
and WindsorContainer
.
When done, the code above will work when a factory is used to create an IRecorder
, i.e.:
// during type registration/bootstrapping
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IRecorder>().ImplementedBy<NotifyingRecorder>());
container.Register(Component.For<IRecorder>().ImplementedBy<ModelUpdatingRecorder>());
container.Register(Component.For<IRecorderFactory>().AsFactory());
Where IRecorderFactory
is:
public interface IRecorderFactory
{
IRecorder Create(int seed);
}
Then this will work as expected:
IRecorderFactory recorderFactory = container.Resolve<IRecorderFactory>();
IRecorder recorder = recorderFactory.Create(20);
recorder.Add(6);
Hopefully that helps others!
Upvotes: 1
Reputation: 14962
You could wrap the seed in an interface (ISeedHolder
?) and register it with a singleton lifestyle. Then use the interface in your ModelUpdatingRecorder
instead of the raw int. Unless your seeds may need to be parallelized it should allow you to set the seed and resolve it when constructing the ModelUpdatingRecorder
public interface ISeedHolder
{
int Seed {get;set;}
}
public class ModelUpdatingRecorder : IRecorder
{
int seed;
public ModelUpdatingRecorder(ISeedHolder seedHolder)
{
this.seed = seedHolder.Seed;
}
Would this solution achieve what you need?
Upvotes: 0