Plugin Based Application Using C# MEF

Figure 1. Service Export Attributes

Here is a small tutorial for this topic on YouTube.

Using a Plugin-Based Application C# MEF

HINT

Please read the article carefully because some users tried using it, but did not understand how it works. For example, in some cases, you are forced to set up Visual Studio to recompile all DLLs on every build. This is important because due to the loss of dependency between the start of the project. The module contains the DLL yet Visual Studio does not recognize that the DLL has changed, so it may not build it again. To enable this behavior, go to Tools -> Projects and Solutions -> Build and run and set the option "On-Run When projects are out of date" To "Always Build" (Visual Studio restart required).

Prerequisite

I expect you to understand your language. You should be aware of Class inheritance, and some basic understanding of Inversion of Control and Dependency injection.

  1. Trunk Source includes
  2. JPB.Shell.exe
  3. Contracts.dll
    • Shell Interfaces and Attributes host
  4. MEF.dll
    • The Framework itself

Branches Source includes

For now, there are 2 existing branches checked into Github. It all depends on the Main Nuget Package.

  • Console
  • JPB.Shell.Example
  1. Console.exe
    • Start application
  2. BasicMathFunctions.dll
    • Implementation of a Simple calculator
  3. Contracts.dll
    • The application specifies Interfaces

The console application should help you to understand how a Plugin-based application should be designed and how in general the framework could be used without any UI-specific revelation Foo.

Contains

This solution should present you with a sample application that shows you how to build a complete Plugin-based application in WPF. The basic work is the same as in some Web projects or Forms. You provide the Modules via MSEF and then load them by accessing their properties.

Introduction

The building, Maintaining, and actual usage of a Plugin-based application is something new to many developers. But in our world of fast-changing requirements and open-source projects around us all, it's something that will cross the way of nearly every developer. Even if this is just a moment in a project when we think "Hmm, this will change in the future, let's use an interface to ensure that the work will be not that big". In that case, I introduce a method to get this approach to a new level. Let's talk about the buzzwords Dependency Injection and Inverse of Control (IoC). There are some nice articles here so I will not explain them in-depth just to ensure we are talking about the same thing. Dependency injection means that we move the responsibility of certain functions from the actual actor away. We Inject some logic into a fixed class. The only constraint on this approach is that at most times the target Actor must support this kind of behavior. And at least if the class is not intended to allow this coding pattern you should not use it. Most likely you would break the desired code infrastructure if you try to take control of some process when the process is not designed to allow this. The inverse of Control is like the Dependency Injection of a coding pattern and is intended to provide a process from one actor to another. As the name says, it allows the Control over a process from the original Actor to some other. This applies to the simple usage of a Delegate for Factory creating as on the Complete WPF Framework. We provide away the Control about how something is done.

Background

The reason I created this framework was very simple. I was facing some problems with the creation of a program that was developed fast but then, as a big surprise for me, the growth of the Stackholder was Hell.

In the end, we had a program that was so big that it was nearly impossible to maintain. We decided to redevelop and since we had so many functions inside this application (what was not desired to contain all these functions) we also decided to allow this kind of function "madness".

Then the idea of some kind of Plugin/Module application was born inside my head. I did some research and found the Managed Extensibility Framework (MEF) inside the .NET Framework. But as fast as I had found this, I found the limitations of it. In my opinion, it was just too slow and for this kind of "simple" work, I began extending the MEF and wrote some manager classes. The result of my efforts can be explored here today. Since it started for a UI-related application we quickly discovered the full advantages of my code. The code is simple and useful and does 2 things. First, it speeds up the enumeration process with the usage of Plinq and some other Optimations. Second, it limits access. Why would this be a good thing? You may ask yourself now. MEF provides you with many "extra" functionality and some kind of "Nuclear Arsenal" of Configuration. This is for a developer who has no idea of Dependency injection and IoC far too much. So limiting the configuration and also some things you do not necessarily need to understand, is the exact right thing. Some configuration was just changed in a more centralized way.

MSEF

