Search code examples
c#wpfmvvmmef

MEF: Creating a ViewModel in my UserControls


I am currently converting my WPF/MVVM application from Ninject to MEF to take advantage of some plugin architecture. There is no Prism or Unity, nor do I want to go down that path. I'm using VS2015 and .Net 4.6.

I was using the technique that was popular with MVVM Light where you instantiate the ViewModel inside of the XAML with Ninject.

  public static ImageViewModel ImageVM => Kernel.Get<ImageViewModel>();

But now that I'm moving to MEF, I would like to see if there are some alternatives. Most of the currently answered posts on Stack Exchange are fairly old and I'm hoping there are some new alternatives available now that .NET 4.5 is available.


tl;dr

I have a Window that contains 10 UserControls. Each user control has a new instance of a ViewModel attached to it. Since the window is creating the user controls in xaml, how can I get a unique instance of my ViewModel into each control?

public partial class FingerprintControl{

    public FingerprintControl() {
        InitializeComponent();
    }

    [Import]
    public FingerprintControlViewModel ViewModel
    {
        get { return DataContext as FingerprintControlViewModel; }
        set { DataContext = value; }
    }

One suggestion that I saw said to add

    CompositionInitializer.SatisfyImports(this);

after the InitializeComponent(). But that is a Silverlight only class.

I looked at https://www.nuget.org/packages/Microsoft.Composition/ but the documentation for MEF 2 is just incredibly non-existent on the site.

I also saw that ExportFactory was added to MEF 2, but not sure if that would help either.

I did find in MEF 2 the static method CompositionContextExtensions.SatisfyImports, but I don't know what to do with it. The documentation only says, "Satisfies the imports of the specified object from the specified context." (Not real useful...)


Solution

  • We use a class wrapper for Mef with static methods for all our apps:

    public class Mef
    {
        private static CompositionContainer container = null;
        public static CompositionContainer Container { get { return container; } }
    
        private static AggregateCatalog catalog;
    
        public static void Initialize()
        {
            catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.exe"));
            catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.dll"));
            container = new CompositionContainer(catalog);
            StartWatch();
        }
    
        private static void StartWatch()
        {
            var watcher = new FileSystemWatcher() { Path = ".", NotifyFilter = NotifyFilters.LastWrite };
            watcher.Changed += (s, e) =>
            {
                string lName = e.Name.ToLower();
                if (lName.EndsWith(".dll") || lName.EndsWith(".exe"))
                    Refresh();
            };
            watcher.EnableRaisingEvents = true;
        }
    
        public static void Refresh()
        {
            foreach (DirectoryCatalog dCatalog in catalog.Catalogs)
                dCatalog.Refresh();
        }
    }
    

    (Note: We use the above to dynamically load plugins on-demand into our app as required by the user. You may want to use a different catalog system - several to choose from)

    Then we initialize the class early on in the app life-cycle, usually in the App.Xaml code-behind constructor:

        public App()
        {
            this.InitializeComponent();
            Mef.Initialize();
        }
    

    and when I have a base-level mef-import, in the class/code-behind constructor call:

    Mef.Container.SatisfyImports(this);
    

    Hope this helps.