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 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, some base understanding of Inversion of Control and Dependency injection.
Trunk Source includes
JPB.Shell.exe
Contracts.dll
- Shell Interfaces and Attributes host
MEF.dll
Branches Source includes
For now there are 2 existing branches checked into github. All depends on the Main Nuget Package.
Console
JPB.Shell.Example
Console.exe
BasicMathFunctions.dll
- Implementation for a Simple calculator
Contracts.dll
- The application specify 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 relevation Foo.
Contains
- JPB.Shell.
- exe.
- Contains the Loading mechanism but not more.
- CommonApplicationConatiner.dll.
- Contains the a Generic Ribbon UI that loads the other services.
- CommonContracts.
- For interfaces only, this extends IService interfaces to extend the functionality
- VisualServiceScheduler.
- A WPF MVVM Module that provides a Surface that can be used in any other app without rebuilding
- JPB.Foo.
- Client.Module.dll.
- Very simple Visual Module.
- ClientCommonContracts.
- The extension of JPB.Shell.Contracts.
- CommenAppParts.
- Single sample implementation of the extended Interfaces.
This solution should presend you 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 project or Forms. You provide the Modules via MSEF and then load them by accessing there properties.
Introduction
Building, Maintaining and the 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 just a moment in a project when we are think "hmm, this will change in the future, let's use an interface to ensure that the work will be not that big". For that case I introduce a method to get this approach in a new level. Let's talk about the buzzwords Dependency Injection and Inverse of Control (IoC). There 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 the 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. 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.
At the end we had a program that was so big that is was nearly unable to maintain. We decided to redevelop and since we had so many functions inside this application (what was not desired to contain all this functions) we also decided to allow this kind of function "madness".
Then the idea of some kind of Plugin/Module driven application was born inside my head. I'd made some research and found the Managed Extensibility Framework (MEF) inside the .NET Framework. But as fast 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 you can explore 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 the access. Why would this be a good thing? You may ask yourself now. MEF provides you many "extra" functionality and some kind of "Nuclear Arsenal" of Configuration. This is for a developer that has no idea of Dependency injection and IoC far to much. So limiting the configuration and also some things you do not necessarily need to understand, it is the exact right thing. Some configuration was just changed in a more centralized way.
MSEF
The Manged Service Extensibility Framework (MSEF) builds up on MEF and extents the usage and Access to MEF. It wrappes 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 inherits from IService. IService is most likely of an Marker interface with just one definition of a starting method that will be invoked when the Service started 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 the 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
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 IsDefauldService { 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 analysing 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 into your needs then just extend it or wrap it. Just this basic infomation must be provided. Even for the normal usage you are forced to extend it.
Using the codeThe normal usage would be the following.
You define some set of services to create the interface that inherits from IService.
- public interface IFooDatabaseService : IService
- {
-
- 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 impliement it in some DLL and mark it with the ServiceExport Attribute.
- [ServiceExport(
- descriptor: "Sone name",
- contract: new[]
- {
- typeof(IFooDatabaseService),
-
- }
- )]
- 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 be the ServicePool class.
- [DebuggerStepThrough]
- public class ServicePool : IServicePool
- {
- internal ServicePool(string priorityKey, string[] sublookuppaths)
- }
- It only contains a internal constructor and can only created with the ServicePoolFactory.
- 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 new Task<IServicePool>(() => CreatePool(priorityKey, sublookuppaths));
- }
- }
- }
Another way would be to access the 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 Assemblys 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.
AssumePriorityKey = "*Module*"
- Enumerate all DLLs in your shell directory.
- Search for a specific part in the name and flag them as high priority of the name to improve performance. (That just means that DLLs with a name like "FooClientModule.dll" are loaded at startup, but DLLs like "FooClient.dll" are not!)
- Search those high-priority assemblies for exports and add them into my StrongNameCatalog, skip all non-high-priority assemblies.
- Wait for ending of 3.
- Start the default service IApplicationContainer.
- The main window is opening.
- All assemblies without the high-priority flag will be searched for exports.
Handling of IApplicationContainer. This interface is provided by the framework itself indicates a serivce 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 paramenter 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.
- Enumerate all IApplicationProviders that are marked to be executed Async and start them Unobserved.
- Execute all services that are marked to be executed synchronously inside the caller thread.
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 machnine. This is caused be 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 too. 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 thing back to our FooDatabaseService, it provides us a method to access a database and preload the request data.
But this brings us too 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 IService interface.
- public interface IService
- {
- void OnStart(IApplicationContext application);
- }
-
- Every serivce contains this starting logic and handles the IApplicationContext. This interfaces provide us the basic functions like DataStorage, DataCommunication, ServiceHandling and some more. The idee is that services are Isolated from each other and from the Main logic of your application. They can not 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 a centralised 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 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
- ...
- #endregion
-
-
-
-
-
- void Contracts.Interfaces.Services.IService.OnStart(IApplicationContext application)
- {
-
- ...
-
- 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) them it will get this one and second IApplicationProvider will be called as soon as possible.
The next important thing is the IMessageBroker that allows us to transport data from one costumer 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 a 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 the global ServicePool. To get even rid of this dependency between every Serivce 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?
-
- var infoservice = Module
-
- .Context
-
- .ServicePool
-
- .GetSingelService<IFooDatabaseService>();
-
-
-
- if (infoservice == null)
- return;
-
-
- var logEntry = Module.Context.ImportPool.LogEntries.Last();
-
-
-
- infoservice.Insert(logEntry);
Metadata and Attributes
There a 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 the properties. Or the service (represented by its interface) can define some standard properties that will be applied to all inherited services.
Figure 2 Service Export Attribute 2
In this case the service interface does not contain any kind of metadata, just the implementations defines metadata on there own.
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 got this problem.
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 original 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 complete 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 a 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 populates 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 requiers 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 you Models goes 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 guaranty 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 some ideas of how the system is working 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 that is called Calculate(). This method takes only 2 paramters 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 and without any dependency on it.
Inhert from ICalculatorTask and create an 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 what 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 then 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 applications you made with it.
Just contact me here. Thank you in advice.
GitHub
As suggested from 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.
- JPB.Shell.MEF
- JPB.Shell.Contracts
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
- Fixed the MEF container bug that causes a IService method OnLoad to be called multiple times.
- Fixed the improper usage of INotifyPropertyChanged implementation of IServicePool.
- Due the first call, all services are wrapped into a new Lazy instance that will take care of OnStart.
V2 Minor changes
- Removed unnecessary things from the Trunk.
- Made a Console example.
- Added as Nuget Packet (see Top).
New features
- New function for loading callbacks inside the IImportPool.