The Managed Service Extensibility Framework (MSEF) builds upon MEF and extends the usage and Access to MEF. It wraps all MEF Exports into services that can be accessed through its Methods. A class represents one or more Services. When a class defines one or more Services it must not implement its functionality. In the worst-case scenario, this will break the law of OOP.

Structure

Every shared code we want to manage must be wrapped into an Assembly. This Assembly contains classes that inherit from IService. IService is most likely of a Marker interface with just one definition of a starting method that will be invoked when the Service starts once. Every service that will be accessed by the framework will be handled as SingleInstance.

namespace JPB.Shell.Contracts.Interfaces.Services
{
    public interface IService
    {
        void OnStart(IApplicationContext application);
    }
}

The IService interface is an important thing when we talk about the implementation. But searching for inheritance would be also slow. So we need something additional like an attribute. A .Net attribute is the desired way of adding metadata to a class. The base attribute is the ServiceExportAttribute as in the following.

public class ServiceExportAttribute : ExportAttribute, IServiceMetadata
{
    // Implementation goes here
}

As you see, it also inherits from the MEF Export attribute and the MSEF interface. The base class ensures an enumeration from MEF so all classes that are marked for Export are also usable from MEF without change. The IServiceMetadata attribute contains some additional info.

public interface IServiceMetadata
{
    Type[] Contracts { get; }
    string Descriptor { get; }
    bool IsDefaultService { get; }
    bool ForceSynchronism { get; }
    int Priority { get; }
}

As I said, a class can be used to define one or more Services. To "present" this information to the outside without analyzing the class self the information must be written to the Attribute. For that case, the Contracts array is used. For every type, in this collection, the defined class will be seen as the type.

Every aspect of this code is extendable. If the code does not fit your needs then just extend it or wrap it. Just this basic information must be provided. Even for normal usage, you are forced to extend it.

Using the code

The normal usage would be the following.

You define some set of services to create the interface that inherits from IService.

public interface IFooDatabaseService : IService
{
    // CRUD operations
    void Insert<T>(T entity);
    T Select<T>(string where);
    void Update<T>(T entity);
    bool Delete<T>(T entity);
}

That is your service definition. You could just extend this interface as much as you want, even inheriting from other interfaces or extending this Service by another service. That's all possible as long as it inherits from IService. To make the service usable you must implement it in some DLL and mark it with the ServiceExport Attribute.

[ServiceExport(
    descriptor: "Some name",
    contract: new[] {
        typeof(IFooDatabaseService),
        // Additional Services like services that are inherited
    }
)]
class FooDatabaseService : IFooDatabaseService
{
    public void Insert<T>(T entity)
    {
        throw new NotImplementedException();
    }
   
    public T Select<T>(string where)
    {
        throw new NotImplementedException();
    }
   
    public void Update<T>(T entity)
    {
        throw new NotImplementedException();
    }
   
    public bool Delete<T>(T entity)
    {
        throw new NotImplementedException();
    }
   
    public void OnStart(IApplicationContext application)
    {
        throw new NotImplementedException();
    }
}

You may have noticed that the class has no access modifier. This is possible but really bad. Since MEF does no more than use Reflection it can and will break the law of OOP. So keep in mind that these laws are not applied here.

We now have created all the necessary things. We extended the IService interface to allow our own service implementation and marked the class as a Service with the ExportService attribute. The next step would be the enumeration of the services and the use of that service.

To start the initial process we must first create the processor. The process is handled by the ServicePool class.

[DebuggerStepThrough]
public class ServicePool : IServicePool
{
    internal ServicePool(string priorityKey, string[] sublookuppaths)
    {
        // Constructor implementation
    }
}

