Okay, so here's how I understand IoC and DI in Web API to work when using Castle Windsor.
Note, though, that my confidence that I understand it as I should, though, falls somewhere between my confidence I could best Dennis Rodman in a one-on-one basketball game and my confidence in his sanity.
Another way to put it is to say that I have spent the last several days reading up on IoC/DI/Castle Windsor with Web API and experimenting with it, but I still feel like wearily responding, "All down but nine, pard; set 'em up on the other alley."
I have several more specific questions on Stack Overflow about it, such as this one, but am not yet much clearer about it.
So I'm going to "talk out loud" and hope that somebody responds with something that will refine my understanding, shed some light on this opaque (to me) subject, or help unravel this Gordian knot.
Point: The basis of IoC/DI lies in passing interfaces to constructors, so that the class with the constructor does not have to instantiate its own objects; in fact, has no need to know the concrete component/class.
Point: To set this up, you need code like so in your Controllers:
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository is null");
}
_deptsRepository = deptsRepository;
}
Point: Some class that implements IDepartmentRepository is passed to the DepartmentsController constructor
Point: The coder doesn't do that explicitly - the IOC container (Castle Windsor, in this case) does that "automatically" by intercepting the Web API routing mechanism with its own, as with code like this, in global.asax.cs:
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator), new WindsorCompositionRoot(_container));
Point: global.asax.cs' Application_Start() gets the ball rolling and calls the Installer/Registrar with code such as:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigureWindsor(GlobalConfiguration.Configuration);
}
public static void ConfigureWindsor(HttpConfiguration configuration)
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
_container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
var dependencyResolver = new WindsorDependencyResolver(_container);
configuration.DependencyResolver = dependencyResolver;
}
Point: Lots of other Castle Windsor scaffolding has to be set up for this. Specifically, classes need to be "installed" (registered - I think registered would have been a better/more easily grokkable term for it).
This is one of my major "say what?"/headscratching areas. I don't know if that registration code should look like this:
public object GetService(Type serviceType)
{
return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (!_container.Kernel.HasComponent(serviceType))
{
return new object[0];
}
return _container.ResolveAll(serviceType).Cast<object>();
}
...or like this:
public class ApiControllersInstaller : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly() // should it be Types instead of Classes?
.BasedOn<ApiController>()
.LifestylePerWebRequest());
}
}
...or like this:
public class ServiceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
Component.For<IExpenseRepository>().ImplementedBy<ExpenseRepository>().LifestylePerWebRequest(),
Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository>().LifestylePerWebRequest(),
Component.For<IInventoryRepository>().ImplementedBy<InventoryRepository>().LifestylePerWebRequest(),
Component.For<IItemGroupRepository>().ImplementedBy<ItemGroupRepository>().LifestylePerWebRequest());
}
}
...or elsewise...
Point: Once all this is set up, and the Controllers (and Repositories? Is there more that do I have to about them, to make them safe for CW?) have been registered by the Castle Windsor routing engine, a call to my Web API app like:
http://platypus:28642/api/Departments
...will be resolved to a "component" (concrete class) that implements IDepartmentRepository. CW will be able to figure that out, as long as I have one class that implements IDepartmentRepository, that that is the one to silently pass to the Controller's constructor which takes an arg for a class that implements IDepartmentRepository.
But...what if there are are N classes that implement IDepartmentRepository? How does CW know which one to pass to the Controller's constructor? How can I, the lowly human in this meeting of silicon and pink squishy stuff, designate which of the N I want CW to pass to the constructor?