Mono.Addins Reference Manual

Table of Contents

Introduction

Mono.Addins is a generic framework for creating extensible applications, and for creating libraries which extend those applications.

This framework is derived from the add-in engine used by MonoDevelop, although it has been completely rewritten and improved in many ways to make it more generic and easier to use. The MonoDevelop add-in engine was an improvement over the SharpDevelop engine, which took many ideas from the Eclipse add-in engine.

Mono.Addins has been designed to be useful for a wide range of applications: from simple applications with small extensibility needs, to complex applications (such as MonoDevelop itself) which need support for large add-in structures.

This document includes a detailed description of all features that Mono.Addins provides. If you are interested only in learning what Mono.Addins is about, the document Introduction to Mono.Addins may be more appropriate.

If you have questions or suggestions about Mono.Addins, please subscribe to the Mono.Addins mailing list (http://groups.google.com/group/mono-addins).

Presenting Mono.Addins

The extension model used by Mono.Addins is based on four concepts:

  • Add-in host: an application or library which can be extended by add-ins. Extension possibilities are declared using extension points.
  • Extension point: a placeholder where add-ins can register extension nodes to provide extra functionality. Extension points are identified using extension paths.
  • Extension node: an attribute-decorated element that describes an extension. Extension nodes are typed. Extension points may declare which types of extension nodes do they accept.
  • Add-in: A set of files which register new nodes in one or several extension points defined by add-in hosts. An add-in can also act as an add-in host, and as such it can be extended by other add-ins.

Mono.Addins also defines an Add-in Description Model, which is used by add-ins and add-in hosts to declare all extensibility information. Add-in descriptions can be represented either using an XML manifest, or by applying custom attributes to assemblies and types.

Finally, Mono.Addins provides an API (implemented in Mono.Addins.dll) which can be used at run-time to query and handle add-in extensions

In order to clarify all those concepts, let's see a very simple example of an application based on Mono.Addins.

A simple example

The idea is to implement a Text Editor which can be extended by add-ins. The following diagram shows the extension points that the editor will offer, and how they are extended by several add-ins:

Subsequent chapters of this document will explain how to use Mono.Addins to provide different kinds of extensibility features to the editor. Now let's start with the skeleton of the application and a very simple extension.

We want to allow add-ins to run a custom command at application startup. Add-ins could use it, for example, to subscribe to editor events so they will perform custom actions when something happens, or to do any other kind of initialization work.

The Text Editor application is composed by two assemblies:

  • TextEditor.exe is the editor application.
  • TextEditorLib.dll will define several interfaces to be implemented by extensions

Let's see a basic implementation of those:

ICommand library (TextEditorLib.dll)

public interface ICommand
{
	string Run ();
}

It's a very simple interface with just one method: Run. The editor will call this method to run the custom command.

Add-in host manifest (TextEditor.addin)

<Addin id="TextEditor" version="1.0" isroot="true">
 
	<Runtime>
		<Import assembly="TextEditor.exe"/>
		<Import assembly="TextEditorLib.dll"/>
	</Runtime>
	
	<ExtensionPoint path = "/TextEditor/StartupCommands">
		<ExtensionNode name="Command" type="Mono.Addins.TypeExtensionNode"/>
	</ExtensionPoint>
</Addin>

The host manifest includes the following information:

  • The Addin element declares the host identifier and version. The isroot attribute specifies that the manifest belongs to an add-in host.
  • The Runtime element declares the files that belong to the host.
  • The ExtensionPoint element declares a new extension point. The path attribute specifies the location of the extension point in the extension tree.
  • The ExtensionNode element specifies the type of node that the extension point accepts. In this case, the extension point will only allow nodes of type TypeExtensionNode, which will be identified in extensions using the name Command.

The host application (TextEditor.exe)

public class TextEditorApplication
{
	public static void Main ()
	{
		// Initialize the add-in engine
		AddinManager.Initialize ();
 
		// Get the extension nodes in the extension point
		foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes ("/TextEditor/StartupCommands")) {
			ICommand command = (ICommand) node.CreateInstance ();
			command.Run ();
		}
	}
}

The host application uses the Mono.Addins API to get the nodes registered in the extension point. To do it:

  • Initializes the add-in engine by calling AddinManager.Initialize()
  • Calls the method AddinManager.GetExtensionNodes() to get all nodes registered by add-ins in the StartupCommands extension point. The Call returns nodes of type TypeExtensionNode, since the extension point was defined to accept this kind of nodes.
  • TypeExtensionNode is a special type of node which allows specifying a type. It provides methods for creating instances out of the described type. The host calls the CreateInstance method on each node to get the implementations of ICommand, and then calls Run on each of them.

An add-in (SampleAddin.dll)

class HelloWorldExtension: ICommand
{
	public string Run ()
	{
		Console.WriteLine ("Hello World");
	}
}

This add-in provides a simple implementation of the ICommand interface.

Add-in manifest (SampleAddin.addin)

<Addin>
	<Runtime>
		<Import assembly="SampleAddin.dll"/>
	</Runtime>
	
	<Dependencies>
		<Addin id="TextEditor" version="1.0" />
	</Dependencies>
	
	<Extension path = "/TextEditor/StartupCommands">
		<Command type="HelloWorldExtension" />
	</Extension>
	
