How to load assemblies at runtime using Microsoft Extensibility Framework in C#
By FoxLearn 2/4/2025 4:15:06 AM 91
MEF simplifies the process by automatically discovering and creating instances of exported types, which is an easier alternative to more manual approaches like using AssemblyLoadContext
.
Let’s look at an example where we load an instance of IShapePlugin
from an assembly located in the C:\Plugins
directory.
Loading and using a shape plugin
using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; // Step 1 - Create an aggregate catalog var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Plugins")); // Step 2 - Create the composition container var container = new CompositionContainer(catalog); // Step 3 - Get an instance of the exported type try { var plugin = container.GetExportedValue<IShapePlugin>(); plugin.Draw(); } catch (CompositionException ex) { Console.WriteLine(ex); }
Exporting Types with MEF
To let MEF know which types can be used to create instances, you mark the type with the Export
attribute. Here’s an example of the shape plugin:
using System.ComponentModel.Composition; [Export(typeof(IShapePlugin))] public class Circle : IShapePlugin { public void Draw() { Console.WriteLine("Drawing a Circle"); } }
In this section, I’ll walk you through how to use MEF in various scenarios, from loading specific assemblies to handling dependencies.
Lazy vs Eager Initialization
You can choose between lazy and eager initialization for your exported instances.
Lazy Initialization: The object is only created when accessed for the first time. This can optimize memory usage.
Lazy<IShapePlugin> lazyPlugin = container.GetExport<IShapePlugin>(); // Use the lazy instance later lazyPlugin.Value.Draw();
Eager Initialization: The object is created immediately upon request.
IShapePlugin plugin = container.GetExportedValue<IShapePlugin>(); plugin.Draw();
Loading a Specific Assembly
You can restrict MEF to load types only from a specific assembly using the searchPattern
parameter. For example, if you want to load plugins from the assembly ShapePluginLib.dll
, you can do it like this:
var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Plugins", searchPattern: "ShapePluginLib.dll"));
You can also use wildcards to match multiple files, like this:
var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Plugins", searchPattern: "*ShapePluginLib.dll"));
Loading from a Relative Path
Relative paths can be used to load assemblies. If your plugins are in a subfolder or at a different level in your directory structure, you can load them like this:
var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog("Plugins"));
If the Plugins
folder is located at the same level as your application, you can use ..\
to go up one directory level:
var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"..\Plugins"));
Handling Dependencies
When an exported type has dependencies, MEF will attempt to inject them automatically if they’re also exported types.
Let’s say Circle
(in ShapePluginLib.dll
) depends on a logging service (ILogger
from LoggingLib.dll
). First, you export the logger:
using System.ComponentModel.Composition; [Export(typeof(ILogger))] public class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine($"Log: {message}"); } }
Next, you modify the Circle
class to import ILogger
:
using System.ComponentModel.Composition; [Export(typeof(IShapePlugin))] public class Circle : IShapePlugin { [Import] public ILogger Logger { get; set; } public void Draw() { Logger.Log("Drawing a Circle"); Console.WriteLine("Drawing a Circle"); } }
Using Constructor Injection
You can use constructor injection to provide dependencies instead of property injection.
Here’s how you can modify Circle
to use constructor injection for ILogger
:
using System.ComponentModel.Composition; [Export(typeof(IShapePlugin))] public class Circle : IShapePlugin { public ILogger Logger { get; set; } [ImportingConstructor] public Circle(ILogger logger) { Logger = logger; } public void Draw() { Logger.Log("Drawing a Circle"); Console.WriteLine("Drawing a Circle"); } }
Full Example: Loading Multiple Plugins
In this example, we’ll load multiple plugins from assemblies located in C:\Plugins
. One of the plugins has a dependency, and the other is independent.
The interface IShapePlugin
is defined in CommonLib.dll
:
public interface IShapePlugin { void Draw(); }
Here’s how the Rectangle
plugin (without dependencies) is defined:
using System.ComponentModel.Composition; [Export(typeof(IShapePlugin))] public class Rectangle : IShapePlugin { public void Draw() { Console.WriteLine("Drawing a Rectangle"); } }
Here’s a plugin with a dependency on ILogger
:
using System.ComponentModel.Composition; [Export(typeof(IShapePlugin))] public class Circle : IShapePlugin { [Import] public ILogger Logger { get; set; } public void Draw() { Logger.Log("Drawing a Circle"); Console.WriteLine("Drawing a Circle"); } }
And now, here’s the console application that loads all plugins and uses them:
using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; static void Main(string[] args) { // Step 1 - Create aggregate catalog var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Plugins")); // Step 2 - Create container var container = new CompositionContainer(catalog); // Step 3 - Load all instances var plugins = new List<IShapePlugin>(); foreach (var lazyPlugin in container.GetExports<IShapePlugin>()) { try { plugins.Add(lazyPlugin.Value); } catch (CompositionException ex) { Console.WriteLine(ex); } } // Step 4 - Use the instances elsewhere foreach (var plugin in plugins) { plugin.Draw(); } Console.ReadKey(); }
If everything is set up correctly, this is the expected output:
Drawing a Rectangle Log: Drawing a Circle Drawing a Circle
This example demonstrates how MEF can dynamically load and instantiate plugins, whether they have dependencies or not, and shows how to configure it to suit different use cases like loading from specific directories or handling dependencies.
- Using the OrderBy and OrderByDescending in LINQ
- Querying with LINQ
- Optimizing Performance with Compiled Queries in LINQ
- MinBy() and MaxBy() Extension Methods in .NET
- SortBy, FilterBy, and CombineBy in NET 9
- Exploring Hybrid Caching in .NET 9.0
- Using Entity Framework with IDbContext in .NET 9.0
- Primitive types in C#