Building a plugin architecture with Managed Extensibility Framework - Part 4

In this final post, we'll explore the implementation of a plugin architecture using the Managed Extensibility Framework (MEF).

What is MEF ?

MEF (Managed Extensibility Framework) is a component of the .NET Framework that provides a framework for building extensible applications. It allows developers to easily create applications with a plugin architecture, where components can be added or removed at runtime without recompiling the main application. MEF simplifies the development of extensible applications by providing a set of APIs and tools for discovering, loading, and managing plugins. It provides a flexible and declarative model for defining extensions and their dependencies, making it easy to compose applications from a set of loosely coupled components.

  • MEF enables automatic discovery of available extensions within an application or assembly. This allows developers to easily add new plugins without having to manually register them (contrary to our initial example).

  • MEF provides mechanisms for composing applications from multiple extensions. It automatically resolves dependencies between components and ensures that plugins are loaded and initialized in the correct order.

  • MEF manages the lifecycle of plugins, including their instantiation, initialization, and disposal. This helps ensure that resources are properly managed and that plugins behave predictably within the application.

Overall, MEF simplifies the development of extensible applications by providing a standardized framework for building plugin architectures. It promotes modularity, flexibility, and maintainability, allowing developers to easily extend and customize their applications to meet changing requirements.

A worked example

In this section, we will systematically restructure the requirement for an ecommerce website to offer multiple payment methods. We will develop the application incrementally, one step at a time, in a progressive manner.

  • Create a new solution named EOCS.Plugin for example and a new Console App project in it named EOCS.Plugin.Main.

  • Add the following code in the Program class.

 1internal class Program
 2{
 3    static void Main(string[] args)
 4    {        
 5        Console.WriteLine("Enter method:");
 6
 7        var s = Console.ReadLine();
 8        
 9        // Process payment with the selected method
10    }
11}
Information

This class will serve as our host (core system) and will bear the responsibility for loading and overseeing our plugins.

As previously indicated, the next step is to define contracts that our plugins must adhere to. To accomplish this, we will establish a dedicated project within our solution.

  • Add a new Class Library project and name it EOCS.Plugin.Interfaces.

  • Create an interface named IPaymentMethod and add the following code in it.

1public interface IPaymentMethod
2{
3    string Name { get; }
4
5    bool TryProcessPayment(double amount);
6}
Information

In our context, these interfaces emphasize the contracts that plugins must conform to.

  • Do not forget to reference this project in the EOCS.Plugin.Main project.

Up to this point, there hasn't been any groundbreaking code or particularly novel concepts introduced. We are simply utilizing conventional object-oriented programming techniques. Now, it's time to introduce MEF. We'll begin by implementing our first plugin, which in this case, will be our initial payment method.

  • Add a new Class Library project and name it EOCS.Plugin.PaymentMethods.PayPal.

  • Add the System.ComponentModel.Composition Nuget package.

Information

This package is the key to unlocking the potential of a plugin architecture with MEF. It will encompass all the necessary components to fully utilize its capabilities.

  • Create a new class named PayPal.cs and add the following code to it.
 1[Export(typeof(IPaymentMethod))]
 2public class PayPal : IPaymentMethod
 3{
 4    public string Name => "PAYPAL";
 5
 6    public bool TryProcessPayment(double amount)
 7    {
 8    	// Process payment for PayPal
 9       return true;
10    }
11}

We notice that our class inherits from IPaymentMethod (so remember to reference EOCS.Plugin.Interfaces project) and encapsulates the specific logic for processing an order with PayPal. However, the crucial aspect is that the class is adorned with the Export attribute. This attribute signifies that this class can serve as an implementation of IPaymentMethod and will be automatically discovered at runtime by the host.

Information

In our previous context, classes adorned with the Export attribute signify the available plugins within the system.

Now, it's time to tie everything together. We need to instruct the host to automatically discover plugins and specify where it can locate them.

  • Edit the Program.cs class with the following code.
 1internal class Program
 2{
 3    static void Main(string[] args)
 4    {
 5        var p = new Bootstrap();
 6        Console.WriteLine("Enter method:");
 7
 8        var s = Console.ReadLine();
 9        var method = p.Methods.FirstOrDefault(t => t.Name == s);
10
11        if (method != null)
12        {
13            var res = method.TryProcessPayment(200.0);
14        }
15    }
16}
17
18public class Bootstrap
19{
20    private CompositionContainer _container;
21
22    [ImportMany(typeof(IPaymentMethod))]
23    public List<IPaymentMethod> Methods;
24
25    public Bootstrap()
26    {
27        try
28        {
29            var path = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.FullName + "\\Modules";
30
31            var catalog = new AggregateCatalog();
32            catalog.Catalogs.Add(new DirectoryCatalog(path));
33
34            _container = new CompositionContainer(catalog);
35            _container.ComposeParts(this);
36        }
37        catch (CompositionException compositionException)
38        {
39            Console.WriteLine(compositionException.ToString());
40        }
41    }
42}

Here, we define a Bootstrap class where we specify that plugins related to payment methods are available somewhere. The term "somewhere" may seem vague; however, in reality, MEF offers several methods to specify the location of plugins. In this case, we have opted to simply indicate a folder on the computer.

Information

Here, we have the essential plumbing code required to instantiate the plugin architecture using MEF. We utilize the CompositionContainer class and include a list of catalogs specifying where plugins can be found. It's worth noting that we can incorporate multiple areas where plugins may reside by adding several catalogs.

In our example, plugins will reside in a "Modules" directory. Therefore, we need to first create this directory by adding a folder to the project. However, it's essential to populate it with the corresponding plugins.

  • Compile the EOCS.Plugin.PaymentMethods.PayPal project and copy the EOCS.Plugin.PaymentMethods.PayPal.dll in the Modules folder.

By taking these steps, we supply plugins to the host. Subsequently, the host will load these plugins at runtime and discover them using reflection mechanisms. Consequently, plugins are automatically discovered, eliminating the need to modify anything else each time a new module is added.

What is the process for integrating a new payment method using this approach ?

To accommodate a third party wishing to add a new payment method that may not be widely used, such as one dedicated to a specific country, we can follow these steps:

  • Ensure that the new payment method adheres to the interface defined for payment methods (IPaymentMethod).

  • The third party develops the plugin for the new payment method according to the defined interface. They should decorate their implementation class with the Export attribute to signify it as a plugin.

  • The third party provides the plugin DLL to be placed in the designated "Modules" directory.

  • At runtime, the host application will automatically discover the new plugin and integrate it into the system without any manual intervention.

By following this approach, the host application can seamlessly incorporate new payment methods introduced by third parties, ensuring flexibility and extensibility without requiring modifications to the core application code.

Final thoughts

With that, we conclude this series on MEF. In truth, we've merely scratched the surface, and there's much more to explore. Nonetheless, what we've covered thus far is adequate for establishing a highly robust and elegant plugin architecture.

The subsequent textbook proves useful for concluding this series.

Software Architecture by Example: Using C# and .NET (Michaels)