</Addin>

The add-in manifest is similar to the host manifest:

  • It has an Addin root element, although it does not provide Id or Version information. Id and version are not required in add-ins unless they declare new extension points that can be extended by other add-ins.
  • The Dependencies element declares dependencies to other add-ins or add-in hosts.
  • The Extension element can be used to declare new extensions. The path property specifies the extension point being extended.
  • The Command element is an extension node. Since nodes of this extension point are of type TypeExtensionNode, extenders must provide a type attribute which specifies the name of the class represented by this node. That's the class that will be created when calling CreateInstance on the node.

A simple example using assembly attributes

The same example can be implemented using attributes instead of an XML manifest. Notice that not all declarations that can be expressed in a manifest can also be expressed using attributes. However, attributes are easy to use and are very handy for simple extensions.

ICommand library (TextEditorLib.dll)

[assembly:AddinRoot ("TextEditor", "1.0")]
 
[TypeExtensionPoint ("/TextEditor/StartupCommands")]
public interface ICommand
{
	string Run ();
}

The AddinRoot attribute can be used to mark an assembly as being an add-in host. The TypeExtensionPoint attribute declares a new extension point in path the specified in the constructor, and requiring nodes of type TypeExtensionNode.

An add-in (SampleAddin.dll)

[assembly:Addin]
[assembly:AddinDependency ("TextEditor", "1.0")]
 
[Extension ("/TextEditor/StartupCommands")]
class HelloWorldExtension: ICommand
{
	public string Run ()
	{
		Console.WriteLine ("Hello World");
	}
}

The Addin attribute indicates that an assembly is an add-in. It can also be used to specify the add-in Id and Version if needed. The AddinDependency attribute declares the dependency on the TextEditor add-in host. The Extension attribute marks a class as being an extension. It will register an extension node of type TypeExtensionNode in the provided path.

The host application can be the same app shown in the manifest example.

Architecture of an Extensible Application

In general, extensible applications are composed by at least one executable assembly and by one library. The library is needed in order to define interfaces or classes to be implemented or subclassed by add-ins. So the most basic architecture would look like this:

Mono.Addins is based on what could be called an strongly typed extension model. In this model, extensible applications need to explicitly declare all extension points they provide, and extension points need to define the type of information they accept. All that extensibility information constitute what we call an add-in root.

Add-in roots

Extensible applications must declare one add-in root, but they can also declare more than one. Defining several add-in roots is useful for applications composed by several modules. In this way it is possible to implement add-ins which target specific modules of the application. For example, an application might implement some logic in one assembly and the GUI in another assembly. In this way it would be possible to build add-ins for extending the logic (with no GUI dependencies), and add-ins for extending the GUI, or add-ins extending both:

In order to allow this kind of architectures, add-in roots must have an unique identifier. Add-ins must then specify the add-in root(s) they extend by referencing this ID. Add-in roots also have a version number, which is used to ensure consistency between the extension points they provide, and the extensions provided by add-ins.

Add-in roots can be bound to one or more assemblies, which can be executables or libraries. Complex applications are usually composed by several assemblies, so it is important to carefully decide what constitutes an add-in root and what not.

For example:

In this example, an application is composed by two executables: a console app and a graphical app. Both executables share the same logic libraries, and the GUI app has a GUI library and a components library. The application defines two add-in roots:

  • The Logic add-in root is composed by two assemblies: Logic.dll and LogicHelper.dll. Since both assemblies are libraries, they can be reused from the console and the gui exes.
  • The GUI add-in root is composed only by the GUI library.

There are basically two reasons for including an assembly in an add-in root:

  • The assembly defines some classes or interfaces to be implemented by add-ins.
  • The assembly is required to load other add-in root assemblies.

Mono.Addins provides several ways of declaring add-in roots and the assemblies they are bound to. This is explained in detail in the Description of Add-ins and Add-in Hosts section, but summarizing, there are three ways:

  • By creating an add-in manifest file (an XML file with a .addin extension).
  • By creating an add-in manifest file and embedding it in an assembly as a resource.
  • By applying the [assembly:AddinRoot] attribute to an assembly.

In the first and second case, a Runtime section in the manifest can be used to reference other assemblies to be included in the add-in root. In the second and third case, other assemblies can be referenced using the attribute [assembly:AddinAssemblyInclude].

Add-in roots always have a main file. When using an standalone manifest, the main file is the manifest file. When using a manifest embedded in an assembly, or when using the [AddinRoot] attribute, the main file is the assembly. Only the main file can include other files in the add-in root.

Add-ins

Add-ins have many similarities to add-in roots, because add-ins can also behave as add-in hosts for other add-ins. So, add-ins can be composed by several assemblies and other files, and they can be described using a standalone manifest, and embedded manifest or by applying the [assembly:Addin] attribute to an assembly.

The following diagram shows an add-in composed by two assemblies, and extended by another add-in:

Add-in roots and add-ins are described using the same Add-in Description Model, but they are handled differently by the add-in engine. Add-in root assemblies are loaded by applications hosts like regular assemblies, while add-ins are dynamically discovered and loaded by the add-in engine.

Extensible libraries

