Search code examples
c#wpfmvvmdata-bindingmef

How to bind data from host to plugin


How do I bind data from host to a plugin with MEF?

So the thing is:

  • I work with MVVM so I have my Models, ViewModels and Views.
  • I want to use MEF to be able to expand my application.
  • I want to store all the data in the MainViewModel so every plugin can work with the actual data.
  • The plugin is a UserControl wich will be displayed as a ContentControl in the MainViewModel.

What I have so far:

  • MainViewModel
  • Models
  • Databinding from MainViewModel to View.
  • Import plugins from folder X

What I need: - the plugins need to bind the data from the MainViewModel to the plugin UI. - changing the property in the plugin UI must update the data in the MainViewModel and update the UI from all other plugins.

The PluginInterfaces:

public interface IPlugin
{

}
   public interface IPluginData
{
   string Name { get; }
}

The MainViewModel: (part of it)

private MyModel myfirstmodel; 
private DirectoryCatalog catalog;
private CompositionContainer container;

[ImportMany] 
IEnumerable<Lazy<IPlugin, IPluginData>> Plugins;

public MainWindowViewModel()
{
    string pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    pluginPath = Path.Combine(pluginPath, "plugins");
    if (!Directory.Exists(pluginPath))
    Directory.CreateDirectory(pluginPath);
    catalog = new DirectoryCatalog(pluginPath, "*.dll");
    container = new CompositionContainer(catalog);

    try
    {
        this.container.ComposeParts(this);
    }
    catch (CompositionException compositionException)
    {
        Console.WriteLine(compositionException.ToString());
    }
}

The Model

public class MyModel
{
    private string message;
    private int number;
    private DateTime date;

    public string Message { get { return message; } set { message = value; } }
    public int Number { get { return number; } set { number = value; } }
    public DateTime Date { get { return date; } set { date = value; } }
}

The Plugin

[Export(typeof(IPlugin))]
[ExportMetadata("Name", "MyFirstPlugin")]
public partial class MyFirstPlugin : UserControl, IPlugin
{

    public MyFirstPlugin()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //Change the message in MainWindowViewModel and the date when it gets changed.
    }
}

I tried using INotifyPropertyChanged but did not came that far..

Does anybody got a really good tutorial for that or can show me how to do this? I would appreciate a "how to" and not just a "just use INotifyPropertyChanged".

Is this even possible?


Solution

  • Personally I think you're going about this the wrong way, MEF was specifically designed to do this type of plumbing so that you don't have to.

    In a proper MVVM application the views usually get created by way of data templates, and even if you don't do this there is usually other global data you need to import like brushes and behaviours etc. And if you do use templates (which you really should be doing) then you don't need to explicitly export your controls; simply referencing them by the templates will result in them getting imported as well.

    You can achieve this in practice with another pass of the importer. Give each plugin a custom ResourceDictionary and put all the global data/templates/UI resources etc in there (so it's effectively the plugin's version of App.xaml). The trick is to then give those ResourceDictionaries their own code-behind file and decorate those classes with Export. Then all your main application has to do is Import all the classes of type ResourceDictionary (or preferably a derived class or interface that returns the dictionary) and add each one to Application.Current.Resources.MergedDictionaries. From that point on the development of your plugins is pretty much the same as if they were in a DLL project that you were statically linking the usual way.