MVVM with MEF in WPF

Mar 25, 2010 at 11:19 PM

My goal is to program a flexible WPF application. It should be extensible via MEF. The application should be similar to the project "XFileExplorer" in the examples of MEF (preview 9) but with the MVVM pattern.

In the question I asked before I asked how I can have a ViewModel and a View which have no dependencies between each other. It should also be possible to test the application via unit testing. Therefore we can not have dependencies between the view and the ViewModel. Such that we can not set the DataContext ni the Code-Behind file of the View.

My first idea was that the catalog has access to the ViewModel. But this is not so so simple. Attributes aren't good because they have to load the class. To edit the assembly in the client application (Activator.CreateInstance) (connect the view with the viewmodel) is also not good because we obtain these references. A controller like in WAF is the worst idea.

What is the best solution to implement an WPF-MVVM-MEFy application? How do i implement it the best way?

Thanks in advance
int

Mar 26, 2010 at 1:20 AM

Hi int

From a testability standpoint the primary focus is the ViewModel rather than the Views. Your models should contain the bulk of your UI business logic and designed in such a way that they can be tested. User controls (the view) should have minimal logic which only relates specifically to UI concerns. In general you won't need any code in the code-behind as most UI concerns can be addressed through databinding to the ViewModel or through commanding.

You can achieve decoupling between the View and the ViewModel by using MEF. There's several different approaches to achieving this. The approach you choose really depends largely on preference. I'll list a few of them below.

1. Import the ViewModel into the View. The simplest way to do this is to have your UserControl import it's ViewModel and set the datacontext. In general having the View import a concrete ViewModel is absolutely fine as it does not creating coupling from the ViewModel to the view, which inhibits testabilty. In the first example below, MainView is importing MainViewModel and setting it's DataContext.

public class App : Application {
  public Application() {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly));
    catalog.Catalogs.Add(new DirectoryCatalog("Extensions"));
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
    MainView.Show();
  }

  [Import]
  public MainView MainView {get;set;}
}

[Export]
public class MainView : UserControl {
  [Import]
  public class MainViewModel ViewModel {
    get{return (MainViewModel) DataContext;}
    set{ DataContext=value;}
  }
}

[Export]
public class MainViewModel {
}

Above the Application is importing MainView. MainView then imports MainViewModel and sets it's DataContext to the ViewModel. Having the View import it's ViewModel is what is known as "View First"

2. Alternatively you can have the ViewModel import it's View in a "ViewModel First" composition.

public class App : Application {
  public Application() {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly));
    catalog.Catalogs.Add(new DirectoryCatalog("Extensions"));
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
    MainViewModel.View.Show();
  }

  [Import]
  public MainViewModel MainViewModel {get;set;}
}

public interface IMainView { 
  void SetViewModel(MainViewModel model);
}

[Export(typeof(IMainView))]
public class MainView : UserControl, IMainView {
  public void SetViewModel(MainViewModel model) {
    this.DataContext=model;
  }
}

[Export]
public class MainViewModel {
  [ImportingConstructor]
  public MainViewModel(IMainView view) {
    view.SetViewModel(this);
    View = view;
  }

  public IMainView View {get;private set;}
}


Above you can see that app now imports MainViewModel. The ViewModel model imports IMainView an interface that MainView exports. The VM then passes itself to the View by calling the SetViewModel method. This style of wireup is more simliar to an MVP (Model View Presenter) style of wire-up. The advantage of this style is it allows the ViewModel to drive everything. Disadvantage is it's more work to setup the plumbing. It also tends to be less designer (VS Designer) friendly since data will never show in the designer unless the ViewModel is set.

3. Have a seperate class import the View and the ViewModel and marry them together.

public class App : Application {
  public Application() {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly));
    catalog.Catalogs.Add(new DirectoryCatalog("Extensions"));
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
    MainView.DataContext = ViewModel;
    MainView.Show();
  }

  [Import]
  public MainView MainView {get;set;}

  [Import]
  public MainViewModel ViewModel {get;set;}
}

//in the executing assembly
[Export]
public class MainView : UserControl {
}

//in the extensions folder
[Export]
public class MainViewModel {
}

Above the app is importing MainView and ViewModel and wiring them together. The advantage of this approach is it completely decouples the View and ViewModel from one another. Disadvantage is it requires the app (or whoever creates the view) to do the wiring, and it is also not as desing friendly.

4. Use a ViewModel locator. Baiscally it entails creating a class which wraps the container and hangs off of it properties which expose the view model. This class is then put in application resoursces and the control binds to the resource directly. Nikhil Kothari talks a bit about this approach on his blog: http://www.nikhilk.net/ViewModel-View-Hookup-Options.aspx. You can also read more here: http://blogs.xamlninja.com/silverlight/mvvm-design-time-data-and-blendability In this case, to use MEF you would plug it in within the locator.

5. Use attached properties or markup extensions.

1, 3 and 4 are all relatively easy to do out of the box.

Hope this helps

Glenn

 
 
Mar 26, 2010 at 3:07 PM

Can you post a MVVM-MEFy example of the XFileExplorer, please? I think that will help me to understand my problem. :) :) :)

Apr 19, 2010 at 2:13 AM
Edited Apr 28, 2010 at 7:44 PM

[edited]

I resorted to use the View first approach and solved some of my issues using a ViewModel proxy/locator. So ignore the following.

[/edited]

Glen, is it possible to inject the ViewModel into the resources of the View with any of those approaches, so that we can reference it with StaticReference?
Having the ViewModel as the DataContext is nice, but in Silverlight we also need StaticReference(s) to the ViewModel e.g. inside DataTemplates.

Slightly off-topic:

I'm using the ViewModel first approach (with Prism) and noticed that while it works fine at runtime, I can't get it to work at design time, because I currently see no way of programmatically adding the ViewModel into the resources of the View before it initializes its components; which is needed in order to be accessible via StaticReference.
See http://forums.silverlight.net/forums/t/174429.aspx and http://forums.silverlight.net/forums/t/162415.aspx
Does anyone know if this scenario is considered to be supported in the future or if a workaround exists?

Regards,
Kasimier

May 11, 2010 at 6:27 PM
int wrote:

.... A controller like in WAF is the worst idea. ...

Why do you think the controller approach used by the WPF Application Framework (WAF) is the worst idea?

Best Regards,
   jbe