TSM - Designing complex .NET systems using Managed Extensibility Framework

Horațiu Florea - .NET software developer @ BisSoft

Without exaggerating, we can admit that we are at the beginning of a new trend in IT. For the majority of the companies, duly accomplishing the functional requirements and delivering a high end product obviously have the highest priority. Therefore, most of the complex software systems need to be expanded with new features, new modules respectively, in a simple and effective manner. Designing an extensible software system is not easy to achieve, so it was developed in a series of platforms that facilitate the design of these software systems. One of the newest such platforms is Managed Extensibility Framework (MEF). The MEF platform enables the development of lightweight, extensible .Net applications.

MEF deals with three fundamental concepts that constitute the essential pillars that give real value and utility to this platform:

Extensibility

An extensible system is one that allows for adding or replacing existing functionality without modification to that system. Extensibility starts with composition: combining the parts (extensions, plugins) to form a whole. The composition ensures the creation of a weakly coupled, cohesive system, as it avoids the use of rigid dependencies between components, and respects two of the SOLID principles (SOLID is an acronym that refers to five basic principles of object-oriented programming, principles that lay the foundation of a well-written code, easy to maintain and optimized for modifiability): Open/Close - the platform facilitates the expansion of functionality (open for extension), without modifying the base system (close for modification); Dependency Inversion - high-level modules (i.e. the extensions) do not depend of low-level modules (i.e. host application), but depend on abstractions (i.e. on contracts - MEF concept that will be explain later in this article).

