WPF + MEF doesn't work

Jul 29, 2010 at 9:43 AM
Questions:
- System.ComponentModel.Composition et. al. are part of .NET 4.0 so, do I have to download any other tool to use MEF?
- Creating a console program I can easily use ImportAttribute and the objects get hooked into the right places using declaration (ImportAttribute) only but if I try the same in a WPF project all ImportAttribute marked members remain null. I have to declare the members Lazy<> and manually call GetExport<>() 
  Do I really have to do this? 

My Exports in same assembly as consuming code
namespace TestMEFOne {
  [InheritedExport]
  public interface IIntergratedAction {
    List<string> Actions { get; }
    object Invoke(object sender, string actionName, object payload);
  }

  [Export(typeof(VerySimple))]
  public class VerySimple {
    public string GetName() { return "wirds was?"; }
  }
  public class SimpleActions : IIntergratedAction {

    private List<string> _actions = new List<string>();
    public enum MyActions { open, close, author };
    public SimpleActions() {
      _actions.Add("Open");
      _actions.Add("Close");
      _actions.Add("Author");
    }
    public List<string> Actions {
      get {
        return _actions;
      }
    }

    public object Invoke(object sender, string actionName, object payload) {
      switch (actionName) {
        case "Open"return new SimpleActionOpen().Invoke(sender, payload);
        case "Close"return new SimpleActionClose().Invoke(sender, payload);
        case "Author"return new SimpleActionAuthor().Invoke(sender, payload);
        defaultreturn "Command not supported";
      }
    }
    public interface IInternalAction {
      object Invoke(object sender, object payload);
    }
    public class SimpleActionOpen : IInternalAction {
      public object Invoke(object sender, object payload) {
        return "Opened " + (string)payload;
      }
    }
    public class SimpleActionClose : IInternalAction {
      public object Invoke(object sender, object payload) {
        return "Closed " + (string)payload;
      }
    }
    public class SimpleActionAuthor : IInternalAction {
      public object Invoke(object sender, object payload) {
        return "Author " + (string)payload;
      }
    }
  }
}

attempt to import

namespace TestMEFOne {
using System.ComponentModel.Composition;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
[Import(typeof(TestMEFOne.IIntergratedAction))]
public Lazy<IIntergratedAction> _action { getset; }
[Import]
public IIntergratedAction SimpleAction { getset; } // <- this will not get hooked up
[Import]
public Lazy<VerySimple> vs;
public MainWindow() {
vs = App.TheInstance._container.GetExport<VerySimple>();
_action = App.TheInstance._container.GetExport<IIntergratedAction>();
InitializeComponent();
DataContext = _action.Value;
  btnText.Content= vs.Value.GetName();
}

private void btnText_Click(object sender, RoutedEventArgs e) {
MessageBox.Show((string)_action.Value.Invoke(this"Open""theFileToOpen"));
}

private void btnSatify_Click(object sender, RoutedEventArgs e) {
var s ="not defined";
if (SimpleAction != null)
s = (string)SimpleAction.Invoke(sender, "Close""blablabla");
MessageBox.Show(s);
}
}
}

hooking up?
	public partial class App : Application {
public static App TheInstance;
public CompositionContainer _container;
private void Application_Startup(object sender, StartupEventArgs e) {
TheInstance = this;
var catalog = new AggregateCatalog();
//Adds all the parts found in the same assembly as the Program class
catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly));

//Create the CompositionContainer with the parts in the catalog
_container = new CompositionContainer(catalog);
try {
this._container.ComposeParts(this);

catch (CompositionException compositionException) {
Console.WriteLine(compositionException.ToString());
}
}

private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
MessageBox.Show(e.Exception.Message, "arg");
e.Handled = true
}
}
 
Jul 29, 2010 at 11:09 AM

Absolutely not. However, if MEF is not constructing the objects the imports are not satisfied automatically.

Dependency injection (which MEF does) is all about delegating the construction of objects. Some people go as far as to say that the newing of objects is a code smell, I would agree to that to some extent.

Now, the declarative approach to creating views in WPF results in code generation of many strongly typed objects. These types are instantiated by default. You need to swap this instantiation for MEF.

Patterns & Practices has published something called the Composite Client Application Guidance which does not use MEF, but you'll be able to learn a lot of how to approach this by looking into the documentation and code there.

That said, unless your App class is an attributed part, ComposeParts does nothing for you. And what you wanna be doing if you can not delegate the construction of objects to MEF is to use SatisfyImportsOnce rather than querying the container for exports yourself.

Jul 29, 2010 at 11:34 AM

Hi,

do you think you could explicity show which lines have to be changed (and how? ;))

thanks (...)

Jul 29, 2010 at 11:36 AM

with MEF?

Jul 29, 2010 at 1:45 PM

I can, but you should make sure you understand these changes. Why they are needed. There's plenty of really good documentation on these basic concepts.

Change the constructor for MainWindow like this:

public MainWindow() 
{
    InitializeComponent(); // initialize first
    App.TheInstance._container.SatisfyImportsOnce(this); // then satisfy imports
    DataContext = _action.Value;
    btnText.Content= vs.Value.GetName();
}

Jul 29, 2010 at 2:30 PM

Thanks, that worked.

      //App.TheInstance._container.SatisfyImportsOnce(this);  (1)  // this is the MainWindow
      //App.TheInstance._container.ComposeParts(this); (2) // this is the MainWindow

actually either line would've done the trick

As far as I understand (1) will resolve parts (have suitable objects built) for the MainWindow from the _container of parts, (2) will do the same but will also add parts ([export]s) that might be contained in MainWindow as well, hmmm...

The examples are too simple and misleading -> trial & error -> discusison -> trial & error ...

Its a shame that information is treated so poorly: blogs == IST (Information Scattering Technology)

Aug 3, 2010 at 8:24 AM
Edited Aug 3, 2010 at 8:27 AM

I agree,

I actually have a VS test project where I assert the perceived behavior of things. I've had to make assumptions about how things came together and I put all these assumptions in a test, so if my assumptions break, well, then I guess I was wrong. But I would at least notice it.

I found that wrapping my head around all things MEF involved a lot of trial and error. But we're getting there. The next thing on my mind is leveraging MEF to build composite applications.

Aug 3, 2010 at 8:27 AM

That's not a bad approach at all. We have actually seen several customers who have written code to validate that required contracts are present in the system at startup time. If you look in our codeplex drop there is a diagnostics api (I think Microsoft.ComponentModel.Composition.Diagnostics) that you can use for doing such validations.

That api is the underlying plubming for MEFX the diagnostic tool we ship on Codeplex.

Cheers

Glenn