Click or drag to resize
DigitalRuneService Location and Inversion of Control

The DigitalRune Base library provides a light-weight Inversion of Control (IoC) container for use in .NET applications and games.

Important note Important

Note that the ServiceContainer is contained in a separate assembly (DigitalRune.ServiceLocation.dll) because it has a dependency on a third-party assembly (Microsoft.Practices.ServiceLocation.dll).

Tip Tip

More general information about the service provider design pattern can be found in the following article: Service Provider Pattern

This topic contains the following sections:

The service container

The ServiceContainer is a fast and efficient Inversion of Control (IoC) container which can be used in .NET Framework, Windows Presentation Foundation (WPF), Silverlight, Windows Phone 7, and Xbox 360 applications.

IoC is a software design principle based on the idea that components should not directly create or access dependent objects ("services") to avoid tight coupling. Instead, components should only access services through abstracted interfaces. At runtime the components can acquire services by using a service locator, or the services are supplied automatically by an IoC container through Dependency Injection (DI). IoC facilitates software reuse, loose coupling, and easy testing of software components.

This documentation is not meant to be an introduction on inversion of control. Instead, it explains how the DigitalRune ServiceContainer can be used. To find out more about Inversion of Control (IoC), Dependency Injection (DI), Service Location, and related concepts see

  • "Inversion of Control" in Wikipedia: Link
  • "Inversion of Control Containers and the Dependency Injection pattern" by Martin Fowler: Link
Service registration

Services are usually abstracted using interfaces. This ensures that components do not have a hard dependency on an actual implementation. Implementations can be exchanged without requiring a change or rebuild of the component. For example:

C#
// A random service...
interface IService1
{
}

// ... and its implementation.
class Service1 : IService1
{
}

Services can be registered in the IoC container using one of the Register-methods.

C#
var container = new ServiceContainer();

// Register service by type. 
// (If required, the service container creates a new service instance using 
// reflection.)
container.Register(typeof(IService1), null, typeof(Service1));

// Register service instance. 
// (The service container will always return the specified instance.)
var service2 = new Service2();
container.Register(typeof(IService2), null, service2);

// Register a service using a factory method.
// (If required, the service container will create a new instance using the
// factory method.)
container.Register(typeof(IService3), null, container => new Service3());

The first parameter is the service type, the type for which the service is registered. The service type is usually a .NET interface, but it can be a class type as well.

The second parameter is the name under which the service is registered. The name is a string, which can be null. If the name is null, the service instance is considered the "default instance". Only one default instance can be registered for any given service type. But when using different names, multiple service implementations can be registered for any given service type. For example:

C#
// Register multiple implementations by name. 
container.Register(typeof(IService1), "Implementation1", typeof(ServiceImpl1));
container.Register(typeof(IService1), "Implementation2", typeof(ServiceImpl2));
container.Register(typeof(IService1), "Implementation3", typeof(ServiceImpl3));

The Register-methods have additional overloads to customize when/how a new service is created and when a service instance is disposed (if it implements IDisposable). See CreationPolicy and DisposalPolicy for more informations.

Registered default services

The only service that is registered by default is the service container itself.

C#
// In each ServiceContainer, the ServiceContainer itself is registered by default.
// The ServiceContainer is available as IServiceProvider, IServiceLocator, or 
// ServiceContainer. 
// FYI: These are calls used to register the container:
container.Register(typeof(IServiceProvider), null, container => container, CreationPolicy.LocalShared, DisposalPolicy.Manual);
container.Register(typeof(IServiceLocator), null, container => container, CreationPolicy.LocalShared, DisposalPolicy.Manual);
container.Register(typeof(ServiceContainer), null, container => container, CreationPolicy.LocalShared, DisposalPolicy.Manual);
Service resolution

There are various ways to consume a registered service.

Service location

The IoC container can be used as a service locator. Components that depend on a certain service can acquire the service using one of the GetInstance-methods.

C#
// Resolve service (default instance).
IService1 service1 = container.GetInstance<IService1>();

// Resolve named service.
IService1 service1 = container.GetInstance<IService1>("Implementation1");

// Resolve all named services of type IService1.
IEnumerable<IService1> service1 = container.GetAllInstances<IService1>();

Constructor injection

Services or components that depend on other services can get the required services via constructor injection. For example, consider the following service which depends on two other services.

C#
class DependentService
{
  public DependentService(IService service1, IService2 service2)
  {
    ...
  }
}

The IoC container can create a new instance of the given type and automatically resolve its dependencies.

C#
// Create a new instance using constructor injection.
// (Note: IService1 and IService2 need to be already registered.)
container.Register(typeof(DependentService), null, typeof(DependentService));
var dependentService = container.GetInstance<DependentService>();

// Alternatively, if the DependentService is not registered in the container:
var dependentService2 = (DependentService)container.CreateInstance(typeof(DependentService));

Note that by default the ServiceContainer uses a greedy strategy for constructor injection: When creating a new instance, the IoC container looks for the constructor with the most arguments and tries to resolve the arguments from within the container.

Property injection

Dependencies can also be defined via properties. For example:

C#
class ComponentXyz
{
  // Dependencies:
  public IService1 Service1 { get; set; }
  public IService2 Service2 { get; set; }

  ...
}

These dependencies can then be resolved using property injection:

C#
// Create new instance.
var component = new ComponentXyz();

// Resolve the dependencies using property injection.
container.ResolveProperties(component);

When ResolveProperties is called the IoC container will examine all public properties and try to resolve all missing objects.

Important note Important

Constructor injections is applied automatically when a new instance is created by the ServiceContainer using reflection. However, property injection is not applied automatically. The method ResolveProperties needs to be called explicitly.

Libraries and inversion of control

As a general rule, a library should not register any services in the ServiceContainer. Services should only be registered by the caller of a library. If a library expects a specific service, this needs to be documented explicitly. In addition, libraries also need to document whether they expect a global IoC container in ServiceLocatorCurrent.

The DigitalRune Engine libraries (such as DigitalRune Animation, DigitalRune Game, DigitalRune Physics, etc.) do not register any services in the ServiceContainer and they do not require any registered services.

Compatibility

The ServiceContainer is yet another IoC/DI container among many other implementations on the Internet. The class ServiceContainer implements the interface IServiceLocator. This interface is defined in the Commmon Service Locator library (Microsoft.Practices.ServiceLocation.dll, from Microsoft patterns and practices). This means that the container shares a common interface with many other IoC/DI container implementations.

Applications or frameworks should access services only through the IServiceLocator interface. This ensures that the code consuming the services is independent from the actual IoC/DI container. The ServiceContainer can be replaced by another IoC/DI container, or the code can be reused in a different context where a different IoC/DI container is used.

Making the service locator available

Code that requires a specific service needs have access to the IServiceLocator. There are several strategies for making the IServiceContainer available.

Using a global IServiceLocator

The IServiceLocator can be made globally available using the static class ServiceLocator.

C#
var container = new ServiceContainer();

// Register services.
container.Register(typeof(IService1), null, typeof(Service1));
...

// Make the service container globally available.
ServiceLocator.SetLocatorProvider(() => container);

// Now, any component can access the global service locator. For example,
var service1 = ServiceLocator.Current.GetInstance<IService1>();

Passing the IServiceLocator

Some developers may have concerns using a global service locator. Instead, the IServiceLocator instance can simply be passed from the caller to all components.

See Also