Prism 2.1:将模块注入ViewModel

5
我一直在尝试将ModuleCatalog中的模块注入到我的Shell的ViewModel中,但我没有太多的运气...
我在Bootstrapper中创建了ModuleCatalog,并且我的模块可以从它的Initializer上得到屏幕展示,没有问题。然而,我希望能够将我的模块列表绑定到一个DataTemplate容器中,以便可以从菜单启动它们!
这是我的Boostrapper文件,我将在不断添加更多模块,但目前,它只包含了我相当刻意的“ProductAModule”:
public class Bootstrapper : UnityBootstrapper
{
    protected override void ConfigureContainer()
    {
        Container.RegisterType<IProductModule>();

        base.ConfigureContainer();
    }

    protected override IModuleCatalog GetModuleCatalog()
    {
        return new ModuleCatalog()
            .AddModule(typeof(ProductAModule));
    }

    protected override DependencyObject CreateShell()
    {
        var view = Container.Resolve<ShellView>();
        var viewModel = Container.Resolve<ShellViewModel>();
        view.DataContext = viewModel;
        view.Show();

        return view;
    }
}

以下是我的Shell的ViewModel:

接下来,这是我的Shell的ViewModel:

public class ShellViewModel : ViewModelBase
{
    public List<IProductModule> Modules { get; set; }

    public ShellViewModel(List<IProductModule> modules)
    {
        modules.Sort((a, b) => a.Name.CompareTo(b));
        Modules = modules;
    }
}