When an add-in root is entirely composed by libraries, what we have in fact is an extensible library. It is important in this case to properly encapsulate the access to extension points in the library. In this way the library will look like any other regular library, and any application will be able to use it like any other library.

Mono.Addins supports having several copies of the same add-in root in different locations. When loading an add-in root, the add-in engine will take care of loading the add-ins that extend that specific add-in root version.

For example, let's say somebody implements a generic library for parsing source code files called NParser. NParser would provide:

  • A common API for parsing files.
  • An extension point to be used by add-ins to add support for new languages.

Any application might be able to use NParser by just linking to it and distributing it as a private assembly, or using it from the GAC. In this case, add-ins for the library would be loaded from the global registry, unless the application specified a different add-in registry.


Description of Add-ins and Add-in Roots

Every add-in and add-in root has to provide a description of the extensibility elements it provides. There are two ways of doing this:

  • Using custom attributes: Mono.Addins provides several attributes which can be used to mark classes as extensions or extension points. Attributes can also be used to declare add-in information and dependencies.
  • Using an add-in manifest: a manifest is an XML description of an add-in. Attributes are very easy to use, but not all extensibility features that Mono.Addins provides can be expressed using attributes. Any declaration that can be done using custom attributes can also be done in a manifest, but not everything that can be declared in a manifest can also be expressed using custom attributes. Manifests can be deployed as standalone files, or they can be embedded as a resource in assemblies. Manifest files are identified by their extension, which must be ".addin" or ".addin.xml".

It is allowed to use a mix of both approaches to describe an add-in. That is, an add-in could declare simple extensions using attributes, and more complex extensions in a manifest embedded as a resource.

As you can see from the previous examples, the description of an add-in root looks similar to the description of an add-in. In fact, from the point of view of the add-in engine, add-ins and add-in roots are almost the same, so they use the same description model.

The following sections describe in detail all information that can be included in an add-in or add-in root description. The term add-in is used generically in this chapter to refer to add-ins and add-in roots, unless specifically differenced.

The add-in header

Every add-in and root has a header which specifies basic information about the add-in or root. In an XML manifest, header information is declared in the root Addin element:

<Addin 	namespace="TextEditor"
	id="Core" 
	version="1.0"
	compatVersion="0.9"
	name="Text Editor"
	description="An extensible text editor."
	author="Some author"
	url="http://some/url"
	defaultEnabled="true"
	isroot="true">
	...
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/@id: The identifier of the add-in. It is mandatory for add-in roots and for add-ins that can be extended, optional for other add-ins.
  • /Addin/@namespace: Namespace of the add-in. The full ID of an add-in is composed by "namespace.name".
  • /Addin/@version: The version of the add-in. Like the id, it is mandatory for add-in roots and for add-ins that can be extended.
  • /Addin/@compatVersion: Version of the add-in with which this add-in is backwards compatible (optional).
  • /Addin/@name: Display name of the add-in.
  • /Addin/@description: Description of the add-in.
  • /Addin/@author: Author of the add-in.
  • /Addin/@url: Url of a web page with more information about the add-in.
  • /Addin/@defaultEnabled: When set to 'false', the add-in won't be enabled until it is explicitly enabled by the user. The default is 'true'.
  • /Addin/@isroot: Must be true if the description belongs to an add-in root.

The [Addin] and [AddinRoot] attributes can also be used to mark assemblies as being add-ins or add-in roots. For example:

[assembly:Addin ("Core", "1.0", Namespace="TextEditor")]

Add-in naming conventions

Looking at the previous example it may seem a bit strange to use the identifier "Core" for an add-in root, however it makes a sense when used together with a namespace. The full ID of an add-in is the concatenation of the namespace and the id, so the full ID would be "TextEditor.Core".

All add-ins extending the an add-in root should use the same namespace. So for example, we would have "TextEditor.XmlAddin", "TextEditor.CompilerService", etc.

The following declaration would be valid for an add-in:

<Addin namespace="TextEditor">
	...
</Addin>

IDs are not mandatory if the add-in can't be extended. Specifying the namespace is also not mandatory, but very convenient to make it clear what's the scope of the add-in.

Files included in an add-in

The Runtime element can be used to declare the set of files that compose the add-in. An add-in can be composed by assemblies and other kind of files (such as image or text files).

<Addin 	namespace="TextEditor" id="Core" version="1.0" isroot="true">
	...
	<Runtime>
		<Import assembly="TextEditor.exe" />
		<Import assembly="TextEditorLib.dll" />
		<Import file="license.txt" />
		...
	</Runtime>
	...
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/Runtime: Contains the list of files.
  • /Addin/Runtime/Import/@assembly: Declares that an assembly belongs to the add-in.
  • /Addin/Runtime/Import/@file: Declares that a file belongs to the add-in.

It is important to properly declare all files used by an add-in. For example, when a type from the add-in is required (e.g. an ICommand implementation), only assemblies declared in this section will be checked. This information is also used by setup tools to know exactly what needs to be packaged when creating an add-in package.

Dependencies of an add-in

Add-ins must explicitly declare dependencies on add-in roots or other add-ins. Dependencies are declared in the Dependencies element:

<Addin namespace="TextEditor">
	...
	<Dependencies>
		<Addin id="Core" version="1.0" />
		...
	</Dependencies>
	...
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/Dependencies: Contains the list of dependencies.
  • /Addin/Dependencies/Addin: Declares a dependency.
  • /Addin/Dependencies/Addin/@id: Identifier of the extended add-in.
  • /Addin/Dependencies/Addin/@version: Version of the extended add-in.

Notice that although the header does not specify an ID for this add-in, it does specify a namespace. When a namespace is declared, IDs specified in dependencies will be prefixed with that namespace. So, the previous example would be equivalent to:

<Addin>
	...
	<Dependencies>
		<Addin id="TextEditor.Core" version="1.0" />
		...
	</Dependencies>
	...
</Addin>

Dependencies can also be declared using the [AddinDependency] custom attribute:

[assembly:AddinDependency ("TextEditor.Core", "1.0")]

Extension Points

Extension points can be declared by add-in roots or by add-ins which may be extended by other add-ins. Let's see how an extension point can be declared using an example which would allow extending the toolbar of our Text Editor application:

<Addin 	namespace="TextEditor" id="Core" version="1.0" isroot="true">
	...
	<ExtensionPoint path="/TextEditor/ToolbarButtons" name="Main toolbar">
		<Description>Main toolbar of the editor. Extensions can register here new buttons.</Description>
		<ExtensionNode name="ToolButton" type="TextEditor.ToolButtonNode">
			<Description>Registers a new button in the toolbar</Description>
		</ExtensionNode>
		<ExtensionNode name="ToolSeparator" type="TextEditor.ToolSeparatorNode">
			<Description>Adds a new separator to the toolbar</Description>
		</ExtensionNode>
	</ExtensionPoint>
	...
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/ExtensionPoint: The root element for extension points.
  • /Addin/ExtensionPoint/@path: Path of the extension point.
  • /Addin/ExtensionPoint/@name: Display name of the extension point (to be shown in documentation).
  • /Addin/ExtensionPoint/Description: Long description of the extension point.
  • /Addin/ExtensionPoint/ExtensionNode: A type of node allowed in this extension point.
  • /Addin/ExtensionPoint/ExtensionNode/@name: Name of the node type. When an element is added to an extension point, its name must match one of the declared node types.
  • /Addin/ExtensionPoint/ExtensionNode/@type: CLR type that implements this extension node type. It must be a subclass of Mono.Addins.ExtensionNode. If not specified, by default it is Mono.Addins.TypeExtensionNode.
  • /Addin/ExtensionPoint/ExtensionNode/Description: Description of what this kind of node represents.

Every extension point is identified by a path. The add-in ecosystem of an application can be seen as a tree of extension points, each of which can be reached using a path, like a file path in a file system.

An extension point may accept several kinds of nodes at the same time, as long as all node types have a different name. The name attribute specifies the node name to use when extenders want to register an new node. The type attribute specifies the CLR class that will handle nodes of that type. That is, there will be one TextEditor.ToolButtonNode instance for each node registered with the name ToolButton (the next chapter explains how to create such node classes).

The node class also determines the attributes that can be used in a node. For example, in a toolbar button, it will allow specifying the name of the icon, or the command that must be run when clicking the button.

It is not mandatory to specify the name of an extension node. When not provided, a default name obtained from the node type implementation will be used (see Default Node Name and Description).

Extensions

Given the extension point described in the previous section, the following extension would then be correct in an add-in:

<Addin namespace="TextEditor" id="Core" version="1.0" ...>
	...
	<Extension path="/TextEditor/ToolbarButtons">
		<ToolButton id="New" icon="gtk-new" />
		<ToolButton id="Open" icon="gtk-open" />
		<ToolSeparator id="FileSectionSeparator"/>
		<ToolButton id="Cut" icon="gtk-cut" />
		<ToolButton id="Copy" icon="gtk-copy" />
		<ToolButton id="Paste" icon="gtk-paste" />
	</Extension>
	...
</Addin>

Extensions are registered using the Extension element:

<Addin>
	...
	<Extension path = "...">
		<SomeNode id="..." insertafter="..." insertbefore="..." .../>
		...
	</Extension>
	...	
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/Extension: Registers extension nodes to an extension point.
  • /Addin/Extension/@path: Path of the extension point where the nodes will be registered.
  • /Addin/Extension/{any}: An extension node. The name is defined by the extension point.
  • /Addin/Extension/{any}/@id: Identifier of the node. It's optional, but needed if the node will be referenced from other nodes.
  • /Addin/Extension/{any}/@insertafter: Identifier of the node after which this node has to be placed.
  • /Addin/Extension/{any}/@insertbefore: Identifier of the node before which this node has to be placed.
  • /Addin/Extension/{any}/@{any}: Node-specific attributes.

The kind of nodes that may be added to an Extension element depend on the extension point being extended (see previous section).

Notice that the previous example extension is declared in the TextEditor add-in root. It is allowed because the extension point being extended is declared in the same root.

Extension nodes may have an ID. Specifying the ID is optional but needed if the node has to be referenced by other nodes or from the host. It is recomended to use namespace-qualified node IDs, to avoid name collision.

