Lightweight Containers and Plugin Architectures: Dependency Injection and Dynamic Service Locators in .NET

Note: this entry has moved.

Required reading: Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler. If you haven't read it, you will not understand what I'm talking about, and I'm not fond of reproducing other's work here. It's better if you just read it, it's a very interesting article.

I'd like to analyze Fowler's article in the light of .NET and what we have now in v1.x. After reading his article, he seems to imply that lightweight containers are a new concept mainly fuelled by the Java community unsatisfied with heavyweight EJB containers. It turns out that .NET supported and heavily used this approach since its very early bits, released back in PDC'00 (July 14th 2000).

The basic building blocks for lightweight containers in .NET live in the System.ComponentModel namespace. Core interfaces are:

  • IContainer: the main interface implemented by containers in .NET.
  • IComponent: provides a very concrete definition of what a component is in this context. It's any class implementing this interface, and which can therefore participate in .NET containers.
  • ISite/IServiceProvider: the former inherits the later. It provides the vital link between a component and the container it lives in (its site), which enables service retrieval by the component. IComponent has a Site property of this type.
  • IServiceContainer: a default container for services, that IComponents can access through the ISite. Not actually required as the IContainer can store available services through other means.

The relationship between these clases can be depicted as follows:

Diagram showing interfaces relationship

A typical container/component/service interaction is:

  1. Specific container class is created by client code.
  2. Container initializes all services that components will have access to (optionally through an internal IServiceContainer). Client can optionally (if the container exposes its own IServiceContainer ) add further services at will.
  3. Either the container or client code adds components.
  4. Container "sites" these components by setting their IComponent::Site property, with an ISite implementation that offers services that are retrieved from the optional internal IServiceContainer or another implementation.
  5. Client code can access components by name or index through IContainer indexer.
  6. Components can perform actions requiring services that are retrieved through IComponent::Site::GetService(Type). This method is inherited from IServiceProvider actually.

So, in Fowler's terms, the IContainer implementation performs Interface Dependency Injection (through IComponent) of a single dependency, the Dynamic Service Locator (ISite : IServiceProvider). The former happens because the IContainer automatically sets the IComponent::Site property upon receiving the component to add through its IContainer::Add(IComponent) method. The later is the implementation of IServiceProvider::GetService(Type) method, which allows dynamic retrieval of services from the container.

Diagram of call sequence for IContainer/IComponent/ISite interaction

Fowler dislikes dynamic service locators because he says they rely on string keys and are loosely typed. In .NET IServiceProvider, you don't pass a string key but the actual Type of the service you request. What's more, the default interface implementation, System.ComponentModel.Design.ServiceContainer, checks that services published with a certain Type key are actually assignable to that type. Therefore, it's safe to cast them back upon retrieval. At most you get a null value from a provider, but never an InvalidCastException.

Following his example so that this is a natural adaptation to the .NET world, his MovieLister component will look like the following:

public class MovieLister : Component { public void Movie[] MoviesDirectedBy( string name ) { IMovieFinder finder = (IMovieFinder) GetService( typeof( IMovieFinder ) ); if ( finder == null ) return new Movie[0]; ArrayList all = finder.FindAll(); // Iterate, filter by name and return subset. }

It's common, that instead of implementing IComponent directly, concrete components inherit from its built-in default implementation Component, which provides a GetService shortcut method that also checks that the Site property is set before requesting a service from it.

Lifecycle of components is handled through three states:

