Introduction
Typed Factory Facility provides automatically generated Abstract Factories
that you can use to create components in your code, while still remaining agnosting to the presence of the container. It is part of MicroKernel's dll, which means it does not require any additional assemblies, and works in scenarios where MicroKernel is used alone (without Windsor).
All the following code samples will assume that the following variable is in scope
IKernel kernel = new DefaultKernel();
Why would I need it?
Preferable way of working with the container is to have the container inject all the required dependencies upon creation of the object. There are some scenarios where an object needs to have other objects supplied to it after it was created, for example to handle some external event. In this case the object needs to pull its dependencies. Best way to do it is to use a factory that will supply the objects as requested. Typed Factory Facility implements the factories for you (under interface you provide) and supplies instances to you from the container.
Basic usage from code
To use the facility you have to register it with the container
Registering the facility
You need the appropriate namespace in scope:
using Castle.MicroKernel.Facilities.TypedFactory;
Then you can register the facility with the container
kernel.AddFacility<TypedFactoryFacility>();
Registering factories
Having the facility registered, we can register our factories
kernel.Register( Component.For<IDummyComponentFactory>() .AsFactory() );
AsFactory is an extension method defined in facility's namespace.
Factory requirements
Not just any type may be used as typed factory. The following requirements must be met:
- The type must be an interface
- None of its methods can have out parameters.
Using typed factories
You use of typed factories much like you would use any other service. There are three kinds of methods it treats specially:
- methods with no return value (having 'void' as return type)
- methods with return value (having something different than 'void' as return type)
- Dispose method (if typed factory implements IDisposable)
| Lifetime and releasing components Remember that by default all components in Windsor are singletons and that using default release policy container keeps reference to all components, even transient ones. That's why it's important to release components via typed factory or to change release policy. Also pay attention to lifetime you assign to components resolved via the facility. |
We will now go over them in turn.
Resolving methods
Methods with non-void return type are used for supplying components to the caller. Say we register a component and a factory that will be used to create it.
kernel.Register(
Component.For<IDummyComponent>()
.ImplementedBy<Component2>()
.Named("SecondComponent")
.LifeStyle.Transient,
Component.For<IDummyComponentFactory>()
.AsFactory());
now we can resolve the factory:
var factory = kernel.Resolve<IDummyComponentFactory>();
and someplace else in the code we can use the factory to give us the component we need
var component = factory.GetSecondComponent();
Resolving with arguments
You can also use methods that take parameters from the caller to resolve components. The argument you pass in, will be passed on to the container's resolution pipeline.
kernel.Register( Component.For<ICalendarFactory>().AsFactory(), Component.For<ICalendar>().ImplementedBy<Calendar>().LifeStyle.Transient );
Now you can resolve the factory
var calendarFactory = kernel.Resolve<ICalendarFactory>();
And use it passing some arguments in.
var today = DateTime.Today; var calendar = calendarFactory.CreateCalendar(today); Debug.Assert(calendar.Today == today);
|
Specifics about how method being invoked and its arguments are bound to resolved component are determined by an implementation of ITypedFactoryComponentSelector interface. By default the facility uses DefaultTypedFactoryComponentSelector (discussed below) but you can supply your own to customize its behavior. |
Releasing methods
By default MicroKernel and Windsor track disposable non-singleton components. That means that if you resolve a component from the container you should also release it as soon as you're done using it, to allow Garbage Collector to reclaim memory and resources occupied by it. How do you do it when you're using Typed Factory? With Releasing methods.
Releasing methods are counterparts to Resolving methods. They release components resolved by the latter. Any "void method" other than Dispose is a releasing method (actually Dispose is a releasing method as well, just a very special one). It tries to release all objects passed to them as parameters.
Releasing example
Given you resolved a component from a typed factory:
var component = factory.Create();
You can then pass it as an argument to any of factory's void methods to have the factory release the component from the container
factory.Destroy(component);
You can also pass more components at once, and the facility will release them in turn.
Dispose
When your typed factory interface implements IDisposable it gains a powerful ability - disposing the factory releases all components created via the typed factory
var factory = kernel.Resolve<IDisposableFactory>(); var component = factory.Create(); Debug.Assert(component.Disposed == false);
Now you can dispose the factory, and all non-singleton components will be released as well.
factory.Dispose();
Debug.Assert(component.Disposed == true);
Mapping calls to typed factory to kernel's arguments
Typed Factory Facility uses implementation of ITypedFactoryComponentSelector interface to decide how information it received from the factory (factory Resolution method and its arguments) should be mapped to information forwader to the container to perform actual resolution of the component. The facility comes with one implementation DefaultTypedFactoryComponentSelector but if you need custom behavior you can supply your own
DefaultTypedFactoryComponentSelector
DefaultTypedFactoryComponentSelector obeys few conventions
'Get' methods lookup components by name
When you have a factortymethod named GetSomething selector will ask the container for a service named Something.
kernel.Register(
Component.For<IDummyComponent>()
.ImplementedBy<Component2>()
.Named("SecondComponent")
.LifeStyle.Transient
);
Now if we have a method called GetSecondComponent:
var component = factory.GetSecondComponent();
Call to the factory's method is direct equivalent of calling:
var component = kernel.Resolve<IDummyComponent>("SecondComponent");
non-'Get' methods lookup by type
For all other methods, the return type of the method is used as type of component to look-up, so call to factory method:
var component = factory.CreateSecondComponent();
is direct equivalent of calling: (assuiming IDummyComponent is the return type of the method)
var component = kernel.Resolve<IDummyComponent>();
Method parameters are forwarded to the caller by name
When factory method has parameters, their names and values are forwarded to the kernel, so call to the following typed factory method:
IComponent CreateComponent(string componentName, int someParameter);
var component = factory.CreateComponent("foo", 3);
is direct equivalent of calling:
var component = kernel.Resolve<IComponent>(new Dictionary<string, object>{{"componentName", "foo"}, {"someParameter", 3}});
Custom ITypedFactoryComponentSelectors
// TODO
XML configuration
// TODO