The insertafter and insertbefore attributes can be used to specify the relative location of a node. The nodes referenced in those attributes must be defined either in the add-in root being extended, or in any add-in on which this add-in depends. Here is an example:

<Addin namespace="TextEditor">
	...
	<Dependencies>
		<Addin id="Core" version="1.0" />
	</Dependencies>
	...
	<Extension path = "/TextEditor/ToolbarButtons">
		<ToolButton id="Save" insertafter="Open" insertbefore="FileSectionSeparator" icon="gtk-save"/>
	</Extension>
	...	
</Addin>

The add-in engine ensures that if insertafter is specified, the node will be inserted after the specified node, and before any other node defined in the same extension. So, in this example, the insertbefore attribute would not be needed. Notice that other nodes in other add-ins may require to be inserted after the same node, so there is no guarantee that the node will be placed just after the specified node, only that it will be placed somewhere after it.

Nodes with children

Extension nodes may have child nodes. In this case an extension node behaves like an extension point, and the extension node must declare the kind of child nodes it accepts. For example:

<Addin 	namespace="TextEditor" id="Core" version="1.0" isroot="true">
	...
	<ExtensionPoint path="/TextEditor/Templates" name="...">
		<Description>Templates that allow creating files with a default content</Description>
		<ExtensionNode name="TemplateCategory" type="TextEditor.TemplateCategory">
			<Description>A category which contains related templates</Description>
			<ExtensionNode name="FileTemplate" type="TextEditor.FileTemplateNode">
		</ExtensionNode>
	</ExtensionPoint>
	...
</Addin>

The previous extension point would accept extensions like this:

<Addin ...>
	...
	<Extension path="/TextEditor/Templates">
		<TemplateCategory id="Documents">
			<FileTemplate id="Letter" resource="letter-template.txt" />
			<FileTemplate id="Fax" resource="fax-template.txt" />
		</TemplateCategory>
		<TemplateCategory id="Development">
			<FileTemplate id="README" resource="readme-template.txt" />
			<FileTemplate id="ChangeLog" resource="changelog-template.txt" />
		</TemplateCategory>
	</Extension>
	...
</Addin>

Extension nodes which accept children behave like extension points, and can be extended by add-ins just like them. For example, if an add-in adds the previous extensions to the extension point, another add-in could further extend this new template like this:

<Addin ...>
	...
	<Extension path="/TextEditor/Templates/Development">
		<FileTemplate file="LICENSE" resource="license-template.txt"/>
	</Extension>
	...
</Addin>

Notice that the path used in this extension is directly referencing an extension node using its ID. So, only nodes which have an ID can be extended in this way.

Children of an extension node can also be declared by applying the [ExtensionNodeChild] attribute to the class that implements the node. This is explained later in the section Custom Extension Node Types.

Node sets

Node sets allows grouping a set of extension node declarations and give an identifier to that group (the node set). Once a node set is declared, it can be referenced from several extension points which use the same extension node structure. Extension node sets also allow declaring recursive extension nodes, that is, extension nodes with a tree structure.

Extension node sets are declared using the ExtensionNodeSet element, and they look almost like an extension point, but instead of a path, they have an identifier. Node sets can be referenced from extension nodes using a ExtensionNodeSet element with the same ID. For example:

<Addin>
	...
	<!-- Declares the extension node set -->
	<ExtensionNodeSet id="ToolbarNodeSet">
		<ExtensionNode name="ToolButton" type="..." />
		<ExtensionNode name="ToolSeparator" type="..." />
	</ExtensionNodeSet> 
	
	<!-- An extension point using the node set -->
	<ExtensionPoint path="/TextEditor/ToolbarButtons" name="...">
		<Description>...</Description>
		<ExtensionNodeSet id="ToolbarNodeSet"/>
	</ExtensionPoint>
	...
</Addin>

ExtensionNodeSet references can be used anywhere an extension node can be declared, so they can also be used to specify the children accepted by an extension node. It is also possible to reference more than one extension set from the same extension point or extension node.

Another example: the following set of declarations might be used to create extension points for the menus of the text editor:

<Addin namespace="TextEditor" id="Core" version="1.0">
	...
	<ExtensionNodeSet id="MenuNodeSet">
		<ExtensionNode name="MenuItem" type="TextEditor.MenuItemNode" />
		<ExtensionNode name="MenuSeparator" type="TextEditor.MenuSeparatorNode" />
		<ExtensionNode name="Menu" type="TextEditor.SubmenuNode">
			<ExtensionNodeSet id="MenuNodeSet" />
		</ExtensionNode>
	</ExtensionNodeSet> 
 
	<ExtensionPoint path="/TextEditor/MainMenu">
		<Description>Menu for the main window</Description>
		<ExtensionNodeSet id="MenuNodeSet" />
	</ExtensionPoint>
 
	<ExtensionPoint path="/TextEditor/DocumentContextMenu">
		<Description>Context menu for the document being edited</Description>
		<ExtensionNodeSet id="MenuNodeSet"/>
	</ExtensionPoint>
	...
</Addin>

There are two good reasons for using a node set in this example:

  • An application may have several menus, so it makes sense to be able to reuse the same structure of nodes for all menus.
  • Menus are hierarchical. Menus can contain submenus with the same structure. This recursive definition can only be done using node sets. Notice that the Menu item in MenuNodeSet is recursively referencing the same node set.

