WPF Caliburn MEF 应用程序和 DI

WPF Caliburn MEF application & DI

我正在尝试在 WPF 应用程序中使用 Caliburn 和 MEF。我的 MEF 知识充其量只是粗略的。

这是引导程序:

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);
  }

}

这是shell视图:

<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>

IShell 接口:

public interface IShell
{
}

这是我的 shell 视图模型:

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

所以当 Boostrapper 覆盖方法时

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

被调用,我的 ShellViewModel 构造函数被调用并且 MenuMessages 视图模型被 Injected.这是一个依赖注入的例子,对吗?

在我的例子中,菜单和消息视图模型是与 shell 一起创建的。 但是如果一个新的视图模型

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

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

在用户按下 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>

UserViewModel 将向 MessagesViewModel 发布事件,我将需要 IEventAggregator。是的,我可以将它显式传递给构造函数,但我想了解的是为什么 MEF 不会注入它。

我是否试图以非预期的方式使用 MEF? MEF 是否仅在应用程序启动时用于那些已知需要的视图模型?

首先,我强烈建议您放弃 MEF 并使用一个不那么笨重且更易于使用的 IoC 容器(例如 SimpleInjector)。但是如果你想走 MEF 路线,你需要在你的 boostrapper class 中导出 EventAggregator,如下所示:

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);
}

你有点糊涂了。首先,我认为您一开始并不是真的想使用 MEF,但我稍后会谈到这一点。您尝试在 LaunchUserViewModel 中创建一个新的视图模型。 MEFDependency Injection 的整个想法是摆脱它。正如您在此处看到的 new UserViewModel() 当您尝试创建一个新实例时,您 必须 提供所有必需的参数。但整个想法是让框架完成工作并为我们提供它们。所以首先我们必须摆脱 new 关键字并考虑以不同的方式实例化它。让我们看看你的例子:

没关系。您导出某种类型的模块。

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

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

public interface IUserViewModel {
    void SomeMethod();
}

那么我们在导出东西的时候一般会做什么呢?我们将其导入其他地方:

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

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

如您所见,我们已完成删除 new 关键字。这意味着 ShellViewModel 不知道 class 到底要导入什么。这就是它的美妙之处。您可以随时交换实现,而不必更改 ShellViewModel 这里发生的事情是 AddExportedValue<IEventAggregator>(new EventAggregator()); 通知 MEF,嘿,这里有一个 class 可能想要的东西。稍后当您执行 [ImportingConstructor] 时,MEF 会查找构造函数需要的 classes,如果有它们,它会为您注入它们。

现在,我认为您只是想要 Dependency Injection 而进入了错误的框架。虽然 MEF 允许你使用 DI,但它更强大。它使用模块的概念,以便您可以构建模块化应用程序。当您希望允许人们为您的应用程序编写插件时,它非常有用。

它看起来像: 您只会向插件显示您希望它们显示的内容,因此您将创建一个名为 API 的项目,该项目将仅存储合同(接口)。然后您将拥有程序的 Core,而用户将提供 Plugins。用户只会看到 API,而不是 Core,并且您加载他们的插件时会知道他们实际上实现了您告诉他们要实现的 API 接口。

您不需要 MEF 即可使用 Dependency InjectionCaliburn.Micro 内置了容器,但您也可以使用 Simple Injector, Ninject,等等。

我希望这是有道理的。这是一个很大的课题。

编辑:

我创建了一个非常基本的 project ,它使用 Caliburn.Micro 和依赖注入。也许它会帮助你更好地理解它。