Pregunta

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?

¿Fue útil?

Solución

I have experience with Ninject, not Castle Windsor, but the principles should be the same.

To understand dependency injection, it helps to recall how you wrote code without it. Typically, the DepartmentsController constructor would look like something like this:

public DepartmentsController()
{
    _repository = new DepartmentRepository();
}

The controller depends on the repository, and by placing it in the constructor we ensure that every DepartmentsController is instantiated with a repository and begins its life in a valid state. By doing this, however, we make it more difficult to later swap one implementation of DepartmentRepository for another. In particular, if we want to isolate the DepartmentsController and test it independently, we're in trouble. How do we test this class without invoking actual calls to the database through DepartmentRepository?

Your first example improves on this scenario by creating a "seam" between the objects: a point where the two are glued or stitched together that can be separated easily, especially for tests. Instead of creating the DepartmentRepository itself, your controller can ask for it:

public object GetService(Type serviceType)
{
    return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}

This is called the Service Locator pattern, and some consider it bad practice. By exposing Castle Windsor in this way, you have actually lost some of its benefits. In particular, your DepartmentsController now depends on the Service Locator and its GetService method. I won't cover all of the supposed disadvantages of Service Locators, but consider what happens when your application grows and you now have numerous classes (potentially all of them) dependent upon this single class.

Your third example improves upon the Service Locator pattern by making the container invisible to your class:

public DepartmentsController(IDepartmentRepository repository)
{
    if (repository == null)
        throw new ArgumentNullException("repository");

    _repository = repository;
}

Notice how this version has no knowledge of the kernel / container or a service locator. Instead, we have made its true dependencies explicit in the constructor rather than the means to those dependencies. In fact, by writing your classes this way, you can construct entire class libraries that are ignorant of Castle Windsor and will work perfectly fine with Ninject, another DI framework, or no container at all, without modifying the code.

How you actually register your components (the difference between examples two and three) is partly a matter of scale.

container.Register(
        Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
        Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
        Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
...

is fine for a smaller application. If the number of classes grows, you have to stomach a never ending list of bindings or find a better method. One improvement is to favor convention over configuration: if Castle Windor assumes every IDeliverItemRepository has a DeliveryItemRepository implementation, the bindings above can be skipped. However, you haven't solved anything if most of your classes require exceptional configuration; you still have a long, omniscient kernel / container registration method.

Instead, you should probably explore something like your second example. This way, your ConfigureWindsor method can use reflection to find any IWindsorInstallers present in your application without knowing about specific interfaces or implementations. You can break all of your bindings into separate modules (Ninject's name for this concept) that are discovered at runtime.

To address your last question: Castle Windsor won't know which of your N implementations to choose without you telling it. You can accomplish this through a mixture of the concepts discussed above: primarily by convention, but with IWindsorInstallers when needed to specify important details like lifecycle and conditional bindings.

Otros consejos

CW knows which on N classes implement IDepartmentRepository via reflection. It knows which one you want because you registered it - that's the point of registering. It's kind of smart though, it looks at the constructors of what you've registered and tries to deduce the concrete types based on what you have registered. So sometimes registering the top of a dependency tree will be enough to let it figure out the rest.

There are multiple ways to register, for your convenience (and confusion). Using a fluent API for your convenience (and confusion). You also can control things about the life-cycle of dependencies (imagine a dependency needs to call dispose(), but the class getting the dependency didn't create it and can't know about it's life cycle - a more advanced topic though).

You should register the concrete classes you want, then resolve them in your Main method (in your case Application_Start serves this purpose), this will create all your objects in the dependency graph right away. Call release on the container when you are done. Don't call the container anywhere else in the app.

I hope that helps - there were a lot of questions.

I personally liked Dependency Injection in .NET as a reference (no affiliation with me), but it isn't free.

Licenciado bajo: CC-BY-SA con atribución
scroll top