Conditions

Add-ins may use conditions to register nodes in an extension point which are only visible under some contexts.

For example, an add-in registering a custom menu option to the main menu of our sample text editor might want to make that option visible only for some kind of files. To allow add-ins to do this kind of check, the host application needs to define a new condition. Conditions are defined like this:

<Addin namespace="TextEditor" id="Core" version="1.0">
	...
	<ConditionType id="Openfile" type="TextEditor.OpenFileCondition" />
	...
</Addin>

The following describes the attributes and elements shown above:

  • /Addin/ConditionType: Declares a new condition type.
  • /Addin/ConditionType/@id: Identifier of the condition type. This is the ID to be used in extensions to reference this condition.
  • /Addin/ConditionType/@type: CLR type that implements this condition.

This condition defined in the add-in root can be referenced by add-ins like this:

<Addin namespace="TextEditor" id="Xml">
	...
	<Extension path = "/TextEditor/MainMenu/Edit">
		<Condition id="OpenFile" extension="xml,config">
			<MenuSeparator insertafter="Paste" />
			<MenuItem label="Format XML" commandType="TextEditor.Xml.FormatXmlCommand" />
		</Condition>
	</Extension>
	...
</Addin>

Meaning that a separator and new "Format XML" command will be added after the "Paste" command, but only if the current open file has the extension ".xml" or ".config".

Extension points are dynamically updated when the status of a condition changes. Nodes matching the new condition status will be added, and nodes which do not match the condition will be removed.

Conditions are implemented using a subclass of Mono.Addins.ConditionType. For example, OpenFileCondition might be implemented like this:

namespace TextEditor
{
	public class OpenFileCondition: ConditionType
	{
		public OpenFileCondition ()
		{
			// It's important to notify changes in the status of a condition,
			// to make sure the extension points are properly updated.
			TextEditorApp.OpenFileChanged += delegate {
				// The NotifyChanged method must be called when the status
				// of a condition changes.
				NotifyChanged ();
			};
		}
		
		public override bool Evaluate (NodeElement conditionNode)
		{
			// Get the required extension value from an attribute,
			// and check againts the extension of the currently open document
			string val = conditionNode.GetAttribute ("extension");
			if (val.Length > 0) {
				string ext = Path.GetExtension (TextEditorApp.OpenFileName);
				foreach (string requiredExtension in val.Split (','))
					if (ext == "." + requiredExtension)
						return true;
			}
			return false;
		}
	}
}

The add-in engine will create an instance of OpenFileCondition when needed, and will call Evaluate to get the result for a specific condition node.

Extension Point Conditions

It is also possible to define conditions which are local to extension points, which means that those conditions will only be usable in extensions of that extension point.

This kind of extension point are defined like in the following example:

<Addin namespace="TextEditor" id="Core" version="1.0">
	...
	<ExtensionPoint path = "/TextEditor/MainMenu">
		<ExtensionNode type="TextEditor.SubmenuNode"/>
		<ConditionType id="Openfile" type="TextEditor.OpenFileCondition" />
	</ExtensionPoint>
	...
</Addin>

There are two important differences between extension point (local) conditions and global conditions:

  • Local conditions can only be used in the extension point that declares them.
  • ConditionType instances for local conditions are not created by the add-in engine. The host application must register an instance of the ConditionType before trying to query the node.

The next chapter has an example about using local conditions.

Nested Conditions

Nested conditions like in the following are allowed:

<Addin namespace="TextEditor" id="Xml">
	...
	<Extension path = "/TextEditor/MainMenu/Edit">
		...
		<Condition id="OpenFile" extension="xml,config">
			<!-- The following nodes will be added only for .xml and .config files -->
			<MenuSeparator insertafter="Paste" />
			<MenuItem label="Format XML" commandType="..." />
			<MenuItem label="Check DTD" commandType="..." />
			...
			<Condition id="OpenFile" extension="config">
				<!-- This node will be added for .config files only -->
				<MenuItem label="Insert config section" commandType="..." />
			</Condition>
		</Condition>
		...
	</Extension>
	...
</Addin>

Complex Conditions

Extensions can use the ComplexCondition element to build conditions using And and Or operators. For example:

<Addin namespace="TextEditor" id="Xml">
	...
	<Extension path = "/TextEditor/MainMenu/Edit">
		...
		<ComplexCondition>
			<!-- The first child must be a condition operator -->
			<Or>
				<!-- A condition operator can contain conditions or other operators -->
				<Condition id="OpenFile" extension="xml" />
				<Condition id="OpenFile" extension="config" />
				<And>
					<Condition id="OtherCond1" value="1" />
					<Condition id="OtherCond2" value="2" />
				</And>
			</Or>
			<!-- Starting here, nodes to be added if the condition is satisfied -->
			<MenuSeparator insertafter="Paste" />
			<MenuItem label="Format XML" commandType="TextEditor.Xml.FormatXmlCommand" />
		</ComplexCondition>
		...
	</Extension>
	...
</Addin>

The fist child of a ComplexCondition must be a condition operator (Or or And). The other children of the are nodes to be added if the complex condition evaluates to true.

Optional Modules

