I'm letting my IoC container manage the life of my object graph and seem to be reusing a particular lifestyle a lot that is not available in the Castle Windsor default set at the moment.
I need to construct objects context-specifically by passing in resolution-time arguments into a factory and when I pass in the string Dorothy for instance, I would like to get back a singleton of Person that would be instantiated with string Dorothy, if it does not exist in the container yet, or otherwise the existing Dorothy instance. Same when passing the string argument Jane into my factory - please return singleton of Person Jane. And there might be Sally, Bob and many others, the complete set of inputs unravelling at runtime only.
Here's another example, implemented using a dictionary in my custom class:
private readonly static IDictionary<string,IRecruiter> Recruiters
= new Dictionary<string, IRecruiter>();
private IRecruiter GetRecruiter(string recruiterId)
{
IRecruiter recruiter;
if (!Recruiters.TryGetValue(recruiterId, out recruiter))
{
recruiter = this.donorFactory.CreateRecruiter(recruiterId);
Recruiters.Add(recruiterId,recruiter);
}
return recruiter;
}
And where the donorFactory CreateRecruiter method does something like that:
return this.Create<IRecruiter>(new Arguments { { typeof(string), recruiterId }});
I find I use this pattern a lot, so would like to know if it is somehow possible to implement using existing Castle Windsor lifestyles, or maybe there is a case made to add this pattern to Castle Windsor itself?
I can see one way to do it by creating a custom scope accessor:
public class ArgScopeAccessor : IScopeAccessor
{
static readonly ConcurrentDictionary<string, ILifetimeScope> collection = new ConcurrentDictionary<string, ILifetimeScope>();
public void Dispose()
{
foreach (var scope in collection)
scope.Value.Dispose();
collection.Clear();
}
public ILifetimeScope GetScope(CreationContext context)
{
string name = (string)context.AdditionalArguments["name"];
return collection.GetOrAdd(name, n => new DefaultLifetimeScope());
}
}
This creates a scope based on the name
argument passed into the call to resolve the object. I'd then use the typed factory facility to create an IRecruiterFactory
(with a parameter named name
):
public interface IRecruiterFactory
{
IRecruiter Create(string name);
}
And assuming your IRecruiter
and Recruiter
are something like this:
public interface IRecruiter
{
string Name { get; }
}
public class Recruiter : IRecruiter
{
public Recruiter(string name)
{
Name = name;
}
public string Name { get; private set; }
}
Then you could set up your container to use this scope in a reusable way like this:
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IRecruiterFactory>()
.AsFactory());
container.Register(Component.For<IRecruiter>()
.ImplementedBy<Recruiter>()
.LifestyleScoped<ArgScopeAccessor>());
So now, resolving a recruiter by name will resolve the same instance:
IRecruiterFactory recruiterFactory = container.Resolve<IRecruiterFactory>();
IRecruiter jane1 = recruiterFactory.Create("Jane");
IRecruiter susan = recruiterFactory.Create("Susan");
IRecruiter jane2 = recruiterFactory.Create("Jane");
Console.WriteLine("Jane 1: " + jane1.GetHashCode());
Console.WriteLine("Jane 2: " + jane2.GetHashCode());
Console.WriteLine("Susan: " + susan.GetHashCode());
Displays:
Jane 1: 60467532 Jane 2: 60467532 Susan: 63249743
Obviously you can create a different scope accessor to address any of the other ways you want to discriminate on types, such as hash code or a combination of arguments. I don't, however, see a way to change the name
constant in the ArgScopeAccessor
since there's no way to pass a constructor argument in when configuration the scope accessor on the container. But that could be solved with a base type and derived types just specifying the constant.
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.