Building the Castle Windsor Dependency Injection Populator for ASP.NET vNext
As I blogged about previously, I built a Castle Windsor populator for ASP.NET vNext. For this post, I’ll walk through some of the more interesting pieces of it.
Installer
In Castle Windsor, an installer is a class that implements IWindsorInstaller
and is used to register all components. It’s not necessary, but it encapsulates the registrations and allows for more organization if you have a ton of services to register. For the Castle Windsor populator, I set up a KServiceInstaller
to register the components.
The KServiceInstaller
has a constructor that takes IEnumerable<IServiceDescriptor>
, which is the list of services to be registered.
public KServiceInstaller(IEnumerable<IServiceDescriptor> services)
When Install
is called on the installer, it loops through each service and registers it with Windsor, using either an ImplementationType
or ImplementationInstance
.
ImplementationType vs ImplementationInstance
The IServiceDescriptor
interface exposes ImplementationType
and ImplementationInstance
properties. If the type being registered is a singleton and has a specific instance that is already instantiated then the service descriptor include an ImplementationInstance
. If the type will be instantiated by the DI container, then the service descriptor includes an ImplementationType
. Performing a null
check will allow you to figure out which one to use, and they should be registered with the Windsor Instance
or ImplementedBy
registration methods, respectively.
if (service.ImplementationType != null)
{
container.Register(Component.For(service.ServiceType)
.ImplementedBy(service.ImplementationType)
.ConfigureLifeCycle(service.Lifecycle));
}
else
{
container.Register(Component.For(service.ServiceType)
.Instance(service.ImplementationInstance)
.ConfigureLifeCycle(service.Lifecycle));
}
Lifestyle
There are 3 LifeCycles specified in the vNext DI framework:
public enum LifecycleKind
{
Singleton,
Scoped,
Transient
}
These map to the LifestyleSingleton
, LifestyleScoped
and LifestyleTransient
life cycles in Windsor. Singleton, obviously, means we have one and only one, use it everywhere. Transient means give me a new one every time I ask for one and Scoped means give me a new one per scope, and inside that scope use the same one everywhere. The scope set up here is per web request, so it creates a new one on each request and it is used for the life of the request.
A simple extension method makes it easy to configure lifecycles:
internal static ComponentRegistration<object> ConfigureLifeCycle(
this ComponentRegistration<object> registration, LifecycleKind kind)
{
switch (kind)
{
case LifecycleKind.Scoped:
registration.LifestyleScoped();
break;
case LifecycleKind.Singleton:
registration.LifestyleSingleton();
break;
case LifecycleKind.Transient:
registration.LifestyleTransient();
break;
}
return registration;
}
And it’s used as such:
container.Register(Component.For(service.ServiceType)
.ImplementedBy(service.ImplementationType)
.ConfigureLifeCycle(service.Lifecycle));
FallbackLazyComponentLoader
There are many services in the vNext framework that are instantiated and registered prior to registration of the MVC services and any custom services you use in your app. These aren’t known and won’t be registered by Windsor, so we’ll need to fallback to another IServiceProvider
if a request comes in for the service. That’s where the FallbackLazyComponentLoader
comes in.
Castle Windsor includes the ability to acquire components as they’re needed, on the spot without registering them first. This is exposed via the ILazyComponentLoader
interface, which is what FallbackLazyComponentLoader
implements. It exposes a single constructor taking an IServiceProvider
:
public FallbackLazyComponentLoader(IServiceProvider provider)
If Windsor encounters a request for a service that is not registered, it will fallback and attempt to resolve it using this fallback service provider:
public IRegistration Load(string name, Type service, IDictionary arguments)
{
var serviceFromFallback = _fallbackProvider.GetService(service);
if (serviceFromFallback != null)
{
return Component.For(service).Instance(serviceFromFallback);
}
return null;
}
WindsorServiceProvider
To use a custom DI framework, you must register a new IServiceProvider
. The Windsor populator returns a WindsorServiceProvider
which can be registered with the framework:
private class WindsorServiceProvider : IServiceProvider
{
private IWindsorContainer _container;
public WindsorServiceProvider(IWindsorContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
return _container.Resolve(serviceType);
}
}
When a request for a service comes in, Windsor will attempt to resolve it. If it isn’t explicitly registered it checks the fallback service provider, and if that fails it returns null
.