Jana Andropov
Jana Andropov

Reputation: 139

WPF Caliburn MEF application & DI

I'm experimenting with using Caliburn and MEF in a WPF application. My MEF knowledge is sketchy at best.

Here's the Bootstrapper:

class Bootstrapper : BootstrapperBase
{
  public Bootstrapper()
  {
     Initialize();
  }

  protected override void OnStartup(object sender, StartupEventArgs e)
  {
     DisplayRootViewFor<IShell>();
  }

  protected override void Configure()
  {
     //An aggregate catalog that combines multiple catalogs  
     var catalog = new AggregateCatalog();

     //Adds all the parts found in the same assembly as the Program class  
     catalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));

     //Create the CompositionContainer with the parts in the catalog  
     container = new CompositionContainer(catalog);

     var batch = new CompositionBatch();
     batch.AddExportedValue<IWindowManager>(new WindowManager());
     batch.AddExportedValue<IEventAggregator>(new EventAggregator());
     batch.AddExportedValue(container);

     container.Compose(batch);
  }

  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.Any())
        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);
  }

}

Here's the shellview:

<Window x:Class="MefCaliburn.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MefCaliburn"
    mc:Ignorable="d"
    Title="ShellView" Height="300" Width="300">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition/>
        <RowDefinition Height="100"/>
    </Grid.RowDefinitions>

    <ContentControl Grid.Row="0" x:Name="Menu"></ContentControl>
    <ContentControl Grid.Row="1"></ContentControl>
    <ContentControl Grid.Row="2" x:Name="Messages"></ContentControl>

</Grid>

The IShell interface:

public interface IShell
{
}

Here's my shell view model:

namespace MefCaliburn
{
[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
  private readonly IEventAggregator _events;

  UserViewModel uvm;

  [ImportingConstructor]   
  public ShellViewModel(MenuViewModel menu, MessagesViewModel mess, IEventAggregator eventaggregator)
  {
     Messages = mess;
     Menu = menu;
     _events = eventaggregator;
  }

  public MessagesViewModel Messages
  {
     get; set;
  }

  public MenuViewModel Menu
  {
     get; set;
  }

  public void LaunchUserViewModel()
  {
     uvm = new UserViewModel();
  }
}
}

So when the Boostrapper overridden method

  protected override void OnStartup(object sender, StartupEventArgs e)
  {
     DisplayRootViewFor<IShell>();
  }

gets called, my ShellViewModel constructor is called and the Menu and Messages view models are Injected. This is an example of dependency injection, correct?

In my case the Menu & Messages view models are created along with the shell. But if a new view model

[Export(typeof(UserViewModel))]
public class UserViewModel : PropertyChangedBase
{
  private readonly IEventAggregator _events;

  [ImportingConstructor]
  public UserViewModel(IEventAggregator eventaggregator)
  {
     _events = eventaggregator;
  }
}

is created when the user presses a button on the MenuView.xaml

<UserControl x:Class="MefCaliburn.MenuView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:MefCaliburn"
         xmlns:cal="http://www.caliburnproject.org"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <StackPanel Orientation="Horizontal">
        <Button Content="ONE" cal:Message.Attach="[Event Click] = [Action LaunchUserViewModel()]"></Button>
        <Button Content="TWO"></Button>
    </StackPanel>
</Grid>

This UserViewModel will be publishing events to the MessagesViewModel, I'm going to need the IEventAggregator. Yes, I can just pass it in explicitly to the constructor, but what I'm trying to understand is why MEF won't inject it.

Am I trying to use MEF in a manner it's not intended? Is MEF only meant to be used when the application starts up for those view models that are known to be required?

Upvotes: 3

Views: 1308

Answers (2)

FCin
FCin

Reputation: 3915

You are a little confused. First of all I think that you don't really want to be using MEF in the first place, but I will get to that later. You try to create a new viewmodel in your LaunchUserViewModel. The whole idea of MEF and Dependency Injection is to get rid of it. As you can see here new UserViewModel() when you try to create a new instance you have to supply all the required parameters. But the whole idea is to let the framework do the work and supply them for us. So first of all we have to get rid of the new keyword and think about instantiating it differently. Let's look at your example:

This is ok. You Export a module of certain type.

[Export(typeof(IUserViewModel))]
public class UserViewModel : PropertyChangedBase
{
  private readonly IEventAggregator _events;

  [ImportingConstructor]
  public UserViewModel(IEventAggregator eventaggregator)
  {
     _events = eventaggregator;
  }
}

public interface IUserViewModel {
    void SomeMethod();
}

So what do we usually do when we export something? We import it somewhere else:

[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
    [Import]
    public IUserViewModel Uvm { get; set; }

   public void LaunchUserViewModel()
   {
      Uvm.SomeMethod(); // Your Uvm is already created 
   } 
}

As you can see we completed removed new keyword. This means that the ShellViewModel doesn't know what class exactly it's gonna import. And that's the beauty of it. You can swap implementations any time you want, and you don't have to change ShellViewModel What happens here is AddExportedValue<IEventAggregator>(new EventAggregator()); notifies MEF, hey, here's a class that something might want. Later when you do [ImportingConstructor], MEF looks for the classes that the constructor needs and if it has them, it injects them for you.

Now, I think you just wanted Dependency Injection and got into the wrong framework. Although MEF allows you to use DI, it is much more powerful. It uses a concept of modules so that you can build modular applications. It is very useful when you want to allow people to write plugins for your application.

What it would look like is: You would only show plugins what you want them to show, so you would create a project called API which would store only contracts(interfaces). Then you would have you program's Core and users would supply Plugins. Users would only see API, not Core, and you would load their plugins knowing that they actually implement API interfaces you told them to implement.

You don't need MEF to use Dependency Injection. Caliburn.Micro has built in container, but you can also use Simple Injector, Ninject, and many more.

I hope it makes sense. It's a vast subject.

EDIT:

I created a very basic project that uses Caliburn.Micro with Dependecy injection. Maybe it will help you to understand it better.

Upvotes: 3

Eternal21
Eternal21

Reputation: 4664

First of all, I highly recommend you give up MEF and use an IoC container that's much less clunky and easier to use (like SimpleInjector). But if you want to go MEF route you will need to export the EventAggregator inside your boostrapper class as following:

private CompositionContainer _container;

protected override void Configure()
{
   _container = new CompositionContainer(new ApplicationCatalog());

   var batch = new CompositionBatch();

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

   _container.Compose(batch);
}

Upvotes: 3

Related Questions