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
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:
Collection | Object type | Objects Have IDs |
---|---|---|
Commands | Command | yes |
EventTopics | EventTopic | yes |
Items | any type (Object) | yes |
Services | any type (Object) | no |
SmartParts | any type with [SmartPart] attribute | yes |
WorkItems | WorkItem | yes |
Workspaces | IWorkspace | yes |
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".