There has been a lot of discussion on the blogosphere about Inversion of Control (usually abbreviated as IoC) containers in the Java community and more recently in the .NET community. Its been facinating to see how many fans of this technology have come out of the wood work. Think of this as my coming out . . .
As many of you would know, there are all kinds of approaches to acheiving IoC, but they are broadly broken up into two categories, those where the container gives you your dependencies (called dependency injection), and those where you ask the container for your dependencies (a “service locator” pattern). I won’t bother going into a discussion about the pros and cons of each one here – Martin Fowler did a fantastic job of that – and whats the web for if you can’t create a link!
If you clicked through some of the above links you might be left thinking that IoC is strictly a Java affair, in reality nothing could be further from the truth. Daniel Cazzulino pointed out that .NET has one of the many IoC approaches baked right into the framework. In fact I would go so far as to say that without IoC the most popular .NET development tool around (sorry Don, its not Emacs, its VS.NET) absolutely depends on it.
The particular approach that .NET takes to IoC is called the “service locator pattern” and I found stumbled across it when I was doing some component development a few years ago and I needed to integrate with the VS.NET environment at design-time. VS.NET relies of the service locator pattern to allow components to acquire “services” (really just object instances).
Those services allow the component to interact and do things like manipulate the toolbox window, change the contents of the property grid – just about anything. The whole infrastructure depends on three simple interfaces, IComponent, ISite and IContainer.
The way it works is that you build or use a component that ultimately implements the IComponent interface. You insert that component into a container which implements the IContainer interface. As part of the insertion process the component is “sited”. What that means in practical terms is that the component is interfaces with the container through an intermediate object called a “site” which implements the ISite interface. I find this diagram is a good way to explain the relationship.
Now, the ISite interface doesn’t really do much for you except give a named relationship between the component and its container and ideally allow the component to query whether it is in design mode or not (ever wondered why Microsoft produces such kick arse development tools? Its because they factored the design-time experience right into the platform).
The ISite interface also implements the IServiceProvider interface. This is where all the magic starts. The IServiceProvider interface defines one method called GetService(…) which takes a single Type argument as its parameter. The Type allows the component (which has access to its site through the Site property defined on IComponent) to get a service of a particular type.
How the site finds the service really depends on the site implementation but typically it means passing the request to the container and letting it deal with it. Because the container doesn’t have a GetService(…) method this implies a certain amount of tight coupling between the container and its site – thats deliberate, it allows the container which is hosting the component to set up its own rules about the relationship. Note also that many components have a protected GetService(…) method. This is not officially part of the pattern but helps components grab a service without the programmers having to grab the site first – a minor saving in keystrokes.
The whole idea of component-based programming is to allow developers to assemble applications using a set of components layer upon layer to get the job done. The service locator pattern in .NET supports this by allowing you to create a network of sited components which, all of which can access the services provided by their containers.
So what is a service really? Well, like I said its just an object instance. But typically they are keyed on an instance of a Type object. So lets say I called GetService(typeof(IFooService)), I would expect to get an object back that implements the IFooService interface. This is in fact one of the nice side effects of IoC – it encourages contract-based programming which can potentially make it easier to substitute in alternative implementors of interfaces to support testability.
There is one issue with the implementation of the service locator pattern in .NET. Even though Windows Forms controls are components (they ultimately implement IComponent), they don’t seem to get sited properly. Instead you are expected to climb through your list parent controls to find one that is sited and then call GetService(…).
I only found out about this limitation the other day because I was primarily focused on building UI-less components, not controls. I thought that it was just a bug in VS2003, but it appears that it is still a problem in VS2005. I have logged a bug on LadyBug if you want to go and vote on it. It seems like a fairly trivial thing to fix, if not, I’d love to know why.
If you want to understand more about that particular issue I suggest you read the comments from this post on Brian Pepins blog. In the next day or so I will tidy up the code from a component that I wrote that you can drop onto a Form that corrects the issue and allows Windows Forms controls to interact with the service locator pattern. I’ll also follow up with a few posts that show some of the interesting uses for the service locator pattern at runtime – I might call the piece “Beyond the Movie Finder” . . .