Imported objeсts not created at constructor time

Feb 4, 2010 at 1:00 AM

Hi

I have a strange issue when using MEF toghether with WinForms.
That's how I initialize the MEF run-time 

static class Program
  {
    private static CompositionContainer _container;

    [Import(typeof(IUICore))]
    public static IUICore UICore;

    private static bool Compose()
    {
      AggregateCatalog catalog = new AggregateCatalog();
      catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
      catalog.Catalogs.Add(new AssemblyCatalog(typeof (IEveManagerDb).Assembly));

      _container = new CompositionContainer(catalog);
      _container.ComposeParts();

      try
      {
        UICore = _container.GetExportedValue<IUICore>();
      }
      catch (CompositionException ex)
      {
        MessageBox.Show(ex.ToString());
        return false;
      }
      return true;
    }

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);

      if (!Compose())
        return;

      Application.Run(UICore.MainWindow);

      if (_container != null)
        _container.Dispose();
    }

in main form I have

  [Export(typeof(IUICore))] 
public partial class MainWindow : Form, IUICore { [ImportMany(typeof(IEveManagerView))] private readonly List<IEveManagerView> _views = new List<IEveManagerView>(); [Import] private IEveManagerDb _db;

now if I put a breakpoint in form's constructor I see that _db is null and _views is empty, however if the breakpoint is in OnLoad event the fields are fine. Why so?

 

That makes some troubles cause I have such fields in other classes and I need them to be created at the time constructor is called.
 Do I miss some settings or what am I doing wrong?

Thx

Developer
Feb 4, 2010 at 3:12 AM

One thing that jumps right out at me is that static Import of IUICore. MEF does not support static imports and so that import will never be satisfied. Aside from that I'm not entirely sure when MainWindow is being constructed the first time and by who? My guess is that the call to "_container.GetExportedValue<IUICore>()" is not when MainWindow is being constructed, perhaps it is still setup in the app.xaml file, in which case Xaml is constructing it and not MEF.

Feb 4, 2010 at 3:22 AM

good you mentioned about the statiс field, I was wondering why is always null :)
well, as you can see  lower I have such a code

UICore = _container.GetExportedValue<IUICore>();
so this field is set correctly and the MainWindow gets created find by the line above.
And I'm using WinForms, not WPF .
Well anyway, I figured out, that if I share _container field acrros the application and in my constructor I call
Program._container.ComposeParts(this); 
then all the Import fields are automatically filled with correct values.

However here appears another problems, cause all the other libraries (plugin)  that are supposed to be separate from main project do not have access to that field so my problem is not solved entirely.
What the best approach to work with MEF?
thx 
P.S. I checked the samples.
 
Developer
Feb 4, 2010 at 6:34 PM

Ah after reading your statement "now if I put a breakpoint in form's constructor I see that _db is null and _views is empty, however if the breakpoint is in OnLoad event the fields are fine. Why so?" more closely I understand your problem MEF will not be able to set the fields until the object in constructed so you cannot access the imports in the constructor unless you are using an ImportingConstructorAttribute and the imports you are interested in are the constructor arguments.

We do have a callback which occurs once the imports are satisfied which is to implement IPartImportsSatisfiedNotification, in particular the OnImportsSatisifed method is called when the imports are satisfied.

Hopefully that helps.

Wes

Feb 4, 2010 at 6:48 PM

Looks like this attribute and event do not solve the problem.
Here is my class

  public class PriceCheckPresenter : IPartImportsSatisfiedNotification
  {
    private readonly IPriceCheckView _view;

    [Import]
    private IEveManagerDb _db;

    [ImportingConstructor]
    public PriceCheckPresenter(IEveManagerDb db)
    {
      _db = db;      
    }

    public PriceCheckPresenter(IPriceCheckView view)
    {
      _view = view;

      //Program.Container.ComposeParts(this);
    }

    private bool _needInit;
    public void Init()
    {
      _needInit = true;      
    }

    private void InitRunner(AsyncOperation asyncOperation)
    {
      var r = _db.GetGroups();
      asyncOperation.Post(() => _view.RefreshGroups(r));
    }

    public void Search()
    {
      AsyncHelper.RunAsync(SearchRunner);  
    }

    private void SearchRunner(AsyncOperation asyncOperation)
    {
      asyncOperation.Post(() => _view.RefreshResults(_db.SearchinvType(_view.SelectedGroupId, _view.SearchFilter)));
    }

    #region Implementation of IPartImportsSatisfiedNotification

    public void OnImportsSatisfied()
    {
      if (_needInit)
        AsyncHelper.RunAsync(InitRunner);  
    }

    #endregion
  }

This class gets created in OnLoad method of some custom WinForms UserControl. Control is shown fine in main form.
But none of the importing constructor nor OnImportSatisfied are called, thus my _db field is null and method InitRunner is not called.

Am I doing something wrong?

Thx 

 

Developer
Feb 4, 2010 at 10:38 PM

How is this class being constructed? You have 2 constructors so if I were to guess you are calling new PriceCheckPresenter(view) somewhere in which case MEF is not involved at all and will not statisfy the imports.

Also you don't need to have both an ImportingConstructor and an Import importing the same thing.

Feb 4, 2010 at 10:41 PM

you are right I'm using the ctor that has the View. It looks like I'd have to create a method that receives the View and leave only one constructor that is the ImportingConstrcutor.

Feb 4, 2010 at 10:43 PM

but after that, if having such a constructor 

    [ImportingConstructor]
    public PriceCheckPresenter(IEveManagerDb db)
    {
      _db = db;      
    }
    [ImportingConstructor]
    public PriceCheckPresenter(IEveManagerDb db)
    {
      _db = db;      
    }

what's the correct way to use MEF to initialize the object? 

 

Feb 4, 2010 at 11:02 PM

Thx guys, I managed it out, using the Importing constructor. :)