如您所见,我正在尝试注入一个 IProductModule 的列表(其中 ProductAModule 继承了一些属性和方法),以便它可以绑定到我的 Shell 视图。我错过了什么非常简单的东西吗?或者不能使用 Unity IoC 进行操作?(我看到了 StructureMap 的 Prism 扩展可以实现这一点)
还有一件事...当应用程序运行时,在 Bootstrapper 中容器解析 ShellViewModel 时,我收到以下异常:
“PrismBasic.Shell.ViewModels.ShellViewModel”,名称为“”。异常消息是:当前构建操作(构建密钥 Build Key[PrismBasic.Shell.ViewModels.ShellViewModel, null])失败:在尝试调用构造函数 PrismBasic.Shell.ViewModels.ShellViewModel(System.Collections.Generic.List`1[[PrismBasic.ModuleBase.IProductModule, PrismBasic.ModuleBase, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] modules) 时无法解析参数模块。(策略类型 BuildPlanStrategy,索引 3)
总之,很简单...看起来有点困惑...
任何帮助都将不胜感激!
Rob
4个回答

2
我认为你可能只需要这样做:

我认为你可能只需要这样做:

public class Bootstrapper : UnityBootstrapper
{
    protected override void ConfigureContainer()
    {
        Container.RegisterType<IProductModule>();

        base.ConfigureContainer();
    }

    private static ObservableCollection<IProductModule> _productModules = new Obser...();
    public static ObservableCollection<IProductModule> ProductModules
    { 
         get { return _productModules; } 
    }
    protected override IModuleCatalog GetModuleCatalog()
    {
        var modCatalog = new ModuleCatalog()
            .AddModule(typeof(ProductAModule));
        //TODO: add all modules to ProductModules collection

        return modCatalog;
    }

   ...
}

那么您可以拥有一个静态属性,任何东西都可以直接绑定到它上面,或者从您的ViewModel中使用它。


以下是如何获取已在模块目录中注册的模块名称列表。

public class MyViewModel : ViewModel
{

      public ObservableCollection<string> ModuleNames { ... }
      public MyViewModel(IModuleCatalog catalog)
      {
           ModuleNames = new ObservableCollection<string>(catalog.Modules.Select(mod => mod.ModuleName));
      }
}

基本上就是这样。在容器中,IModuleCatalog和IModuleManager是为你设置的唯一可以访问模块的内容。但是,由于这些模块还没有被创建(希望如此),所以你不会得到任何实例数据,只能访问类型数据。

希望这能帮助到你。


1
嗨,安德森,我不这样做有两个原因: 1)我希望通过 MVVM 来揭示 Prism 的神秘面,所以我希望将所有绑定保留在视图模型中(即 ShellViewModel)。 2)如果我要进行//TODO: add...任务,我将不使用已在 UnityContainer 中注册的 ModuleCatalog... 我虽然理解您的想法,但还是感谢您的建议! - Robert Reid
再次问候你,安德森,非常感谢。当然,我知道这不会违反MVVM最佳实践(如果有严格的最佳实践),我只是想让我的ViewModel(s)不依赖于除了它们的Models以外的其他类来获取信息。虽然我想也没有什么说Bootstrapper不能做到!非常感谢你的贡献。 - Robert Reid
@Robert Reid:我已经附上了样例。希望这能澄清你所能访问的内容和你的限制。 - Anderson Imes
@Robert Reid:关于模型的问题,我通常不会在引导程序类中创建静态权限...我之所以这样做是为了简单起见。我通常会为此信息创建一个模型“存储”,例如Model.ModuleStore或类似的东西,供其他内容提取和贡献。我可以理解直接从Bootstrapper.Modules中提取的疑虑。 - Anderson Imes
@Robert Reid: 看起来你想在模块实例化之前获得一些关于模块的更多信息。如果这是静态信息,你可以考虑使用类型上的属性来提供这些信息。你可以使用TypeDescriptor.GetAttributes(Type.GetType(moduleInfo.ModuleType))来获取添加在模块类型上的属性集合。 - Anderson Imes
显示剩余4条评论

1

我认为你误解了模块的目的。模块只是用于容纳你想要使用的视图和服务的容器。另一方面,Shell应该只包含应用程序的主要布局。

我认为你应该在Shell中定义一个区域,然后将视图(在你的情况下是按钮)注册到该区域。

如何部署你的视图和服务与模块有关,这取决于你所寻找的模块化级别,即如果你想能够独立部署ModuleA的视图和服务而不影响ModuleB等的视图和服务。在你的情况下,将所有内容注册到一个单独的模块中可能已经足够了。

花些时间玩一下文档中提供的示例,它们非常好。

你的示例抛出异常的原因是因为你的ShellViewModel依赖于List,而该类型未在Unity中注册。此外,你正在使用Unity注册IProductModule,这是没有意义的,因为接口无法构造。


0
var modules = new IProductModule[]
{
    Container.Resolve<ProductAModule>()
    //Add more modules here...
};
Container.RegisterInstance<IProductModule[]>(modules);

就是这样!使用这段代码,我可以将我的模块注入到ShellViewModel中,并在我的应用程序中显示每个模块作为一个按钮!

如此简单的解决方案!来自CompositeWPF讨论组的伟大人物。我毫不保留地推荐他们 ^_^


除非您为每个模块创建多个实例,否则如果您打算根据显示的信息激活它们,这种方法可能有效。但是,对于每个模块,您在其构造函数中所做的任何操作都将执行两次,并且您将永久地在内存中保留2个每种模块类型的实例。这就是为什么我不建议采用这种方法。这表明您缺乏对Prism实际工作方式的理解。 - Anderson Imes
嗨Anderson,我确实对Prism缺乏理解!我已经测试了您的理论,并且确实有两个实例创建了我的模块。但这是我目前能想到的最好的解决方案(它允许我将我的模块绑定到它们自己的按钮,并带有一个命令)。 - Robert Reid

0

我觉得我今天遇到了一个类似的问题,原来PRISM在初始化模块之前创建了Shell,所以你不能将任何服务从模块注入到Shell本身。

尝试创建另一个依赖于所有其他模块并实现所需功能的模块,然后将其添加到Shell中的区域以显示您的服务列表。不幸的是,我还没有机会尝试它,但这是我计划实施的解决方案。

顺便说一下,我认为你需要使用属性注入标记属性,但我可能错了(因为我已经有一段时间没有直接使用Unity了)。


编辑:在Unity中使用setter注入,您需要将DependencyAttribute应用于属性;您可以在这里阅读有关它的信息。


嗨Rory,感谢你的回答。抱歉,我可能在问题上有点含糊不清...我想将实际模块本身放入ShellViewModule中,服务问题我以后再解决!Bootstrapper中初始化的运行顺序如下:
  • ConfigureContainer
  • GetModuleCatalog
  • CreateShell
所以我肯定已经准备好了我的ModuleInfo数组,但是它们没有足够的用处来绑定到我IProductModule包含的其他属性。谢谢Rory!
- Robert Reid
再次问候,我之前使用过[Dependency]属性(用于属性注入)。我正在尝试在构造函数中进行注入,我猜想即使我使用属性注入,它也不是万能的解决方案! - Robert Reid
嘿,罗伯特,很抱歉造成了误解。我刚刚注意到那个属性的文档已经过时了,所以我马上会删除链接。如果你查看UnityBootloader的源代码,你会发现容器是使用ModuleCatalog进行配置的,但没有包含单独的模块。你最好的选择可能是重写ConfigureContainer并自己向容器注册模块,只要不要忘记调用基本实现即可。 - Rory
哦,亲爱的,那是个问题。很抱歉我今晚没有更多的想法了(除了重新实现ModuleManager中的大部分功能以自己加载模块,但那太过繁琐)。如果您找到解决方案,请告诉我。出于好奇,您不能将所提到的功能移动到各个模块内部的服务中吗?无论如何,在我所在的地方已经是明天了,我需要睡觉了,祝你好运,晚安。 - Rory
非常感谢你的帮助,Rory。我真的很感激你的坚持!我会随时向你更新! - Robert Reid
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接