Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Architecture Overview

Ali Özgür edited this page Oct 26, 2016 · 6 revisions
Home > Architecture Overview

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 type names or 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: An assembly and/or other 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.Addin 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.

Mono.Addins also 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 Add-in Host

The Text Editor application is composed by an assembly (TextEditor.exe) with the following code:

    using System;
    using Mono.Addins;
    
    [assembly:AddinRoot ("TextEditor", "1.0")]
    
    class MainClass
    {
        public static void Main ()
        {
            AddinManager.Initialize ();
            AddinManager.Registry.Update ();
            
            foreach (ICommand cmd in AddinManager.GetExtensionObjects<ICommand> ())
                cmd.Run ();
        }
    }

    [TypeExtensionPoint]
    public interface ICommand
    {
        void Run ();
    }

This code shows some declarations and initializations that you have to do in all extensible applications:

  • The [AddinRoot] attribute applied to an assembly declares that this assembly is an add-in host. All add-in hosts must have an identifier and a version number.
  • The AddinManager.Initialize call initializes the add-in engine. It must be called before doing any operation with the AddinManager class.
  • When calling AddinManager.Registry.Update, the add-in engine will scan the application directory (and other user-defined add-in directories) looking for add-ins, and will update an internal add-in registry which is used to cache add-in information.
  • The AddinManager.GetExtensionObjects call can be used to query an extension point. It returns a list of objects registered in that extension point.
  • The [TypeExtensionPoint] attribute when applied to a class or interface declares an extension point which accepts objects of that type. In this example, we are creating an extension point for objects implementing the type ICommand.

A Sample Add-in

This sample add-in is implemented in a separate assembly:

    using System;
    using Mono.Addins;
    
    [assembly:Addin]
    [assembly:AddinDependency ("TextEditor", "1.0")]
    
    [Extension]
    public class HelloCommand: ICommand
    {
        public void Run ()
        {
            Console.WriteLine ("Hello World!");
        }
    }

Add-ins must be marked with the [Addin] attribute to be recognized as add-ins. Add-ins must also declare the add-in hosts they are extending by using the [AddinDependency] attribute. Notice that an add-in can extend several add-in hosts, and it can even extend other add-ins which declare their own extension points. The AddinDependency attribute must specify the host/add-in id and its version number.

By applying the [Extension] attribute to a class we are declaring a new extension node of that type which is added to an extension point. The extension point is determined by looking at the base types and interfaces of the class. So in this example the class implements the ICommand interface, and there is an extension point defined for that interface, so that's the extension point where the type will be registered. If a class implements several interfaces, we can select the extension point by specifying the type name in the attribute, for example: [Extension (typeof(ICommand))].

Architecture of an Extensible Application

Extensible applications are often composed by at least one executable assembly and by one library. The executable is the driver of the application and implements the most important logic. The library provides a public API which defines interfaces and classes which can be used 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 host 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.

Clone this wiki locally