I have tried the following very good tutorial https://www.eidias.com/blog/2013/7/26/plugins-in-wpf-mvvm-with-mef#cm-249 to migrate to MEF2 but for some reason the assemblies are not shown in the catalog. From MEF2 I wanted to use the API Configuration (RegistrationBuilder class) (here an example: https://stefanhenneken.wordpress.com/2013/01/21/mef-teil-11-neuerungen-unter-net-4-5/ ), maybe somebody has an idea how to apply MEF2 correctly to the tutorial. Thank you very much.
here the overview of the solution:
In the MainViewModel.cs I don't know yet how to integrate the imports into the RegistrationBuilder
.Can you check the rest of the code? Thanks.
namespace WPF_MEF_App
{
public class MainWindowModel : NotifyModelBase
{
public ICommand ImportPluginCommand { get; protected set; }
private IView PluginViewVar;
[Import(typeof(IView), AllowRecomposition = true, AllowDefault = true)]
public IView PluginView
{
get { return PluginViewVar; }
set{ PluginViewVar = value; NotifyChangedThis();}
}
[ImportMany(typeof(IView), AllowRecomposition = true)]
public IEnumerable<Lazy<IView>> Plugins;
private AggregateCatalog catalog;
private CompositionContainer container;
public MainWindowModel()
{
ImportPluginCommand = new DelegateCommand(ImportPluginExecute);
RegistrationBuilder builder = new RegistrationBuilder();
builder.ForType<PluginSecondScreen>()
.Export<IView>(eb =>
{
eb.AddMetadata("Name", "PluginSecond");
})
.SetCreationPolicy(CreationPolicy.Any);
//.ImportProperties(pi => pi.Name == "IView",
// (pi, ib) => ib.AllowRecomposition());
builder.ForType<CalculatorScreen>()
.Export<IView>(eb =>
{
eb.AddMetadata("Name", "CalculatorScreen");
})
.SetCreationPolicy(CreationPolicy.Any);
//.ImportProperties(pi => pi.Name == "IView",
// (pi, ib) => ib.AllowRecomposition());
catalog = new AggregateCatalog();
string pluginsPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
catalog.Catalogs.Add(new DirectoryCatalog(pluginsPath, "Plugin*.dll"));
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder));
//also we add to a search path a subdirectory plugins
pluginsPath = Path.Combine(pluginsPath, "plugins");
if (!Directory.Exists(pluginsPath))
Directory.CreateDirectory(pluginsPath);
catalog.Catalogs.Add(new DirectoryCatalog(pluginsPath, "Plugin*.dll"));
//Create the CompositionContainer with the parts in the catalog.
container = new CompositionContainer(catalog);
}
private void ImportPluginExecute()
{
//refresh catalog for any changes in plugins
//catalog.Refresh();
//Fill the imports of this object
//finds imports and fills in all preperties decorated
//with Import attribute in this instance
container.ComposeParts(this);
//another option
//container.SatisfyImportsOnce(this);
}
}
}
Here are the two plugins:
I have already commented the exports here, because they are no longer needed for RegistrationBuilder
.
I checked your attempt. A few points that need improvement.
ExportFactory<TImport>
(never pass around the container).get
and set
to the definitions of PluginView
and Plugins
.Import
attribute from all properties in the MainWindowModel
. IView
and a single import (cardinality). You should either import a collection of concrete types, register only a single concrete type or introduce a specialized interface for each concrete type (e.g. PluginSecondScreen
and ICalculatorScreen
) where each interface inherits the shared interface (e.g. IView
).CompositionContainer
after you are done with initialization.SetCreationPolicy(CreationPolicy.Any)
is redundant as CreationPolicy.Any
is the default value which usually defaults to CreationPolicy.Shared
.string
literals when using class or class member or type names. Use nameof
instead:ImportProperties(pi => pi.Name == "Plugins")
ImportProperties(pi => pi.Name == nameof(MainWindowModel.Plugins)
. This makes refactoring a lot easier.MainWindowModel.cs
class MainWindowModel
{
// Import a unique matching type or import a collection of all matching types (see below).
// Alternatively let the property return IView and initialize it form the constructor,
// by selecting an instance from the `Plugins` property.
public IPluginSecondScreen PluginView { get; set; }
// Import many (implicit)
public IEnumerable<Lazy<IView>> Plugins { get; set; }
}
Interfaces IView
and specializations in order to create unique types:
interface IView
{
}
interface IPluginSecondScreen : IView
{
}
interface ICalculatorScreen : IView
{
}
Interface implementations:
class PluginSecondScreen : UserControl, IPluginSecondScreen
{
}
class CalculatorScreen : UserControl, ICalculatorScreen
{
}
Initialize the application from App.xaml.cs using the Application.Startup
event handler:
private void Run(object sender, StartupEventArgs e)
{
RegistrationBuilder builder = new RegistrationBuilder();
builder.ForTypesDerivedFrom<IView>()
.ExportInterfaces();
builder.ForType<MainWindowModel>()
.Export()
.ImportProperties(
propertyInfo => propertyInfo.Name.Equals(nameof(MainWindowModel.Plugins), StringComparison.OrdinalIgnoreCase)
|| propertyInfo.Name.Equals(nameof(MainWindowModel.PluginView), StringComparison.OrdinalIgnoreCase));
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder));
catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "InternalShared.dll", builder));
catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "PluginCalculator.dll", builder));
catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory, "PluginSecond.dll", builder));
using (var container = new CompositionContainer(catalog))
{
MainWindowModel mainWindowModel = container.GetExportedValue<MainWindowModel>();
this.MainWindow = new MainWindow() { DataContext = mainWindowModel };
this.MainWindow.Show();
}
}