Metadata Store

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

The 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(AttributeTable) 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.

  1. 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.

  2. 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

MetadataStore

AttributeTable

AttributeTableBuilder

FeatureAttribute

Other Resources

WPF Designer Extensibility