Hosting The .NET Composition Primitives
Hosting The .NET Composition Primitives
1 Contents
2 Introduction .......................................................................................................................................... 3 2.1 3 Unity Example ............................................................................................................................... 4
Composition Primitives ......................................................................................................................... 4 3.1 3.2 3.3 3.4 3.5 ComposablePart ............................................................................................................................ 5 ExportDefinition ............................................................................................................................ 5 ImportDefinition ........................................................................................................................... 6 ComposablePartDefinition ............................................................................................................ 6 ComposablePartCatalog................................................................................................................ 6
4 5
The Role of Programming Models ........................................................................................................ 7 Attributed Programming Model ........................................................................................................... 7 5.1 5.2 Attributed Definitions ................................................................................................................... 7 Consuming the Attributed Programming Model .......................................................................... 7
6 7
High-Level Integration Points ................................................................................................................ 8 Exchanging Exports ............................................................................................................................... 8 7.1 7.2 Executing a Constraint .................................................................................................................. 9 ContractBasedImportDefinition.................................................................................................... 9 ContractName ....................................................................................................................... 9 RequiredTypeIdentity ......................................................................................................... 10 RequiredMetadata .............................................................................................................. 10 Special Cases when interchanging with Types and Names................................................. 10
Standardizing Part Manipulation rather than Object Graph Wiring ................................................... 11 Required Host Behavior ...................................................................................................................... 12 9.1 Compliance with ImportDefinitions ............................................................................................ 12
ComposablePart Lifecycle ........................................................................................................... 12 Full Composition of Prerequisite Dependencies ......................................................................... 12 Required Creation Policy............................................................................................................. 13 Part Creation Policy ..................................................................................................................... 13 Recomposition ............................................................................................................................ 13 Host-Defined Behavior .................................................................................................................... 13 Lazy Deep Object Graph Instantiation ........................................................................................ 13 Circular Dependency Detection .................................................................................................. 13 Rejection ..................................................................................................................................... 13 Defaults and Cardinality .............................................................................................................. 14
2 Introduction
The .NET Framework version 4.0 includes a set of types for general component-oriented development. The lowest-level types, known as the Composition Primitives, represent: Component instances capable of being wired together Component definitions, with rich metadata support Common query interfaces for component catalogs
Layered upon the Composition Primitives is a Type-based component construction library referred to as the Attributed Programming Model. The features of this model are: Attributes that allow regular .NET types to be marked-up as component definitions Types that implement the Composition Primitives by reading, instantiating and manipulating attributed types Component catalogs that discover and load attributed component definitions from a variety of sources
Together with the CompositionContainer included in the .NET Framework, the Attributed Programming Model is a complete composition system:
Hosting
Attributed Model
Primitives
Figure 1 .NET Composition Architecture There are many composition systems available on the .NET Framework. Examples include: Inversion of Control (IoC) containers that support decoupled application architectures XAML readers that instantiate a textual description of an object graph Domain-specific frameworks like Windows Workflow Foundation and Windows Communication Foundation that use a fixed set of concepts to support a family of scenarios Plug-in frameworks that allow applications to be extended by third parties Programming languages that wire up and manipulate objects in an imperative manner
Common to all of these frameworks is an ability to take units of software that were developed independently and use them together to achieve a goal.
There is a great deal in common between most composition frameworks, so the Attributed Programming Model and especially the Composition Primitives are built in such a way that their capabilities can be used without CompositionContainer. The Attributed Programming Model and CompositionContainer focus on application extensibility, of the kind typically supported by plug-in frameworks. This whitepaper is a guide to incorporating features of the Attributed Programming Model and Composition Primitives into composition systems with different primary goals.
3 Composition Primitives
The Composition Primitives are .NET types found primarily in the System.ComponentModel.Composition.Primitives namespace. The role of the Composition Primitives is to specify components that can be wired together to create useful software.
3.1 ComposablePart
The central type in the Composition Primitives is ComposablePart. A ComposablePart is a live, executing software component instance.
ComposableParts provide their capabilities to other components as exports and consume the
capabilities of other components through imports. Each ComposablePart describes its own imports and exports in its ImportDefinitions and ExportDefinitions collections. The actual objects represented by the imports and exports can be exchanged with the ComposablePart using its SetImport and GetExportedObject methods.
3.2 ExportDefinition
An ExportDefinition is a structure comprising a string-based ContractName and a dictionary of additional information called Metadata.
Each ExportDefinition attached to a ComposablePart describes an individual capability of the part. For example, a part providing a parser for both C# and VisualBasic.NET source code would probably represent this through two separate ExportDefinitions. The ContractName would most likely be parser in both cases, but each would use metadata to further elaborate on the programming language able to be parsed.
3.3 ImportDefinition
ImportDefinition describes a dependency.
It includes a function called Constraint that selects ExportDefinitions based on the requirements of the import. For example, an import constraint for a C# parser component might look like:
(ExportDefinition ed) => ed.ContractName = parser && ed.Metadata[language] == C#
Because Constraint is a Linq expression that can be examined at runtime, it is not always necessary for a host to execute the expression in order to work out which exports should be provided. This scenario is discussed below in relation to the ContractBasedImportDefinition type. Linq is an unambiguous language for specifying arbitrarily complex dependency requirements. It excels in this role because requirements to be precisely tested through constraint execution when necessary.
3.4 ComposablePartDefinition
The kinds of ComposablePart that can be created in a given system are described using ComposablePartDefinitions. A ComposablePartDefinition is a factory for one kind of ComposablePart. New instances of a ComposablePart can be created using the CreatePart method:
ComposablePartDefinition consoleLoggerDefinition = var consoleLogger = consoleLoggerDefinition.CreatePart(); ComposablePartDefinitions describe the ComposableParts that they create through the ImportDefinitions, ExportDefinitions and Metadata properties. These will match the same
3.5 ComposablePartCatalog
ComposablePartDefinitions are grouped into ComposablePartCatalogs, which can optimize the
When this type is read by the Attributed Programming Model it will be converted into a ComposablePartDefinition with a single ExportDefinition in its ExportDefinitions collection. This ExportDefinition will have a ContractName equivalent to CompositionServices.GetContractName(typeof(IShape)). When CreatePart is executed on this ComposablePartDefinition, an underlying instance of Square will (logically) be created. When GetExportedObject is used to obtain the IShape export, the Square instance will be returned. Attributes like Export exist in the Attributed Programming Model for declaring a full range of exports, imports and metadata items.
DirectoryCatalog
When one of the above catalog types is instantiated in such a way that the attributed definitions are discovered, these will appear as ComposablePartDefinitions in the ComposablePartCatalog.Parts property. The attributes and the types that expose them are completely encapsulated in the ComposablePartDefinition instances that the catalogs provide. Attempting to map between Type and ComposablePartDefinition is strongly discouraged.
7 Exchanging Exports
ComposableParts express dependencies as queries that select ExportDefinitions. These queries are
predicates attached to ImportDefinitions. Hosts must satisfy imports using values that match the ImportDefinitions predicate. The values can be the exports of other parts, or host-provided services. In either case, the host must have an understanding of what is represented by an ImportDefinition.
7.2 ContractBasedImportDefinition
Many imports are simple queries based on three aspects of the ExportDefinition:
Equality of ContractName Equality of RequiredTypeIdentity Subset of Metadata keys
These import definitions can be expressed as a ContractBasedImportDefinition . Hosts aware of this type can test import definitions and cast to the more specific type in order to gain access to these common data items:
var cbid = importDef as ContractBasedImportDefinition; if (cbid != null) { Console.Write(ContractName={0}, cbid.ContractName); Console.Write(RequiredTypeIdentity={0}, cbid.RequiredTypeIdentity); foreach (var requiredKey in cbid.RequiredMetadata) Console.WriteLine(Requires {0}, requiredKey);
7.2.1
ContractName
ContractNames are unique string values that describe the role of an exported or imported item.
The ContractName value in a ContractBasedImportDefinition must be matched using string equality with the ContractName property on the ExportDefinition being tested. An example value may be MyCompany-AppName-ToolBarItem.
ContractName alone does not indicate compatibility between an exporter and an importer additional
requirements of the importer may be expressed in the RequiredTypeIdentity and RequiredMetadata values. 7.2.2 RequiredTypeIdentity RequiredTypeIdentity represents a requirement of the importer that the exported value will be able to be cast to a specific type. The constraint of a ContractbasedImportDefinition will match the RequiredTypeIdentity value with the Metadata[RequiredTypeIdentityMetadataName] string value on the ExportDefinition.
RequiredTypeIdentity is generated using the CompositionServices.GetTypeIdentity method. This
value alone will not be sufficient to load a System.Type instance for the intended type. If mapping to a Type is required either: scan known types to create a lookup from type identity to Type; or, use the ImportDefinition.Metadata[ImportTypeAssemblyQualifiedName] value as a hint.
7.2.3 RequiredMetadata The string values here specify the keys that must appear in the ExportDefinition.Metadata dictionary. The meaning of such keys is specific to the importer. 7.2.4 Special Cases when interchanging with Types and Names Many host environments locate services using a key base on Type, name, or a combination of the two. When ContractName and RequiredTypeIdentity are the same string value, this can be regarded as a default contract name: implementations can infer that type only is significant. Hosts that allow a null name selector may regard this as the equivalent in the Composition Primitives. Unity Example The import definition containing:
ContractName = MyNamespace.IMyInterface ExportedTypeIdentity = MyNamespace.IMyInterface
When RequiredTypeIdentity is null, only ContractName is relevant. Hosts can return implementations of any type that is allowed by the documentation for the particular ContractName.
Unity Example Unity does not generally support the concept of named instances without an associated type. A basic mapping is made so that such requests assume a registration for System.Object. The import definition containing:
ContractName = Sender.Timeout ExportedTypeIdentity = null
extensions use a common set of APIs (e.g. the Attributed Programming Model) but target a specific host environment, with which they are tested
2. 3. 4. 5. 6. 7.
implementation with new Satisfaction of prerequisite dependencies by calling SetImports Access to exported objects by calling GetExportedObject is now allowed, but the objects themselves cannot be used until the part is activated Satisfaction of non-prerequisite dependencies by calling SetImports Completion of the activation process by calling OnComposed Exported objects from the part can now be used Disposal with IDisposable.Dispose
9.6 Recomposition
If an import definition is not marked recomposable (IsRecomposable == true) then SetImports may only be called for that import until the first time OnComposed is called.
10 Host-Defined Behavior
Below is a list of behavioral features of the CompositionContainer type. These are regarded as host responsibilities and do not need to be implemented consistently by other hosts of the Composition Primitives.
10.3 Rejection
The Export.GetExportedObject method through which parts access imports should always return a value or throw an exception. Exceptions in such circumstances are rarely recoverable. Hosts should strive not to provide exports to a part that cannot be accessed. If the host supports lazy composition, maintaining this behavior requires deep dependency analysis. Hosts that cannot do deep analysis to determine whether an export can be satisfied should favor eager composition.