Send objects from one view model to another view model as argument of constructor [MEF, Caliburn.Micro, Abstract Factory Pattern]

Dec 17, 2010 at 8:38 PM

Hi I create view-models from shell-view-model with abstract factory pattern. I need inject in view-models class from external assembly. On injection I use MEF. Problem is imported classes in view-models are null. As MVVM framework I use Caliburn.Micro.

Shell-view-model:

public interface IViewModelFactory
{
  ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel);
  IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel);
}

[Export(typeof(IViewModelFactory))]
public class DefaulFactoryViewModel : IViewModelFactory
{
  #region Implementation of IViewModelFactory

  public ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel)
  {
    return new LogOnViewModel(shellViewModel);
  }

  public IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel, PokecAccount account)
  {
    return new MessengerViewModel(shellViewModel, account);
  }

  #endregion
}


public interface IShellViewModel
{
  void ShowLogOnView();
  void ShowMessengerView(PokecAccount account);
}

[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>, IShellViewModel
{

  private readonly IViewModelFactory _factory;

  [ImportingConstructor]
  public ShellViewModel(IViewModelFactory factory)
  {
    _factory = factory;
    ShowLogOnView();
  }

  public void ShowLogOnView()
  {
    var model = _factory.CreateLogOnViewModel(this);
    // var model = IoC.Get<LogOnViewModel>();
    ActivateItem(model);
  }

  public void ShowMessengerView(PokecAccount account)
  {
    var model = _factory.CreateMessengerViewModel(this,account);
    ActivateItem(model);
  }
}


View-model:

public class LogOnViewModel : Screen, ILogOnViewModel
{
  [Import]//inject class from external assembly, 
  private IPokecConnection _pokecConn;//after creation of LogOnViewModel is still null

  private PokecAccount _account;

  private readonly IShellViewModel _shellViewModel = null;

  private User _user = null;

  public LogOnViewModel(IShellViewModel shellViewModel)
  {

    _shellViewModel = shellViewModel;

    _user = new User();

    _account = new PokecAccount();
  }

  public void CreateNewViewModel
  {
    //create new view model and pass object _account to the constructor of MessengerViewModel
    _shellViewModel.ShowMessengerView(_account)
  }
}


MEF BOOTSTRAPER CLASS:

public class MefBootStrapper : Bootstrapper<IShellViewModel>
{
#region Fields
private CompositionContainer _container;
#endregion

#region Overrides
protected override void Configure()
{ // configure container
#if SILVERLIGHT
_container = CompositionHost.Initialize(
new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
#else

var catalog =
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

//add external DLL
catalog.Catalogs.Add(
new AssemblyCatalog(string.Format(
CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), @"\Pokec_Toolkit.dll")));

_container = new CompositionContainer(catalog);
#endif

var batch = new CompositionBatch();

batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);

_container.Compose(batch);
_container.SatisfyImportsOnce(this);
}

protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = _container.GetExportedValues<object>(contract);

if (exports.Count() > 0)
return exports.First();

throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}

protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}

protected override void BuildUp(object instance)
{
_container.SatisfyImportsOnce(instance);
}
#endregion
}

I use abstract factory pattern because I would like send some variable(s) from view-model-1 (LogOnViewModel)  to another view-model-2 as argument of costructor of view-model-2 (MessengerViewModel).

So I have question:

What is the most suitable solution in my case. In LogOnViewModel I create new view-model MessengerViewModel. I need send some object from LogOnViewModel to MessengerViewModel. And close LogOnViewModel.  I thinking about use EventAggregator class.

I am newbie in MVVM, WPF so I like to advise from you. Thank for help.

I found solution, where they call

  _container.SatisfyImportsOnce(); 

but I have MEF in external class, so I don’t know how solve my problem.

Coordinator
Dec 17, 2010 at 8:55 PM

My guess is that the reason that your imports are null is because the exports aren't being registered with MEF. You mentioned that they live in external assemblies. You should make sure to override the Bootstrapper's SelectAssemblies method and return instances of all the assemblies that contain exports.

Dec 18, 2010 at 8:05 AM
EisenbergEffect wrote:

My guess is that the reason that your imports are null is because the exports aren't being registered with MEF. You mentioned that they live in external assemblies. You should make sure to override the Bootstrapper's SelectAssemblies method and return instances of all the assemblies that contain exports.

Sorry, I do not know what you exactly mean. I add debuger point in:


        var catalog =
            new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

        //add external DLL
        catalog.Catalogs.Add(
            new AssemblyCatalog(string.Format(
                CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), @"\Pokec_Toolkit.dll")));

        _container = new CompositionContainer(catalog);
    #endif

        var batch = new CompositionBatch();

        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(_container);

        _container.Compose(batch); //DEBUG POINT

 

And in container contains assembly catalog and assembly catalog consist all types from external assembly. Sorry I am newbie in caliburn. Can you show me a code example, please?


Coordinator
Dec 18, 2010 at 2:07 PM

I don't really know a lot about MEF, so I'm not sure I'm the best person to help solve the problem. I did notice that your import is a private field. Is that allowed?

Dec 19, 2010 at 2:58 PM

This I try first, but it doesn’t work...i must try other way how solve it

Dec 27, 2010 at 9:17 PM
Edited Dec 27, 2010 at 9:18 PM
iansvk wrote:
EisenbergEffect wrote:

My guess is that the reason that your imports are null is because the exports aren't being registered with MEF. You mentioned that they live in external assemblies. You should make sure to override the Bootstrapper's SelectAssemblies method and return instances of all the assemblies that contain exports.

Sorry, I do not know what you exactly mean. I add debuger point in:

 

        var catalog =
            new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

        //add external DLL
        catalog.Catalogs.Add(
            new AssemblyCatalog(string.Format(
                CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), @"\Pokec_Toolkit.dll")));

        _container = new CompositionContainer(catalog);
    #endif

        var batch = new CompositionBatch();

        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(_container);

        _container.Compose(batch); //DEBUG POINT

 

 

And in container contains assembly catalog and assembly catalog consist all types from external assembly. Sorry I am newbie in caliburn. Can you show me a code example, please?


 

I got this to work, as I had the same problem that you did.    It took me awhile, but here is what I did:

 

1) Here is my bootstrapper to load the Current Assembly as well as everything in my Modules folder.  Notice I do override

        private CompositionContainer container;
        private string ModuleDirectory = @"C:\\Modules";
        protected override void Configure()
        {
            //Add the current assembly and everything from my Module folder to the MEF Catalog
            var catalog = new AggregateCatalog(new DirectoryCatalog(ModuleDirectory), new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            container = new CompositionContainer(catalog);
            var batch = new CompositionBatch();
            batch.AddExportedValue(container);
            container.Compose(batch);
        }

    protected override IEnumerable<System.Reflection.Assembly> SelectAssemblies()
        {
            //Add each external assembly so the search path of Caliburn.Micro to find Views
            foreach (string file in System.IO.Directory.GetFiles(ModuleDirectory, "*.DLL", SearchOption.AllDirectories))
            {
                AssemblySource.Instance.Add((Assembly.LoadFile(file)));
            }
            return AppDomain.CurrentDomain.GetAssemblies();

        }

:

2) Ok, another important thing was on my UserControl in my External Assembly, I did an [Export]  in the View class file.  I had hoped it would find the view without this, but it doesn't seem to be the case:

[Export]
    public partial class StatusBarView : UserControl

3) I make sure I recompile my host project so that my ExecutingAssembly has the changes in step1.

Now, when I run, Caliburn.Micro can access my external ModelView and the associated View