Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use MEF to allow plugins to override existing functionality?

Tags:

c#

mef

I'm using MEF to allow users to extend my C# library. It's working great so far, but right now I'm trying to use it in a way I haven't seen it used before.

The primary use case for MEF I've seen so far is this:

  • Application exposes primitive interface (IPerson)
  • External library uses MEF and primitive interfaces to extend functionality of main library (e.g. IPoliceman : IPerson, adds functionality)
  • Application then uses ImportMany to search for correct IPerson depending on what it must do

But I need something like this: Let's say I have a tax calculator that takes a bunch of parameters and returns estimated tax depending on those parameters. I want users to be able to create plugins with MEF that modify how those calculations are done. Only one plugin that does this should be able to be loaded at any one time. Otherwise, how do I decide which alternate implementation to use?

So basically, my question boils down to this: Usually MEF allows adding implementations of classes and methods. How do I use it to allow users to replace an implementation?

like image 907
Chris Laplante Avatar asked Jun 25 '12 20:06

Chris Laplante


3 Answers

Normally when you try to override an export which is already present in the application, you will get a cardinality exception for an [Import(typeof(IFoo)] because MEF expects exactly one matching export to be available.

However, you can put your plugins in a separate export provider and give it priority. Here I do that for a "plugins" subfolder inside the application folder:

Assembly executingAssembly = Assembly.GetExecutingAssembly();
string exeLocation = Path.GetDirectoryName(executingAssembly.Location);
string pluginPath = Path.Combine(exeLocation, "plugins");

var pluginCatalog = new DirectoryCatalog(pluginPath);
var pluginExportProvider = new CatalogExportProvider(pluginCatalog);

var appCatalog = new DirectoryCatalog(exeLocation,"*");
var appExportProvider = new CatalogExportProvider(appCatalog);

var container = new CompositionContainer(
    pluginExportProvider, appExportProvider);

pluginExportProvider.SourceProvider = container;
appExportProvider.SourceProvider = container;

The order of the export providers as passed to the composition container determines the priority: if an export is provided by both the plugins and the application parts, then the plugins will get priority.

like image 89
Wim Coenen Avatar answered Nov 03 '22 08:11

Wim Coenen


What you're talking about is actually just a different way of looking at the same problem. The answer is simpler than it sounds - for any behavior that you want a client to be able to override, just put that behavior in a plugin.

There's nothing that says you can't write plugins just because you're the author of the application. Put your TaxCalculator class in a plugin, and expose an interface allowing users to write their own tax calculators. At runtime, if you have more than one loaded, favor the one that isn't yours. Out-of-the-box, you will be using your tax calculator plugin, so it will work exactly the way you expect. If the user creates their own tax calculator plugin and drops it in the right directory, you use it instead, effectively allowing them to "override" your original functionality.

like image 21
Tim Copenhaver Avatar answered Nov 03 '22 08:11

Tim Copenhaver


I'm not sure how much sense is going to make, but let me try.

I would make a TaxCalculatorManager class. That class could load all of the ITaxCalculator implementations from MEF. From there, you could have something in the Export attribute that would allow ranking of the implementations. Then when you need to calculate the taxes, you would call TaxCalculatorManager.Calculate which would rank the ITaxCalculator implementations and call Calculate on the winner.

Let me know if you need me to clarify any points.

like image 32
cadrell0 Avatar answered Nov 03 '22 08:11

cadrell0