namespace JPB.Shell.MEF.Factorys
{
    public class ServicePoolFactory
    {
        public static IServicePool CreatePool()
        {
            return CreatePool(string.Empty, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
        }

        public static IServicePool CreatePool(string priorityKey, params string[] sublookuppaths)
        {
            var pool = new ServicePool(priorityKey, sublookuppaths);
            ServicePool.Instance = pool;

            if (ServicePool.ApplicationContainer == null)
                ServicePool.ApplicationContainer = new ApplicationContext(
                    ImportPool.Instance, MessageBroker.Instance, pool, null, VisualModuleManager.Instance);

            pool.InitLoading();
            return pool;
        }

        public static async Task<IServicePool> CreatePoolAsync(string priorityKey, params string[] sublookuppaths)
        {
            return await Task.Run(() => CreatePool(priorityKey, sublookuppaths));
        }
    }
}

Another way would be to access static ServicePool.Instance property to do the same as calling CreatePool. The Service pool will start the Enumeration of all files in the Given lookup path by using the priority key to define the Assembly that will be searched with level 1 priority. These files are handled as necessary for the program. They should contain all the basic logic, like the window in a visual application or other starting procedures (later more on that). The process will be done as in the following.

Assume

PriorityKey = "*Module*";

Handling of IApplicationContainer. This interface is provided by the framework itself indicates a service that must be started since it's available. If the process is done this service will be instantiated. For this process, the ServiceExport Attribute contains the optional parameter ForceSyncronism and Priority. If an IApplicationProvider does not provide this information then ForceSyncronism will be false and the priority will be very Low.

The first parameter is very important because it will cause your caller to block as long as there are Services that are not executed.

This info is important when using multiple applications that depend on each other. When ApplicationService A tries to load data from ApplicationService B, but service B is not loaded it will fail and cause strange behaviors. Then remember that just because it will work on your machine does not mean that it will work on every machine. This is caused by the huge impact of Multithreading and Tasking. Every machine decides on its own how tasks and threads are handled.

Usage and meaning of IApplicationProvider. The idea is simple. As far as we have an application that is only connected with loose dependencies over interfaces, there is no starting mechanism either. To support this kind of unobserved starting from the caller, we use this interface. Useful implementations would be a Service that pulls data from a database that must be available at the start like it EF does. So when we think back to our FooDatabaseService, it provides us a method to access a database and preload the request data.

But this brings us to the next problem of a service-based application. The communication between services is a very complex point. As long as we cannot really expect a service to exist, the framework brings its own. This kind of communication is implemented inside the IS service interface.

public interface IService
{
    void OnStart(IApplicationContext application);
}

// Every service contains this starting logic and handles the IApplicationContext. 
// This interface provides basic functions like DataStorage, DataCommunication, ServiceHandling, and more. 
// The idea is that services are isolated from each other and from the main logic of your application. 
// They cannot know you and you don't know them. 
// So the framework provides a way of communication.
public interface IApplicationContext
{
    IDataBroker DataBroker { get; set; }
    IServicePool ServicePool { get; set; }
    IMessageBroker MessageBroker { get; set; }
    IImportPool ImportPool { get; set; }
    IVisualModuleManager VisualModuleManager { get; set; }
}

IDataBroker provides your service with a centralized interface for storing data to be persisted. This is at the current state the only Context helper that is null per default. If you want to provide your services as a DataBroker then you need to set it from the outside or from one of your Services. One possible solution would be.

[ServiceExport(
    descriptor: "AppDataBroker",
    isDefauld: false,
    forceSynchronism: true,
    priority: 0,
    contract: new[] {
        typeof(IDataBroker),
        typeof(IApplicationProvider)
    }
)]
class FooDataBroker : IDataBroker, IApplicationProvider
{
    #region IDataBroker Implementation
    // Implement IDataBroker methods here...
    #endregion