By using optional modules, and add-in can declare extensions which will be registered only if some specified add-in dependencies can be satisfied.

A module is declared using the Module element:

<Addin>
	...
	<Module>
		<Dependencies>
			...
		</Dependencies>
		<Runtime>
			...
		</Runtime>
		<Extension ...>
			...
		</Extension>
		...
	</Module>
</Addin>

Where:

  • /Addin/Module: Is the declaration of a module. An add-in can contain several optional modules.
  • /Addin/Module/Dependencies: Dependencies that must be satisfied in order for the extensions of this module to be loaded.
  • /Addin/Module/Runtime: Allows declaring assemblies and files to be loaded if the module is active.
  • /Addin/Module/Extension: Extensions to be loaded when the module is active.

The rules explained in the previous sections for specifying dependencies, runtime files and extensions can also be applied for modules.

Custom Extension Node Types

Every extension point has to declare the type of extension node that it accepts. Add-ins can register new extension nodes to that extension point. At run-time, extension nodes are represented by instances of the class Mono.Addins.ExtensionNode or instances of a subclass of it. Hosts can query extension points and get instances of ExtensionNode to extract whatever extension information is needed from them.

Mono.Addins provides the class TypeExtensionNode, a type of extension node which covers a very common extension scenario: the host defines an interface (or type), and the add-in has to implement it. At run-time TypeExtensionNode represents a type registered by an add-in, and a host can create instances out of it.

TypeExtensionNode may be enough for simple applications, but more complex applications will need to define new extension node types, so more complex extension information can be declared in extension points. Also, extension nodes don't always need to represent type implementations. In those cases, a host will need to implement custom extension node types.

Custom extension node implementation

Creating extension node types is very simple. Let's see one example:

namespace TextEditor
{
	public class FileTemplateNode: ExtensionNode
	{
		[NodeAttribute]
		string resource;
		
		[NodeAttribute]
		string name;
		
		public string Name {
			get { return name != null ? name : Id; }
		}
		
		public virtual string GetContent ()
		{
			using (StreamReader sr = new StreamReader(Addin.GetResource (resource))) {
				return sr.ReadToEnd (); 
			}
		}
	}
}

This extension node type is used in the Text Editor example to register file templates. When creating a new file, the user can choose a file template, so the new file will include the content provided by the template.

This class does several interesing things:

  • It inherits from Mono.Addins.ExtensionNode. All extension node types must be a subclass of this type.
  • It declares a field named resource and marks it with the NodeAttribute custom attribute. When creating a node instance, all fields marked with this attribute will be initialized using attribute values taken from the node.
  • It implements a GetContent method which gets the content of a template using Addin.GetResource. The Addin property is declared in ExtensionNode and provides access to add-in resources and types.

This node type would be able to represent nodes registered in an extension like this:

<Addin ...>
	...
	<Extension path="/TextEditor/Templates">
		<FileTemplate name="README" resource="readme-template.txt" />
		<FileTemplate name="ChangeLog" resource="changelog-template.txt" />
	</Extension>
	...
</Addin>

When calling AddinManager.GetExtensionNodes(), the add-in engine will read all registered elements and will create the corresponding ExtensionNode subclass instance for each of them. It will also transfer attribute values to fields marked with [NodeAttribute].

Extension nodes are not just data containers, but they can also implement some logic. In this example the host application doesn't need to know where does a FileTemplateNode get the template content from. This logic is hidden in FileTemplateNode. FileTemplateNode happens to get the content from a resource, but it might allow getting it from other places in the future.

Extension node deserialization

The [NodeAttribute] custom attribute can be used to specify fields that have to be initialized from node attributes. By default, a field is will get its value from an attribute with the same name, although it is possible to specify a different name by setting the NodeAttribute.Name property. NodeAttribute also has a Required property for specifying if an attribute is mandatory or not. So for example the resource filed might be declared like this:

public class FileTemplateNode: ExtensionNode
{
	...
	// Will match elements like <FileTemplate resource-name="blah" />
	// The true parameter specifies that the attribute is required
	[NodeAttribute ("resource-name", true)]
	string resource;
	...
}

