Metadata Store
The Windows Presentation Foundation (WPF) Designer for Visual Studio framework decouples design-time metadata from implementation. Separating metadata from runtime code is an important design principle for the following reasons.
Building turn-around and integration logistics between teams can make compiling metadata into framework code cumbersome.
Compiling metadata into the runtime code prevents external tools, such as the WPF Designer or Expression Blend, from modifying that metadata later. This is a key issue for agility. Without decoupling design-time metadata from code, Visual Studio cannot version its designers without requiring a new version of the .NET Framework.
Compiling metadata into the runtime significantly increases the size of the runtime assembly. The design time attributes also slow down the runtime. Runtime features, such as data binding, which uses reflection, are affected as additional attributes are loaded into memory.
Design-time metadata provides the "personality" of the designer. The features of a designer are largely tied to the application which hosts it, not the runtime. WPF Designer and Expression Blend use different sets of metadata to provide a feature set that is targeted at a specific type of user.
The Metadata Store
The metadata store is a storage location for design-time metadata. The API for the metadata store is simple. You add a table of metadata attributes by calling the AddAttributeTable method. When a table is added to the metadata store, the attributes defined in that table become available through TypeDescriptor queries. If a type has already been queried, and the table contains additional attributes for this type, a Refreshed event is raised to report that the type’s metadata has changed.
The Attribute Table
An attribute table is essentially a read-only dictionary, but the keys and values are computed separately. It is efficient to query an attribute table for attributes of a particular type. The actual set of attributes is created on demand. You call the GetCustomAttributes method to retrieve the custom metadata for a particular type.
An attribute table supports only a type's properties. An attribute table does not support attributes on fields or methods.
The Attribute Table Builder
To create an attribute table, you start by creating an instance of the AttributeTableBuilder class. You add metadata to the attribute table builder by calling the AddCustomAttributes overloads. When you are finished adding metadata, you produce an attribute table from the attribute table builder by calling the CreateTable method. The attribute table builder methods support callback delegates, so the creation of the attribute table can be deferred until needed.
Custom Attribute Creation
The metadata store relies on the fact that custom attributes have a correctly defined override for their TypeId property. The metadata store uses the TypeId property to determine whether two attributes of the same or different type should be treated as the same instances.
The base Attribute class defines the TypeId property as follows.
public class Attribute
{
...
public virtual object TypeId
{
get
{
return base.GetType();
}
}
...
}
This implementation makes two instances of the same Attribute type appear as the same attribute. One of the instances will be ignored by the default TypeDescriptor implementation. If this is not the desired behavior of a custom attribute, as is the case with the FeatureAttribute class, the custom attribute must override the TypeId property to return a unique object for each type instance. For example, the FeatureAttribute class overrides the TypeId property by using the following code.
public override object TypeId
{
get { return this; }
}
Because this represents a unique object for each object instance, FeatureAttribute can safely decorate the same class multiple times and produce the desired result when it is used in conjunction with the metadata store.
Naming Convention for Metadata Assemblies
Design-time code is deployed in special metadata assemblies. Design-time features that are supported by all designers are deployed in an assembly with ".Design" appended to the main library's name. Design-time features that are supported by Visual Studio only are deployed in an assembly with ".VisualStudio.Design" appended to the main library's name. The following table shows example names for a runtime control library named CustomControlLibrary.dll.
Designer |
Design-time Assembly Name |
---|---|
Visual Studio only |
CustomControlLibrary.VisualStudio.Design.dll |
Expression Blend only |
CustomControlLibrary.Expression.Design.dll |
All designers |
CustomControlLibrary.Design.dll |
Loading Metadata Assemblies
When the designer loads a runtime assembly, the designer also searches for corresponding metadata assemblies. If the corresponding metadata assemblies are found, they are loaded immediately after the runtime assembly is loaded.
When you add a new assembly reference to your project, all corresponding metadata assemblies are searched for and loaded if found.
Metadata assemblies are reloaded when they are rebuilt.
Note
*.Design.dll metadata assemblies are loaded before the designer-specific *.VisualStudio.Design.dll and *.Expression.Design.dll assemblies. Designer-specific metadata overrides shared metadata.
Metadata Assembly Search Order
The following search order applies to assemblies that are directly referenced by the project.
The designer searches the same folder as the referenced runtime assembly. This location is found by using the same algorithm that the build uses to find the assembly, which includes searching the SDK folders and additional paths.
The designer searches for a "Design" subfolder in the folder where the control’s runtime assembly is located.
Although a control’s runtime assembly can be loaded out of the global assembly cache (GAC), the reference is always to a location outside the GAC. Often this location is in the SDK folder. The WPF Designer uses Visual Studio APIs to find a referenced assembly on the file system, even when the project HintPath is not specified. The designer attempts to load the metadata assembly from the location where the control’s runtime assembly is referenced, not from where the control’s runtime assembly is loaded.
Indirectly referenced assemblies are loaded because they are referenced from an assembly that is referenced by the project. For example, if you have a project with a reference to assembly MyAssembly, and MyAssembly has a reference to MyOtherAssembly, which is not referenced directly by the project, then MyOtherAssembly is considered to be indirectly referenced.
In this case, the assembly is not required for the build, and the build system does not find the location of the indirectly referenced assembly in the file system. The following table shows how the designer loads indirectly referenced assemblies.
Referenced Assembly |
Search Procedure |
---|---|
File loaded from the GAC |
SDK folders are searched for the corresponding metadata assembly. If this assembly is found, its path and its "Design" subfolder are used to search for any corresponding metadata assemblies. |
File loaded from a location outside the GAC |
The runtime assembly's path and its "Design" subfolder are searched for corresponding metadata assemblies. |
Finding an IRegisterMetadata implementation
Metadata assemblies must contain one or more implementations of the IRegisterMetadata interface. The IRegisterMetadata implementation is found by using reflection. If multiple IRegisterMetadata implementations exist in an assembly, each is instantiated and called in the order returned by the reflection API.
See Also
Reference
Microsoft.Windows.Design.Metadata