Probably, the most important aspect to note here is the fact that extensibility occurs at runtime (that's why it is also known as dynamic extensibility). The extensions are being integrated and injected in the system at application runtime, and there is no need to recompile, in order for the extensions to be detected and used as part of that application.

Well, other solutions exist in this space problem associated with extensibility, such as Microsoft's own Managed Addin Framework (MAF) and even inversion of control (IoC) frameworks like StructureMap and Unity. Microsoft felt that these solutions were not ideal for third party extensibility because of the solution being either too heavyweight for general use, or just required an infrastructure constructed from scratch. MEF provides a powerful attributed programming model, out of the box, using the regular .NET attributes and provides a standard way for application to expose the extension points as well as to consume extensions. Actually, this represents the fundamental principle that underlines the MEF platform: the supply of services, and the demand/need to consume them.

MEF is part of .NET 4.0 framework, and everything that deals with MEF resides in the System.ComponentModel.Composition namespace. This namespace hosts the .NET attributes used in realizing the composition, as well as several dedicated classes used for handling different MEF specific exceptions.

Discovery

This concept refers to how we detect the plugins that will be loaded in our extensible, decoupled system. The basic premise is this: when we write a class, we want to focus on one thing that class does, a specific service. Let's imagine a series of such classes contained inside an assembly, which provides a set of specific services. At some point, however, we may need to use this set of services inside the host application, but we don't want to have a direct dependency on those services. This can complicate testing and reduce stability to change the application. We will use an interface for these services, and we will only refer to this interface in the host application. However, something in the middle needs to wire those two together - the discovery, representing the process of finding the best implementation and providing the necessary elements, so that other components can use them, when needed.

Metadata

Metadata represents the MEF technology through which we can provide capabilities and extra information about components. It aims to facilitate the rich querying and filtering of components, without knowing their implementation details. Metadata provides a contextual approach to determine what components are appropriate for the current environment, filtering them properly, and then loading them.

Principles and Terminologies

From the above, it is clear that in terms of MEF platform, a software system is composed of the so-called composing parts. Such parts offer services to other parts and, in turn, consume services provided by other parts. The fact that these parts are developed along with the software system or subsequently as third parties does not make a difference. The key concepts that should be understood to use the MEF platform are:

Figure 1: Example of MEF based architecture

Figure 1 shows an example of architecture that uses MEF to interconnect the components of the system software. Hereby, the software system is composed of four parts, two component export services (component part 1 and component part 2) which are imported by two other components (component part 3 and component part 4). In this example, the MEF platform aims to make the services exported by components part 1 and part 2 available for the part 3 and part 4 components. So, the components, that export services, implement the contract interfaces, and the component, that imports the services, will get references to the services through the MEF platform and will be able to call the public functionality of those services that are defined through the contract interfaces. The MEF platform role is to make transparent the connection between components, ensuring the full decoupling of the system components. Thus, the platform handles the creation of instances, and the only wiring between the systems components is given by the contract interfaces that represent an abstraction of the services. In other words, if we intend to have a different implementation of a service, we only need to create a new implementation of the interface that defines the contract of that service and to export that implementation through the MEF platform.

Catalog

The MEF composition based model implies the usage of an automated parts discovery mechanism, at runtime. This mechanism is realized through an object called Catalog. MEF provides, out of the box, four types of catalogs. The classification is induced by the manner in which we discover the component parts:

Container

The container is responsible for managing the composition by creating the adequate dependencies between parts, properly associating the exports with the imports. To achieve composition, the container uses a catalog holding all the available parts that were detected in the system. The most common type of container is the CompositionContainer, and the actual composition is carried out at the ComposeParts method call. Usually, this action occurs at the beginning of the application (e.g. during the bootstraping process).

Figure 2: High level overview of MEF architecture

Figure 2 shows an overall diagram of the MEF architecture. A catalog is responsible for parts discovery, by wiring up the exported parts (services suppliers) with the imported parts (services consumers). The container (an instance of CompositionContainer) manages the actual composition of parts, through the catalog containing the discovered parts.

Demo: Calculator application

To demonstrate the functionality of the MEF platform, we present a small application that simulates a simple calculator that can perform four basic arithmetic operations: addition, subtraction, multiplication and division, exposed as services (extensions) to be imported through the MEF composition.

Solution structure

Figure 3: Solution structure for MEF Calculator application

The solution comprises of three projects, presented in Figure 3:

To highlight the use of different types of catalogs, we included, under the Extensions folder (in the CalculatorDemoMef host application), the reference to a third component (Division.dll) that treats the division operation.

Perhaps, the most important aspect to be mentioned here is the fact that the CalculatorDemoMef host application does not require a reference to the CalculatorExtension assembly. It will only refer to the CalculatorContract library. In other words, the application consuming the services (extensions) does not hold a direct dependency with the application supplying the services. The detection and loading of parts is realized through a contract (separate assembly). It results in a modular, weakly coupled and cohesive system, easy to be maintained and tested.

[Export(typeof(IOperation))]
public class Add : IOperation
{
    public int Calculate(int num1, int num2)
    {
        return num1 + num2;
    }
}

The four classes (basic arithmetic operations) are decorated with the Export attribute, to indicate the fact that these classes will be exported in the composition model, and will follow the IOperation contract type. The extension corresponding to the addition operation is demonstrated above. The subtraction, multiplication and division extensions are created in a similar way.

[ImportMany]
public IEnumerable<IOperation> CalculatorPlugins { get; set; }

Inside the host application (in the Program.cs class), we annotate the CalculatorPlugins object with the ImportMany attribute, to indicate the fact that multiple extensions are associated with this object. The matching is achieved by assigning the IOperation contract as data type for the CalculatorPlugins object. Because we are dealing with a "0 to n" cardinality (ImportMany), the CalculatorPlugins object will be a collection of IOperation.

private const string AssemblyPath = @"C:\Projects\CalculatorDemoMef\CalculatorExtension\bin\Debug\CalculatorExtension.dll";

private const string DirectoryPath = @"C:\Projects\CalculatorDemoMef\CalculatorDemoMef\Extensions";

private void AssembleCalculatorComponents()
{
   //An aggregate catalog that combines
   // multiple catalogs

   var catalog = new AggregateCatalog();

   //Add all the parts found in the assembly
   //located at this path

  catalog.Catalogs.Add(new AssemblyCatalog(Assembly. 
     LoadFrom(AssemblyPath)));

   //Add all the parts found in the assemblies
   //contained in the directory located at this path

  catalog.Catalogs.Add(new  
     DirectoryCatalog(DirectoryPath, "*.dll"));

    //Create the CompositionContainer with the parts 
  //in the catalog

 var container = new CompositionContainer(catalog);

    //Fill the imports of this object
    try
    {
        container.ComposeParts(this);
    }
    catch (CompositionException compositionException)
    {
        Console.WriteLine(compositionException.
           ToString());
    }
}

[ImportMany]
public IEnumerable<IOperation> CalculatorPlugins { get; set; }

The extensible parts are discovered from two different locations. We use an AssemblyCatalog to detect the extensions located inside the CalculatorExtension assembly (extensions specific for addition, subtraction and multiplication operations), and a DirectoryCatalog to detect the extensions located inside the Extensions directory (extension provided by a third party and referred via a dll file, specific for the division operation). Since we deal with two different types of catalogs, we empower an AggregateCatalog where we add these two specific catalogs. The AggregateCatalog is then used by the container (CompositionContainer) to achieve the composition.

Figure 4: Parts loaded in the catalog

Figure 4 shows the manner in which parts are loaded into the AggregateCatalog. We can notice that the first three extensions (Add, Subtract, Multiply) emanate from the CalculatorExtension assembly, and the fourth extension (Divide) comes from the Division assembly.

Metadata

The main advantage provided by metadata is that it allows the exports (plugins) to be discovered and used (e.g. for filtering) before the actual creation of the component part. Loading them in the memory is avoided.

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
public class Add : IOperation
{
    public int Calculate(int num1, int num2)
    {
        return num1 + num2;
    }

Through the ExportMetadata attribute that uses a key-value pair, we can describe the significance of each exported plugin. MEF allows decorating a plugin with multiple ExportMetadata attributes, in case we need to associate more information.

In order to use the metadata provided by an export, we need to make use of a new generic type, Lazy<T>. Lazy<T> provides support for lazy instantiation (the instantiation is deferred until the instance is truly necessary). Actually, we need to use a variant of Lazy<T>, namely Lazy` which provides a lazy indirect reference to an object and its associated metadata. This type has three properties that we care about:

[ImportMany]
public IEnumerable<Lazy<IOperation, IDictionary<string, object>>> CalculatorPlugins { get; set; }

With the help of metadata, we can inspect and filter plugins.

var somePlugins = CalculatorPlugins.Where(p => p.Metadata["Symbol"].Equals('+') || p.Metadata["Symbol"].Equals('*'));

The drawback of the ExportMetadata attribute is that it implies a weak type approach, because we don't have any guarantee that the requested metadata key truly exists (can't verify its validity at compilation). The alternative for this problem is to make use of strong type metadata. This approach needs two important pieces: an interface that defines the metadata properties, and a custom attribute.

Conclusions

MEF provides, out of the box, an efficient platform for application extensibility, through the dynamic discovery of the components at runtime. Third parties are easily integrated in the application, without any need for knowledge regarding their implementation. The lack of direct dependencies between application and the extensible parts empowers a loosely coupled, cohesive, modular based system that facilitates maintenance, testability and reusability.

Bibliography

  1. https://mef.codeplex.com/

  2. http://www.codeproject.com/Articles/188054/An-Introduction-to-Managed-Extensibility-Framework

  3. https://www.safaribooksonline.com/library/view/fundamentals-of-the/9780132929400/

  4. https://app.pluralsight.com/player?course=mef&author=dustin-davis&name=mef-m2-parts&clip=0&mode=live

  5. https://visualstudiomagazine.com/articles/2013/02/12/mef-convention-model.aspx