Anatomy of a WorkItem

This document is a part of the "Inside CAB Dependency Injection" article. It is relatively self-contained, but you still may find it useful to read the whole article from the beginning.

Work Item is CAB's IoC Container

We discuss the structure of a CAB/SCSF work item with focus on dependency injection, or inversion of control (IoC). WorkItem is, among other things, CAB's IoC container. Graphically, the structure of work item may be represented like this:

WorkItem Anatomy

WorkItem class defines a number of public properties that behave like collections.

CAB dependency injection mechanism is activated when you add objects to one of these collections by using their AddNew() or Add() methods.

class WorkItem
{
    public ManagedObjectCollection<Command> Commands { get; }
    public ManagedObjectCollection<EventTopic> EventTopics { get; }
    public ManagedObjectCollection<object> Items { get; }
    public ServiceCollection Services { get; }
    public ManagedObjectCollection<object> SmartParts { get; }
    public ManagedObjectCollection<WorkItem> WorkItems { get; }
    public ManagedObjectCollection<IWorkspace> Workspaces { get; }
}

List of WorkItem Collections

Work item collections differ by the type of objects they may hold and whether the objects in the collection may have string IDs or not:

CollectionObject type Objects Have IDs
CommandsCommandyes
EventTopicsEventTopicyes
Itemsany type (Object)yes
Servicesany type (Object)no
SmartPartsany type with [SmartPart] attributeyes
WorkItemsWorkItemyes
WorkspacesIWorkspaceyes

WorkItem Collections Are Not Real

WorkItem collections don't store their objects. They act as filtered views, or facades for an underlying collection called "locator". The locator is accessible via the protected property InnerLocator. Since this property is not public, only derived classes may access the locator directly.

To be exact, there is one real collection inside the work item: UIExtensionSites. It is not a facade for the locator, and it does store its objects, but it does not participate in the dependency injection.

Building Objects with WorkItem

All work item collections have similar interfaces. You build new objects with dependency injection by calling AddNew() method on one of the collections. You may also inject dependencies to existing objects by calling Add() method. The collections are publicly accessible, so adding new objects can be done inside or outside of the work item class:

class MyWorkItem : WorkItem
{
    protected override void OnRunStarted()
    {
        base.OnRunStarted();
        Items.AddNew<TypeToBuild>(); // using Items collection
        OutsideClass.SomeMethod(this);
    }
}

class OutsideClass
{
    static void SomeMethod(WorkItem workItem)
    {
        workItem.Services.AddNew<SomeServiceType>(); // using Services collection
    }
}

AddNew vs. Regular New

AddNew() is very different from regular new. It will dynamically look for appropriate constructors and properties and inject dependencies as necessary. The dependencies are taken from the locator. One of the objects in the locator is the work item itself:

class MyWorkItem : WorkItem
{
    protected override void OnRunStarted()
    {
        base.OnRunStarted();
        Items.AddNew<TypeToBuild>();
        new TypeToBuild(); // will not work!
    }
}

class TypeToBuild
{
    public TypeToBuild(WorkItem workItem)
    {
        // this constructor is called by AddNew()
        // workItem parameter is set to the creating work item, in this case a MyWorkItem
    }
}

The Locator

Unlike the collection facades, the locator is not a "linear" collection, but an object-to-object dictionary, or map. Theoretically, the locator keys may be of any type, but usually they are of type DependencyResolutionLocatorKey, which cotains a Type and a string ID. This allows to look-up objects by type and/or by string ID. The locator is implemented by the Locator type in the Microsoft.Practices.ObjectBuilder assembly.

The Lifetime Container

The locator uses weak references to the objects it locates (but not to the keys). It means that the objects referenced by the locator may be garbage collected if they are not referenced anywhere else. In theory it allows the locator to reference "dead" objects, but in the CAB as it built today it does not actually happen (or so I think). Besides the collector, the work item contains a special collection of type LifetimeContainer that holds strong references to all objects inside the work item. This prevents the objects inside work item from being garbage collected. When an object is removed from the work item, it is removed from both the locator and the lifetime container.

ManagedObjectCollection<TItem>

All collections except Services are implemented by the ManagedObjectCollection<TItem> generic type from Microsoft.Practices.CompositeUI assembly. Services collection has special features, and thus is implemented by a different type ServiceCollection. The rest of this section applies to all collections except Services, that have their own section.

Managed object collection does not store objects on its own. Instead, it relies on the locator and the lifetime container to keep the objects around. Managed object collection presents a filtered view of the locator. The objects may be filtered by type and by special filtering delegate. An additional implicit filter is that the object ID cannot be null. This filtering is not strictly enforced in all cases, which may lead to interesting results. See "CAB IoC Gotchas".

The only managed object collection that actually uses a filter delegate is SmartParts. It filters objects whose types have the [SmartPart] custom attribute. All other collections are filtered only by type. The Items collection can contain objects of any type, so the only remaining filter for Items is that the object ID must not be null.

Note that if you add an object without an ID, the collection will automatically create a dummy ID based on GUID. This ensures that object ID is never null. Another important note is that the objects are always added to the locator using their real run-time type. If you add to the collection an object of type DerivedClass that inherits from BaseClass, it will be put in the locator under DerivedClass. It cannot be registered under any other type.

The Services Collection

Unlike managed objects collection, services collection contains only objects whose ID is null. Thus, services may be distinguished only by type, and the services collection may contain only one object of each type.

Unlike managed object collections, Services allow you to register your object under its real type, or under any of the base types or interfaces it implements. You can register an object multiple times under different types. However, each type can be still registered only once.

Another feature of the Services collection is AddOnDemand(). It will put a placeholder in the locator, and the real object will be built when the service is first requested.

WorkItem Collections Gotchas

Work item collection sometimes behave in surprizing and bizzare ways. These "gotchas" are summarized in a separate document "CAB IoC Gotchas".