    /// <summary>
    /// This method is called when the service starts.
    /// </summary>
    /// <param name="application"></param>
    void Contracts.Interfaces.Services.IService.OnStart(IApplicationContext application)
    {
        // Do some initial setup like opening a database and pulling application settings...
        // Add yourself as the DataBroker
        application.DataBroker = this;
    }
}

From the top, We define the Export attribute to mark this class as a Service. Force the synchronism and priority to 0 to load this before all other interfaces (depending on your application logic this service might depend also on another). At least we define 2 interfaces to be exported. First IDataBroker so if some other component asks for this interface (inherits also from IService) then it will get this one and the second IApplicationProvider will be called as soon as possible.

The next important thing is the IMessageBroker which allows us to transport data from one customer to another. It has a standard implementation that allows everyone to add himself as a consumer based on a type as a key. If then some other consumer publishes data that is of a type of key, all customers will be notified.

public interface IMessageBroker
{
    void Publish<T>(T message);
    void AddReceiver<T>(Action<T> callback);
}

A useful other implementation would be a service that checks T for some kind of special message and if so it could publish the message instead of locally to a WCF service and spread the message to other customers. This could extend the local application from plugin-based to even remote applications.

So since no one knows the caller, no one would be able to work with other parts of the application. For that general case, every service must understand global ServicePool. To get even rid of this dependency between every Service and the MSEF framework processor, the ServicePool is contained inside the ApplicationContext. With this infrastructure, no service is known of the caller and we centralized all the logic inside the ServicePool. Like the Hollywood principle "Don't call us, we call you" we got an absolutely clear abstraction of every logic.

But this is the most important part for you as the developer. Do not break the law! But there is only one law that you must follow. Do not reference a service directly from another service. Never! if you keep that in mind then you will be very happy and also everyone else will depend on your work.

Last but not least, we will talk about the most basic part, the direct call of IServicePool. How can we obtain a service from the Framework?

// Module is my VisualModule that is invoked before
var infoservice = Module
    // Context is my variable of IApplicationContext
    .Context
    // ServicePool is simply the current instance of IServicePool
    .ServicePool
    // GetSingelService gets a single service of the requested Interface with FirstOrDefault behavior
    .GetSingelService<IFooDatabaseService>();

// Check if null
// If you want to always get a service, mark them as Default and call the Default function
if (infoservice == null)
    return;

// For example, get the last Entry in the ImportPool that logs every activity that is done by ServicePool
var logEntry = Module.Context.ImportPool.LogEntries.Last();

// Call a function on that service that we got
// In this case, we want to insert the Log into the DB
infoservice.Insert(logEntry);

Metadata and Attributes

There 2 ways to use metadata. Based on a Service implementation and based on the service itself. Every service can create its own metadata using ServiceExport and its properties. Or the service (represented by its interface) can define some standard properties that will be applied to all inherited services.

Metadata and Attributes

Figure 2. Service Export Attribute 2

In this case, the service interface does not contain any kind of metadata, just the implementations define metadata on their own.

Service Export Attribute

Figure 3 Inherited Service Export Attributes

In this case, we inverted the metadata away from the implementation to the declaration of the Service. This offers us the following usage.

public class FooMessageBox : IFooMessageBox
{
    Contracts.Interfaces.IApplicationContext Context;

    public void showMessageBox(string text)
    {
        MessageBox.Show("Message from Service: " + text);
    }