  • IComponent::ctor: at construction time, the component is still not ready for work, as it can't access services.
  • IComponent::Site { set; }: the component is "sited", therefore it's fully functional now. At this (overridable) point, components can further configure themselves, for example by caching a reference to a service they use frequently: public class MovieLister : Component, IMovieLister { public override ISite Site { get { return base.Site; } set { base.Site = value; // Cache the finder. this._finder = (IMovieFinder) GetService( typeof(IMovieFinder) ); } }
  • IComponent::Dispose: when it's not needed anymore, a component may be diposed using the IDispose interface inherited by IComponent.

Fowler notes some drawbacks in general with regards to the service locator approach:

So the primary issue is for people who are writing code that expects to be used in applications outside of the control of the writer. In these cases even a minimal assumption about a Service Locator is a problem.
By standarizing on IComponent and ISite from System.ComponentModel, this isn't a problem anymore in .NET. Any component that uses these interfaces can be hooked into any container, and query services. This doesn't require dependencies on external products or unproven approaches: .NET uses extensively this feature.
Since with an injector you don't have a dependency from a component to the injector, the component cannot obtain further services from the injector once it's been configured.
As injection is being done for the service locator itself (the ISite : IServiceProvider instance), this isn't a problem anymore. Further services can be easily requested from it.
A common reason people give for preferring dependency injection is that it makes testing easier. The point here is that to do testing, you need to easily replace real service implementations with stubs or mocks. However there is really no difference here between dependency injection and service locator: both are very amenable to stubbing. I suspect this observation comes from projects where people don't make the effort to ensure that their service locator can be easily substituted.

I agree completely on this view. Aren't these architectures all about the ability to dynamically remove dependencies/hook/replace implementations dynamically? It's obvious to me that if such objective is not achieved, it's clearly not because injection vs service locator choice, but an implementation bug. Testing and stubbing with .NET containers is straightforward as components retrieve services by interface type, so stub impls. of those interfaces can be plugged into a testing IContainer implementation without problems.

Note that additionally, the IContainer can expose its internal IServiceContainer as yet another service, so that a component could publish a new service for consumption by others:

public class MovieLister : Component, IMovieLister { public override ISite Site { get { return base.Site; } set { base.Site = value; // Publish ourselves as new service. IServiceContainer container = (IServiceContainer) GetService( typeof(IServiceContainer) ); container.AddService( typeof(IMovieLister), this ); } }

This combination of IServiceProvider, IContainer and IComponent is in broad usage TODAY in Win and Web Forms platforms, as well as design-time and generally the IDE infrastructure. You're usin them everytime you create a Windows Form, Windows User Control, WebForm, etc.

Layering Service Containers

One scenario that .NET System.ComponentModel supports and that hasn't even been discussed by Fowler is that of chained service containers. Let's say you have a component, sited in a container, that performs some quite complex functionality. Now, let's say this complex functionality requires additional services that are provided by a specialized container and further components. In this case, the "main" component needs to instantiate a new container and execute further components. Needless to say, these components may require not only services from this new "child" container but also the parent one, the one where the "main" component lives.

Stacking service providers at this point is extremely useful. What you actually need is a Chain of Responsibility pattern where the service implementation is returned by the first provider in the chain that can respond to the request for it. This would allow you not only to chain different sets of services, but also to override implementations from a parent service provider. This is supported natively in .NET through the ServiceContainer implementation, and is heavily used in Visual Studio.NET IDE: some services are offered to components by a specific designer, or a VS package, or the IDE itself. Most requests for services propagate up the chain if necessary until they reach the IDE main container.

I've used exactly the same architecture for an upcoming automatic wizard framework for Shadowfax that acts as a child container inside the IDE. Some components need execution of yet another lower layer, a transformation engine that works with code templates to generate code (among other things), which is also a child container. At this point, the three layers, IDE, wizard and transformation engine, are chained together, so any component in the transformation engine, for example, can query services that are being offered by the IDE itself.

This is an extremely powerful and flexible approach, as you don't have to build monolythic container but can instead rely on components instantiating more specialized child containers to perform specific work.

Container Configuration

Of course, any good container should be configurable either programmatically and through configuration files. Fowler discusses the following with regards to configuration:

I often think that people are over-eager to define configuration files. Often a programming language makes a straightforward and powerful configuration mechanism. Modern languages can easily compile small assemblers that can be used to assemble plugins for larger systems. If compilation is a pain, then there are scripting languages that can work well also.

I agree absolutely. One usual dual config mechanism (XML + API) in .NET is creating an XSD for the file, get classes generated ready for XML Serialization, and support config either through the file reference, which is simply deserialized into the object model generated for the XSD, or through this object model itself, like so:

public class MyConfigurableContainer : <code>IContainer</code> { MyConfigurableContainer(string file) { ... } MyConfigurableContainer(MyConfigModel config) { ... } }

However, unless codegen customization is used, this raw XML serialization model is very poor when it comes to programmatic configuration, as classes only have parameterless ctors (so all initialization has to be done through property setters), there's no way to know which properties are required or optional, by default multi-value properties are arrays instead of typed collections, and so on.

Non-language configuration files work well only to the extent they are simple. If they become complex then it's time to think about using a proper programming language.

It's very interesting how most people nowadays perceive programmatic configuration APIs as a drawback over XML config files. I can't really understand why. With dynamic compilation becoming almost common place (i.e. ASP.NET v2 model, upcoming XAML, and so on), having a good programmatic API coupled with full programming language intellisense surely surpasses XML files in usability and productivity, specially for complex stuff.

It's usually the case (i.e. most of .NET) that after inventing a huge daunting configuration file format, admin UIs are created to manipulate them (i.e. .NET Framework Configuration, upcoming ASP.NET v2 admin console, etc.). At this point you start wondering: if nobody is ever going to touch those files except from those UIs, which is the advantage of having it in XML? Why don't just have those UIs generate compiled controllers that programmatically hook and configure everything? Just imagine the savings in parsing, validating, loading time... After all, you have to XCopy deploy those configs, just like the "assemblers" would...

The missing feature?

So, all the plumbing and required interfaces for implementing lightweight containers in .NET are already in-place. The framework doesn't contain any class to perform configuration of a container, though. This is not necessarily a bad thing, as it doesn't force any concrete file format or configuration API, leaving that to implementers. Creating such feature for an specific container (such as the Shadowfax Wizard container, or the transformation engine - code-named T3 for Templated Text Transformations) is almost trivial. Reading config, loading types, hooking services and components and that's it.

So, once more, we can see that .NET is the pioneer on supposedly "new" patterns. It's true that this pattern (and many others found throughout the .NET Framework) don't have enough advertising, and that may be the cause for their scarse use in .NET application architectures.

In a future post I'll discuss Apache Avalon and the Spring Framework, and how they compare to what's built-in .NET.

Update: maybe I should also mention that I've been using this tecnique with excelent results since the initial release of an opensource XML-based code generator back in Nov-2002

8 Comments

