Container

Applications based on the Composite Application Library are composites that potentially consist of many loosely coupled modules. They need to interact with the shell to contribute content and receive notifications based on user actions. Because they are loosely coupled, they need a way to interact and communicate with one another to deliver the required business functionality.

To tie together these various modules, applications based on the Composite Application Library rely on a dependency injection container. Dependency injection containers can reduce the dependency coupling between objects by providing the facility to instantiate instances of classes and manage their lifetime based on the configuration of the container. During the objects creation, the container injects any dependencies that the object has requested into it. If those dependencies have not yet been created, the container creates and injects them first. In some cases, the container itself is resolved as a dependency. For example, modules often get the container injected, so they can register their views and services with that container.

There are several advantages of using a container:

  • A container removes the need for a component to have to locate its dependencies or manage their lifetimes.
  • A container allows swapping the implementation of the dependencies without affecting the component.
  • A container facilitates testability by allowing dependencies to be mocked.
  • A container increases maintainability by allowing new components to be easily added to the system.

Note

For an introduction to dependency injection and inversion of control, see the article Loosen Up - Tame Your Software Dependencies for More Flexible Apps by James Kovacs.

In the context of an application based on the Composite Application Library, there are specific advantages to a container:

  • A container injects module dependencies into the module when it is loaded.
  • A container is used for registering and resolving presenters and views.
  • A container creates presenters and presentation models and injects the view.
  • A container injects the composition services, such as the region manager and the event aggregator.
  • A container is used for registering module-specific services, which are services that have module-specific functionality.

Note

The Stock Trader Reference Implementation (Stock Trader RI) and the QuickStarts rely on the Unity Application Block as the container. The Composite Application Library itself is not container-specific, and you can use its services and patterns with other containers, such as Castle Windsor, StructureMap, and Spring.NET.

The following code shows how injection works. When the PositionModule is created by the container, it is injected with the regionManager and the container. In the RegisterViewsAndServices method, which is called from the module's Initialize method, when the module is loaded, various services, views, and presenters are registered.

public PositionModule(IUnityContainer container, IRegionManager regionManager)
{
    _container = container;
    _regionManagerService = regionManager;
}

public void Initialize()
{
    RegisterViewsAndServices();
    ...
}

protected void RegisterViewsAndServices()
{
    _container.RegisterType<IAccountPositionService, AccountPositionService>(new ContainerControlledLifetimeManager());
    _container.RegisterType<IPositionSummaryView, PositionSummaryView>();
    _container.RegisterType<IPositionSummaryPresentationModel, PositionSummaryPresentationModel>();
    ...
}

Using the Container

Containers are used for two primary purposes, namely registering and resolving.

Registering

Before you can inject dependencies into an object, the types of the dependencies need to be registered with the container. Registering a type involves passing the container an interface and a concrete type that implements that interface. There are primarily two means for registering types and objects: through code or through configuration. The specific means vary from container to container.

Typically, there are two ways of registering types and objects in the container through code:

  • You can register a type or a mapping with the container. At the appropriate time, the container will build an instance of the type you specify.
  • You can register an existing object in the container. The container will return a reference to the existing object.
this.container.RegisterType<IEmployeesController, EmployeesController>();

In the preceding code example, you can see how the EmployeesController is registered with the IEmployeesController interface using the first aforementioned approach. For an example of entering configuration information through the Unity container, see Entering Configuration Information.

Types registered with the container have a lifetime. This can be either singleton (a single instance for the container, so each time you resolve you get the same instance) or instance (each time you resolve you get a new instance). The lifetime can be specified at registration or through configuration. The default lifetime depends on the container implementation. For example, the Unity container registers services as instances by default.

Resolving

After a type is registered, it can be resolved or injected as a dependency. When a type is being resolved, and the container needs to create a new instance, it will inject the dependencies into these instances.

In general, when a type is resolved, one of three things will happen:

  • If the type has not been registered, the container will throw an exception.

    Note

    Some containers will allow you to resolve a concrete type that has not been registered.

  • If the type has been registered as a singleton, the container will return the singleton instance. If this is the first time the type was called for, the container will create it and hold on to it for future calls.

  • If the type has not been registered as a singleton, the container will return a new instance and will not hold on to it.

The following code example shows where the EmployeesPresenter is being resolved by the container.

EmployeesPresenter presenter = this.container.Resolve<EmployeesPresenter>();

The EmployeesPresenter constructor contains the following dependencies, which are injected when it is resolved.

public EmployeesPresenter(IEmployeesView view, IEmployeesListPresenter listPresenter, IEmployeesController employeeController)
    {
        this.View = view;
        this.listPresenter = listPresenter;
        this.listPresenter.EmployeeSelected += new EventHandler<DataEventArgs<BusinessEntities.Employee>>(this.OnEmployeeSelected);
        this.employeeController = employeeController;
        View.SetHeader(listPresenter.View);
    }

Considerations for Using the Container

You should consider the following before using containers:

  • Consider whether it is appropriate to register and resolve components using the container:
    • Consider whether the performance impact of registering in the container and resolving instances from it is acceptable in your scenario. For example, if you need to create 10,000 polygons to draw a surface within the local scope of a rendering method, the cost of resolving all of those polygon instances through the container might have a significant performance cost because of the container's use of reflection for creating each entity.
    • If there are many or deep dependencies, the cost of creation can increase significantly.
    • If the component does not have any dependencies or is not a dependency for other types, it may not make sense to put it in the container.
  • Consider whether a component's lifetime should be registered as a singleton or instance:
    • If the component is a global service that acts as a resource manager for a single resource, such as a logging service, you may want to register it as a singleton.
    • If the component provides shared state to multiple consumers, you may want to register it as a singleton.
    • If the object that is being injected needs to have a new instance of it injected each time a dependent object needs one, register it as a non-singleton. For example, each Presentation Model needs a new instance of a view.
  • Consider whether you want to configure the container through code or configuration:
    • If you want to centrally manage all the different services, configure the container through configuration.
    • If you want to conditionally register specific services, configure the container through code.
    • If you have module-level services, consider configuring the container through code so that those services are only registered if the module is loaded.

More Information

For information related to containers, see the following:

For background information about concepts that are important to understanding the Composite Application Guidance, see the following topics:

Home page on MSDN | Community site