ExtensionNode subclasses can also override the virtual method Read (NodeElement elem) to take control on the process of loading a ExtensionNode instance out of an extension node. If the Read method is overriden (and if it doesn't call the base class) [NodeAttribute] attributes will be ignored.

[NodeAttribute ("resource-name", true)]
public class FileTemplateNode: ExtensionNode
{
	...
	string resource;
 
	protected override void Read (NodeElement elem)
	{
		// This is equivalent to the previous example, but more
		// eficient since reflection is not involved here
		resource = elem.GetAttribute ("resource-name");
	}
	...
}

Notice the [NodeAttribute] attribute declaration applied to the class. When using custom deserialization, [NodeAttribute] can be applied to the class (or to a field) to declare the extension node attributes. This information however is only used for documentation purposes, and the add-in engine will not use it to do any kind of check. All checks must be done by the Read method override.

Getting add-in types, files and resources

As seen in the previous examples, the object returned by the ExtensionNode.Addin property can be used to get information from the add-in that created a node. It is a protected property, and it is intended to be used by ExtensionNode subclasses to get whatever content is needed. There are three methods which can be used to get such content:

  • GetResource (string name): returns a resource embedded in any of the assemblies included in the add-in.
  • GetType (string name): returns a type implemented in any of the assemblies included in the add-in, or in any of the add-ins on which this add-in depends.
  • GetFilePath (string name): Returns a path to a file which has been deployed together with the add-in.

Working with add-in files

It is possible to distribute files together with the add-in, and access those files from extension nodes. To distribute a file with an add-in, the file has to be declared in the Runtime section. For example, let's say we improve FileTemplateNode, so it can get the template not only from resources, but also from files distributed together with the add-in. An add-in declaration might look like this:

<Addin namespace="TextEditor" id="Xml">
	...
	<Runtime>
		<Import assembly="TextEditor.Xml.dll" />
		<Import file="someTemplate.xml" />
	</Runtime>
	...
	<Extension path="/TextEditor/Templates">
		...
		<FileTemplate name="SomeTemplate" fileName="someTemplate.xml" />
		...
	</Extension>
	...
</Addin>

The FileTemplateNode class would need to handle the new fileName attribute:

namespace TextEditor
{
	public class FileTemplateNode: ExtensionNode
	{
		[NodeAttribute]
		string resource;
		
		[NodeAttribute]
		string fileName;
		
		[NodeAttribute]
		string name;
		
		public string Name {
			get { return name != null ? name : Id; }
		}
		
		public virtual string GetContent ()
		{
			StreamReader sr;
			if (resource != null)
				sr = new StreamReader (Addin.GetResource (resource));
			else if (fileName != null)
				// GetFilePath returns the full path to the add-in file
				sr = new StreamReader (Addin.GetFilePath (fileName));
			else
				return null;
 
			using (sr) {
				return sr.ReadToEnd (); 
			}
		}
	}
}

The private data directory

Every add-in has a private directory which can be used to store add-in specific files created at run time. This directory is created the first time that access to it is requested, and will be deleted when the add-in is uninstalled.

For example, let's say we want to support compressed file templates in FileTemplateNode. The GetContent method would need to uncompress the file before reading it, and it should be done only once. The implementation might look like this:

public virtual string GetContent ()
{
	StreamReader sr;
	if (resource != null) {
		sr = new StreamReader (Addin.GetResource (resource));
	}
	else if (fileName != null) {
		
		// Get the full path to the add-in file
		string filePath = Addin.GetFilePath (fileName);
		
		// If it is a gzipped file, it needs additional processing
		if (filePath.EndsWith (".gz")) {
			
			// The file will be unzipped in an add-in specific folder
			string unzippedFile = Path.Combine (Addin.PrivateDataPath, Path.GetFileNameWithoutExtension (filePath));
			
			// Unzip only once
			if (!File.Exists (unzippedFile))
				UnzipFileTo (filePath, unzippedFile);
			
			filePath = unzippedFile;
		}
		sr = new StreamReader (filePath);
	}
	else
		return null;
	using (sr) {
		return sr.ReadToEnd (); 
	}
}

Default node name and description

When an extension point does not provide a name for a node type, a default node name will be used. This default node name is the name of the class that implements it.

It is also possible to provide a custom default name, and also a description, to be used in those cases. This information can be provided using the [ExtensionNode] attribute:

[ExtensionNode ("FileTemplate", "A file template the user can choose when creating a new file")]
class FileTemplateNode: Mono.Addins.ExtensionNode
{
	...
}

Handling children

The property ExtensionNode.ChildNodes returns a list of children of a node. ExtensionNode implementations can query this list in order to build objects which are composed by several nodes.

For example, the TextEditor.SubmenuNode extension node, which represents an extensible submenu of the main menu, might be implemented like this:

public class SubmenuNode: MenuNode
{
	[NodeAttribute]
	string label;
	
	public override Gtk.MenuItem GetMenuItem ()
	{
		// Create the menu item
		Gtk.MenuItem it = new Gtk.MenuItem (label);
		Gtk.Menu submenu = new Gtk.Menu ();
 
		// Iterate through all children, and add a menu item for each of them
		foreach (MenuNode node in ChildNodes)
			submenu.Insert (node.GetMenuItem (), -1);
		it.Submenu = submenu;
		return it;
	}
}
 
// Abstract class to be subclassed by any kind of node that
// can generate a menu item.
public abstract class MenuNode: ExtensionNode
{
	public abstract Gtk.MenuItem GetMenuItem ();
}

The ChildNodes list may dynamically change while the application is running as a result of add-ins being enabled/disabled, or if the status of some conditions change. There are some overridable methods which are called when this happen: OnChildNodeAdded, OnChildNodeRemoved and a more generic OnChildrenChanged.

The [ExtensionNodeChild] attribute allows declaring the type of the child nodes of a node:

[ExtensionNode ("Menu")]
[ExtensionNodeChild (typeof(MenuItemNode))]
[ExtensionNodeChild (typeof(MenuSeparatorNode))]
[ExtensionNodeChild (typeof(SubmenuNode))]
public class SubmenuNode: MenuNode
{
	...
} 
 
[ExtensionNode ("MenuSeparator")]
public class MenuSeparatorNode: MenuNode
{
	...
}
 
[ExtensionNode ("MenuItem")]
public