MVP with MEF

Krzysztof had a question on StackOverflow around doing MVP triads wtith MEF. He came up with a way to do it that he wasn’t happy with and which I told him was pretty hacky :) After chatting with Krzysztof I took a shot and came up with something which looks pretty clean.

   1: using System.ComponentModel.Composition;
   2: using System.Reflection;
   3: using Microsoft.VisualStudio.TestTools.UnitTesting;
   4:  
   5: namespace MVPwithMEF
   6: {
   7:     /// <summary>
   8:     /// Summary description for MVPTriadFixture
   9:     /// </summary>
  10:     [TestClass]
  11:     public class MVPTriadFixture
  12:     {
  13:         [TestMethod]
  14:         public void MVPTriadShouldBeProperlyBuilt()
  15:         {
  16:             var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
  17:             var container = new CompositionContainer(catalog.CreateResolver());
  18:             var shell = container.GetExportedObject<Shell>();
  19:             Assert.IsNotNull(shell);
  20:             Assert.IsNotNull(shell.Presenter);
  21:             Assert.IsNotNull(shell.Presenter.View);
  22:             Assert.IsNotNull(shell.Presenter.Model);
  23:         }
  24:     }
  25:  
  26:     [Export]
  27:     public class Shell
  28:     {
  29:         private IPresenter _presenter = null;
  30:         
  31:         public IPresenter Presenter
  32:         {
  33:             get { return _presenter; }
  34:         }
  35:  
  36:         [ImportingConstructor]
  37:         public Shell(IPresenter presenter)
  38:         {
  39:             _presenter = presenter;
  40:         }
  41:     }
  42:  
  43:     public interface IModel
  44:     {
  45:     }
  46:  
  47:     [Export(typeof(IModel))]
  48:     public class Model : IModel
  49:     {
  50:         
  51:     }
  52:  
  53:     public interface IView
  54:     {
  55:     }
  56:  
  57:     [Export(typeof(IView))]
  58:     public class View : IView
  59:     {
  60:     }
  61:  
  62:     public interface IPresenter
  63:     {
  64:         IView View { get;}
  65:         IModel Model { get; }
  66:     }
  67:  
  68:     [Export(typeof(IPresenter))]
  69:     public class Presenter : IPresenter
  70:     {
  71:  
  72:         private IView _view;
  73:         private IModel _model;
  74:  
  75:         [ImportingConstructor]
  76:         public Presenter(IView view, IModel model)
  77:         {
  78:             _view = view;
  79:             _model = model;
  80:         }
  81:  
  82:         public IView View
  83:         {
  84:             get { return _view; }
  85:         }
  86:  
  87:         public IModel Model
  88:         {
  89:             get { return _model; }
  90:         }
  91:  
  92:     }
  93: }

So what’s going on here?

Shell gets injected with Presenter. Presenter gets injected with View and Model. Everything here is singletons, but doesn’t have to be.

The difference between our two examples is that the Presenter is getting injected into the shell rather than the View. If the Presenter is creating the View then you can’t just grab the View first (as he was doing), or the Presenter will not get created. Well you can do it, but you end up hacking it to bits. Cleaner is to just inject the Presenter and have it expose an IView. We did this in Prism and it worked quite well.

Any questions?

Yes, yes this looks DI containerish. You can use MEF for simple DI stuff (as Krz is doing), but as you get to more complex systems, the experience won’t be optimal.

This entry was posted in MEF, MVP, patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.codebetter.com/blogs/glenn.block/ Glenn Block

    Hans depending on how you implement ViewModel, you may or may not need an IView. For example if your View only needs to talk to the VM through DataBinding, then an IView may not be necessary. You can also have the VM raise events that the UI listens to. On the other hand you can also inject an IView to the ViewModel if that is more appealing.

    The one thing to remember when doing VM, is that you are databinding directly to the VM itself, rather than a model hanging off off the Prsenenter as is the case when you do Supervising Controller.

  • Hans

    Nice Post… On question I am looking at the MVVM pattern which is somehow related how is this matching on to your post?… Do I need to create for every view my special interface… for example MainView has IMainView and IMainViewModel… and
    FormView has IFormView and IFormViewModel?
    … because the have obviosly a different interface…

  • http://ctrl-shift-b.blogspot.com Derek

    “If I simply resolve the view in the Shell (which is what was happening), then the Presenter never gets created.”

    Actually, the Presenter does get created in Krzysztof’s example, though it appears to be due to the fact that the presenter is importing an exported type via the constructor. Here is a possibly more concise example of what he is doing:

    [TestClass]
    public class MEFTestFixture
    {
    [TestMethod]
    public void DependencyGetsCreated()
    {
    ComposablePartCatalog catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog.CreateResolver());
    var shell = container.GetExportedObject();
    container.Compose();
    Assert.IsNotNull(shell.View);
    Assert.IsNotNull(shell.View.Presenter);
    }
    }

    [Export]
    public class Shell
    {
    [Import("View")]
    public View View { get; set; }
    }

    [Export(typeof(View))]
    public class View
    {
    public Presenter Presenter { get; set; }
    }

    public class Presenter
    {
    [Export("View")]
    public View view;

    [ImportingConstructor]
    public Presenter(View view)
    {
    this.view = view;
    view.Presenter = this;
    }
    }

    After further consideration, however, I do agree that your way is cleaner given it doesn’t require this maneuvering to get the Presenter instantiated.

  • Bryan Watts

    Yes it does. I’d like to to see if I understand the scenario:

    1. A catalog surfaces part definitions.
    2. A container composes parts.
    3. Presenters are parts defined in some catalog.
    4. Views, if framework-level controls, are “external” parts.
    5. There is a container with the same lifetime as the views.
    6. For each view, add to the container and compose a corresponding presenter.

    Does that sound right?

    “vs. vice-versa”

    Now that’s just fun to say.

  • http://www.codebetter.com/blogs/glenn.block/ Glenn Block

    @Bryan

    No problem, with MEF you can manually add parts that were created for you to the container. So you can take a Page and throw it into the container and have it’s imports satisfied. To do this though you’d be having the View create the Presenter vs vice-versa.

    Make sense?

  • Bryan Watts

    How would you structure this if another runtime stack is responsible for creating view instances, i.e. ASP.NET or XAML?

    I realize this may be a loaded question :-)

  • http://www.codebetter.com/blogs/glenn.block/ Glenn Block

    @Derek

    It’s not that it’s a bad idea, the problem has to do with how DI is performed. In this case the Presenter gets the View injected into it. If I simply resolve the view in the Shell (which is what was happening), then the Presenter never gets created.

    Now there are a few options to work around this, and I chose to make the Shell resolve the Presenter as it was the easiest in fitting with the model.

    a. Make the View resolve the Presenter. If you do this you can just grab the view and the Presenter will get injected into it.
    b. Make sure that the Presenter is first resolved elsewhere which creates the view. Then the shell can resolve the view.

  • http://ctrl-shift-b.blogspot.com Derek

    MVP style arguments aside, I’m not sure I see where Krzysztof’s approach of having the Presenter created implicitly is a less desirable approach. That said, this certainly looks to be a clean approach if following this style of the MVP pattern.

  • http://www.bluespire.com Rob

    Clear, concise and a good example of how you can do things in the real world :) Thanks Glenn!

  • http://www.codebetter.com/blogs/glenn.block/ Glenn Block

    @Mike, MEF is in the core framework so it should work anywhere the famework is present.

    Glenn

  • http://azurecoding.net Mike Brown

    Whoa…that is so clean…will MEF work in Azure?