    public void OnStart(Contracts.Interfaces.IApplicationContext application)
    {
        Context = application;
    }
}

For this case, we can skip the metadata declaration of the class level.

[InheritedServiceExport("FooExport", typeof(IFooMessageBox))]
public interface IFooMessageBox : IService
{
    void showMessageBox(string text);
}

This has good and bad sides.

Good

We do not need to care about the metadata because we did that once when we created the interface.

Bad

We can no longer control or modify the metadata. Since the attribute needs constant values, every implementation of IFooMessageBox provides the same metadata and for the framework, they are all the same. So we have this problem.

IFooMessageBox

Figure 4. Inherit Service Export Attributes 2

You can see that since we are using the InheritedServiceExportAttribute we don't know who is who because they all declare the same metadata.

Using the code for UI

There is a not-so-deniable usage for a UI application using plugins to extend their surface and logic. Even, for this reason, the framework was originally designed. I talked a lot about how to use the framework in general so now I will introduce you to the possible usage for a UI application.

When setting up a completely new project you need a starting logic that invokes the enumeration of the modules and a directory. So even by creating a new ConsoleApplication, you need a starting logic. But let us get a bit more specific. As it was designed for it, the framework contains an easy-to-use service for applications that are using the MVVM.

It contains an interface IVisualService and a Service IVisualModuleManager. Both builds support MVVM.

The IVisualModuleManager is implemented by the VisualModuleManager and it is most likely a wrapper for the ServicePool that filters for IVisualServices.

A possible usage would be that the executable calls the ServicePool and starts the process. A DLL contains an IApplicationProvider and he will show a window with a list. Then the AppProvider will use the given VisualModuleManager to ask for all instances of IVisualServiceMetadata and populate the list box with it. When selecting an item on this list box, the AppProvider will call VisualModuleManager to create an IVisualService based on the given metadata. Then the other Service will be created and we can access a VisualElement by calling the property View of IVisualService. Last but not least, the VisualServiceManager requires some inherited metadata called VisualServiceExport. If you would like to you can extend both the metadata or the IVisualService to add your own properties or functions.

A proper example with a Ribbon is in GitHub.

Before you start

To ensure that your application and your Models go into the same folder (that is very important because how should MEF know where your DLLs are? ;-), set the BuildPath to the same as the Shell (the default is "..\..\..\..\bin\VS Output\" for Release and "..\..\..\..\bin\VS Debug\" for Debug. Then ensure that you have set the "On-Run When Projects out of date" option to Always build.

Features that you should know

There are 2 Preprocessor Directives that control some of the behavior.

#define PARALLEL4TW

"#define PARALLEL4TW" allows the StrongNameCatalog the use of PLinq to search and include the assemblies in the ¬ catalog with a mix of Eager and Lazy loading. In my opinion, you should not disable this in your version because it is Tested, Saved, and very fast. Just for debugging purposes does this make sense?

#define SECLOADING

As in this link described, the Catalog contains a way to guarantee a small amount of security. I never used this so I cannot say anything about it.

Possible Scenarios

There are many possible scenarios. I will explain some to provide you with some ideas of how the system works and for what kind of work it is designed. We will start with a simple application that does some work. Like a calculator that can multiply numbers. Since the system was designed with some interface logic, it contains an interface named ICalculatorTask. This interface defines only one method which is called Calculate(). This method takes only 2 parameters of type string. There are now 2 ways to implement this plugin approach.

Reimplement ICalculatorTask and Inherit from IService

This approach will cause all existing and future tasks the be "known" as a service. This is sometimes not exactly what we want. This would be the easy way and sometimes the best but this depends on your coding style. By implementing IService and then defining the export attribute, we could load all interfaces at once without any dependency on it.

Inherit from ICalculatorTask and create a Service Adapter

This approach will use the Adapter pattern and will wrap all Service functionality into its own class. That is useful if we don't want to alter the existing code and prevent the target classes the be known that they are really used as Services. To do this we will create a class called CalculatorWrapper. The base calls will take an ICalculatorTask instance and will delegate all tasks to it. Then we inherit from this wrapper class and create a new class called Multiply or every exact instance we are defining.

This simple Scenario would be a good way to use a Plugin-based system.

A proper example with a console and a small calculator is in GitHub.

Points of Interest

I had a lot of fun creating this project and I guess it is worth it, to share not only the idea with other developers and everyone interested in it. When I started with MEF in .NET 4.0 I was driven crazy because the pure MEF system is very complex. I have maintained this project now for more than a year and still get ideas for how to extend or improve it. I would highly appreciate any new ideas and impressions from you.

Also, I would like to hear/see the applications you made with it.

Just contact me here. Thank you for your advice.

GitHub

As suggested by the user John Simmons I cleaned up the repository and removed everything specific to WPF from the trunk solution. Now it only contains the 2 main assemblies.

Help me!

Since I am very interested in improving my skills and the quality of my code, I created a form to receive input from you directly. If you are using this project then please feel free to take it, it will not last longer than a minute.

Project Usage

History

V1: Initial creation

  • V1.1: Minor bugfixes as

V2 Minor changes

New features

  • New function for loading callbacks inside the IImportPool.


Similar Articles