  • This is awesome....great post.

  • Thanks Daniel, we need more posts like this. There many uncovered great things about .NET that need to be communicated.

  • [quote] due to an apparent lack of foresight, IServiceContainer interface alone can't be used as the container for services, even when it's the natural choice as it allows addition and removal of services. The reason is that it lacks the GetService(Type) method that exists in IServiceProvider, so you can administer services but not retrieve them. [/quote]



    Dear sir, actually, IServiceContainer is itself an IServiceProvider, by definition (through interface inheritance), see? :)



    Anyway, great post! Thanks Daniel. :)

  • What an unforgivable mistake! You're definitely right!

  • Useful for us,thanks

  • Technically, this framework isn't an example of the &quot;Dependency Injection&quot; pattern. It's an example of &quot;(Dynamic) Service Locator.&quot; Spring and Pico are dependency injectors.



    The components in the System.ComponentModel container are required to lookup/locate the services they depend on. Thus instead of having their dependents injected by the container, the components ask the container for the dependents -- service locator.



    But technically dependency injection, and component creation and configuration in general is outside the scope of this framework anyways (or what I've seen so far). I assume you have to roll your own creation and configuration semantics. Only then could you throw in some dependency injection.



    --choy

  • It actually *IS* dependency injection. What's being injected is the dynamic service locator, that is, the ISite.

    If you read carefully Fowler's article, on the section &quot;Using both a locator and injection with Avalon&quot;, you'll that it's exactly this same approach.

  • The pattern described here has even a longer history than July 2000. It already existed in Microsoft's Visual J++ 6.0 as part of its Windows Foundation Classes (WFC) and was released somewhere in 1997 (if I remember).

Comments have been disabled for this content.