2
\$\begingroup\$

I have a project that uses Caliburn.Micro to implement the MVVM pattern. For IoC, I am using MEF. All the ViewModels are marked with attribute [Export(typeof(IScreen))]. IScreen is defined in Caliburn.Micro.

In my BootStrapper file I have set up MEF like so:

private CompositionContainer container;

protected override void Configure()
{

    //Set up MEF

    container = new CompositionContainer(new AggregateCatalog``AssemblySource.Instance.Select``(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));

    var batch = new CompositionBatch();

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

    ...
}

Now, I show the first ViewModel using:

protected override void OnStartup(object sender, StartupEventArgs e) => DisplayRootViewFor<ViewModels.Main.MainViewModel>();

All that is fine and dandy. Now, the problem starts when I want to switch from one ViewModel to another.

An example of a ViewModel is:

[PartCreationPolicy(CreationPolicy.NonShared), Export(typeof(IScreen))]
sealed class SampleViewModel: ScreenVMwithUoW<IAddItemUoW>
{
    [ImportingConstructor]
    public SampleViewModel(IAddItemUoW uow, IEventAggregator eventAggregator) : base(uow, eventAggregator)
    {

    }

    ...
}

So far so good.

Let's say I want to open SampleViewModel and close my MainViewModel. How do I do it using MEF?

Here is what I am doing now. I am using ExportFactory of MEF. I have a class that holds a collection of ExportFactory, one for each IScreen.

[Export(typeof(IScreenFactory))]
class ScreenFactory : IScreenFactory
{
    private Dictionary<Type, ExportFactory<IScreen>> _screenDictionary { get;  set; }        
    private IEnumerable<ExportFactory<IScreen>> _screenfactoryCollection { get; set; }
    private ExportLifetimeContext<IScreen> _currentScreenLifeTimeCtx { get; set; }        

    [ImportingConstructor]
    public ScreenFactory([ImportMany(AllowRecomposition = true)] IEnumerable<ExportFactory<IScreen>> screenList)
    {
        _screenfactoryCollection = screenList;
        _reportfactoryCollection = reportsList;            
    }

    public IScreen GetScreen(Type t)
    {
        if (_screenDictionary == null)
        {
            PopulateScreenDictionary(_screenfactoryCollection);
        }

        _currentScreenLifeTimeCtx = _screenDictionary[t].CreateExport();                
        return _currentScreenLifeTimeCtx.Value;
    }

    private void PopulateScreenDictionary(IEnumerable<ExportFactory<IScreen>> screenfactories)
    {
        _screenDictionary = screenfactories.ToDictionary(c => c.CreateExport().Value.GetType(), c => c);
    }

    public void DisposeCurrentScreenContext()
    {
       _currentScreenLifeTimeCtx?.Dispose();
    }

    public void Dispose()
    {
        DisposeCurrentScreenContext();
    }
}

Whenever I need a new instance of a ViewModel, I just publish a message using the EventAggregator and the MainViewModel does this:

DeactivateItem(ActiveItem, true);
_vmFactory.DisposeCurrentScreenContext();
ActivateItem(_vmFactory.GetScreen(NextViewModelName));

This CANNOT be the normal way to do so. Also, this adds a huge hit in performance, especially the startup time of the application, when the number of ViewModels increase to about 25-30. That is because of how ExportFactory is messily implemented in the ScreenFactory class.

I would also like to avoid the Service Locator Pattern, if possible. I am also open to using a proper IoC Container instead of MEF, but MEF allows me to add ViewModels with very little effort. Just adding the annotation [Export(typeof(IScreen))] does the job.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.