【问题标题】:Load Prism module with stand-alone module manager使用独立模块管理器加载 Prism 模块
【发布时间】:2015-09-06 01:54:51
【问题描述】:

我正在开发的 WPF 应用程序将有许多插件来扩展其功能。每个插件将包含一个或多个程序集(通常是“主”插件程序集,其中包含用于表示层组件和视图的单独程序集),但应用程序会将其视为单个 Prism 模块。 Prism 将用于发现和加载加载项,MEF 用于确保 Prism 模块可以访问加载项相关程序集中的依赖项。 Prism 模块的 Initialize 方法将负责配置 IoC 容器(在本例中为 Unity)。在已部署的场景中,这一切都将在启动时使用MefBootstrapper 进行加载和管理。

我在尝试对加载项进行单元测试时出现问题。为了使加载项代码与主应用程序半隔离,每个加载项也将有自己的单元测试程序集。这些测试程序集之一将负责检查服务在 IoC 容器中的注册。在测试场景中,我不想使用引导程序来加载 Prism 模块,因为它们具有我不想引入我的测试程序集的依赖项。因此,我编写了我的测试夹具基类,以便它创建自己的MefModuleManager 来加载要测试的模块。

ResolutionTestBase.cs

public abstract class ResolutionTestBase
{
  [ClassInitialize]
  public static void TestFixtureInitialise(TestContext context)
  {
    // Create the main resolution container.
    var container = new UnityContainer();

    // Install the service locator.
    var locator = new UnityServiceLocator(container);
    ServiceLocator.SetLocatorProvider(() => locator);
  }

  // Here go some helper methods for performing resolution tests.

  protected IUnityContainer Container
  {
    get { return ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer; }
  }
}

AddInResolutionTestBase.cs

public abstract class AddInResolutionTestBase:ResolutionTestBase
{
  static AddInResolutionTestBase()
  {
    Logger = new EmptyLogger();
  }

  [TestInitialize]
  public virtual void TestInitialise()
  {
    // Create MEF catalog.
    var aggregateCatalog = new AggregateCatalog();
    foreach (var testAssembly in TestAssemblies)
    {
      aggregateCatalog.Catalogs.Add(new AssemblyCatalog(testAssembly));
    }

    // Load module manager.
    var container = new CompositionContainer(aggregateCatalog);
    var serviceLocator = new MefServiceLocatorAdapter(container);
    var parts = new DownloadedPartCatalogCollection();
    var moduleInitialiser = new MefModuleInitializer(serviceLocator, Logger, parts, aggregateCatalog);
    var moduleManager = new MefModuleManager(moduleInitialiser, ModuleCatalog, Logger);
    moduleManager.ModuleTypeLoaders = new[] { new MefFileModuleTypeLoader() };
    moduleManager.Run();
  }

  protected static ILoggerFacade Logger { get; private set; }
  protected abstract IModuleCatalog ModuleCatalog { get; }
  protected abstract IEnumerable<Assembly> TestAssemblies { get; }
}

对于插件,它们的主程序集中有一个独立的类来实现 Prism 模块的要求,以及用于配置容器的 Unity 扩展。

模块.cs

[ModuleExport("AddInModule", typeof(Module), InitializationMode = InitializationMode.OnDemand)]
public class Module : IModule
{
  private readonly IEnumerable<IUnityContainerExtensionConfigurator> _extensions;

  [ImportingConstructor]
  public Module([ImportMany]IEnumerable<IUnityContainerExtensionConfigurator> extensions)
  {
    _extensions = extensions;
  }

  public void Initialize()
  {
    // Load the dependency injection container.
    var container = ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer;
    if (container != null)
    {
      foreach (var extension in _extensions)
      {
        container.AddExtension((UnityContainerExtension) extension);
      }
    }
  }
}

ContainerInstallerExtension.cs(在加载项的主程序集中)

[Export(typeof(IUnityContainerExtensionConfigurator))]
public class ContainerInstallerExtension : UnityContainerExtension
{
  protected override void Initialize()
  {
    // perform container configuration here.
  }
}

PresentationInstallerExtension.cs(在外接程序的演示程序集中)

[Export(typeof(IUnityContainerExtensionConfigurator))]
public class PresentationInstallerExtension:UnityContainerExtension
{
  protected override void Initialize()
  {
    // perform container configuration here.
  }
}

AddInResolutionTest.cs(在外接程序的 IoC 测试程序集中)

[TestClass]
public class AddInResolutionTest : AddInResolutionTestBase
{
  private IEnumerable<Assembly> _testAssemblies;

  private IModuleCatalog DoGetModuleCatalog()
  {
    var moduleInfo = new ModuleInfo("AddInModule", typeof (Module).AssemblyQualifiedName)
    {
      InitializationMode = InitializationMode.WhenAvailable,
      Ref = typeof (Module).Assembly.CodeBase
    };
    return new ModuleCatalog(new[] {moduleInfo});
  }

  protected override IModuleCatalog ModuleCatalog
  {
    get { return DoGetModuleCatalog(); }
  }

  protected override IEnumerable<Assembly> TestAssemblies
  {
    get { return _testAssemblies ?? (_testAssemblies = new[] { typeof(ContainerInstallerExtension).Assembly, typeof(PresentationInstallerExtension).Assembly }); }
  }

  [TestMethod]
  public void ResolveSomeService()
  {
    // perform resolution test here.
  }
}

值得注意的是分辨率测试夹具,“测试程序集”通过项目引用链接到 IoC 测试程序集,并按类型直接引用(而不是使用目录扫描目录),因此我可以避免使用生成后事件将程序集复制到公共文件夹以进行测试。

当我(按原样)运行单元测试时,我收到一个异常,表明模块管理器未能加载 Prism 模块:

初始化方法 AddInResolutionTest.TestInitialise 抛出异常。 Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException:Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException:无法加载模块 AddInModule 的类型。

如果在 Silverlight 应用程序中使用 MEF 时发生此错误,请确保对 MefExtensions 程序集的引用的 CopyLocal 属性在主应用程序/shell 中设置为 true,在所有其他程序集中设置为 false。

错误是:对象引用未设置为对象的实例.. ---> System.NullReferenceException:对象引用未设置为对象的实例.. 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo) --- 内部异常堆栈跟踪结束 --- 在 Microsoft.Practices.Prism.Modularity.ModuleManager.HandleModuleTypeLoadingError(ModuleInfo moduleInfo,Exception 异常) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.IModuleTypeLoader_LoadModuleCompleted(对象发件人,LoadModuleCompletedEventArgs e) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(ModuleInfo 模块信息,异常错误) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.BeginRetrievingModule(ModuleInfo moduleInfo) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModuleTypes(IEnumerable`1 模块信息) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModulesWhenAvailable() 在 Microsoft.Practices.Prism.Modularity.ModuleManager.Run() 在 AddInResolutionTestBase.cs 中的 AddInResolutionTestBase.TestInitialise():第 xx 行

在调用 moduleManager.Run() 时,我的代码中没有任何内容为空,所以我不清楚“真正的”问题是什么。

我尝试了各种更改来解决问题,包括:

  • 在 AddInResolutionTestBase.cs 中调用 moduleManager.LoadModule() 而不是 moduleManager.Run()
  • 操作AddInResolutionTest中创建的ModuleInfoState绕过模块管理器中的问题

我所做的任何其他更改都导致了不同的错误,但仍然表明模块管理器尝试加载 Prism 模块时出现问题。

是否需要一些额外的步骤才能正确配置模块管理器以便能够以这种方式加载模块,同时记住单元测试不需要一些通常的开销(例如记录器)?

【问题讨论】:

    标签: c# unit-testing mstest mef prism-4


    【解决方案1】:

    在反编译器的帮助下,我能够找出“缺失的部分”并进行一些更改,以确保注册/安装所有必需的组件,以便模块管理器能够初始化模块( s) 用于测试。对于任何有兴趣的人:

    public abstract class AddInResolutionTestBase:ResolutionTestBase
    {
      private CompositionContainer _container;
      private IModuleCatalog _moduleCatalog;
      private IEnumerable<object> _testEntities;
      private IEnumerable<ModuleInfo> _testModuleInformation;
    
      static AddInResolutionTestBase()
      {
        Logger = new EmptyLogger();
      }
    
      [TestInitialize]
      public virtual void TestInitialise()
      {
        // Create MEF catalog.
        AggregateCatalog = CreateAggregateCatalog();
        ConfigureAggregateCatalog();
        AggregateCatalog = DefaultPrismServiceRegistrar.RegisterRequiredPrismServicesIfMissing(AggregateCatalog);
    
        ConfigureContainer();
    
        // Initialise modules to be tested.
        CompositionContainer.GetExportedValue<IModuleManager>().Run();
      }
    
      #region Protected Methods
      protected virtual void ConfigureAggregateCatalog()
      {
        var testAssemblies = TestEntities.OfType<Assembly>();
        foreach (var testAssembly in testAssemblies)
        {
          AggregateCatalog.Catalogs.Add(new AssemblyCatalog(testAssembly));
        }
    
        if (TestEntities.Any(entity => entity is System.Type))
        {
          var catalog = new TypeCatalog(TestEntities.OfType<System.Type>());
          AggregateCatalog.Catalogs.Add(catalog);
        }
      }
    
      protected virtual void ConfigureContainer()
      {
        CompositionContainer.ComposeExportedValue<ILoggerFacade>(Logger);
        CompositionContainer.ComposeExportedValue<IModuleCatalog>(ModuleCatalog);
        CompositionContainer.ComposeExportedValue<IServiceLocator>(new MefServiceLocatorAdapter(CompositionContainer));
        CompositionContainer.ComposeExportedValue<AggregateCatalog>(AggregateCatalog);
      }
    
      protected virtual AggregateCatalog CreateAggregateCatalog()
      {
        return new AggregateCatalog();
      }
    
      protected virtual CompositionContainer CreateContainer()
      {
        return new CompositionContainer(AggregateCatalog);
      }
    
      protected virtual IModuleCatalog CreateModuleCatalog()
      {
        return new ModuleCatalog(TestModuleInformation);
      }
    
      protected abstract IEnumerable<object> GetTestEntities();
      protected abstract IEnumerable<ModuleInfo> GetTestModuleInformation(); 
      #endregion
    
      #region Protected Properties
    
      protected AggregateCatalog AggregateCatalog { get; set; }
    
      protected CompositionContainer CompositionContainer
      {
        get { return _container ?? (_container = CreateContainer()); }
      }
    
      protected static ILoggerFacade Logger { get; private set; }
    
      protected IModuleCatalog ModuleCatalog
      {
        get { return _moduleCatalog ?? (_moduleCatalog = CreateModuleCatalog()); }
      }
    
      protected IEnumerable<object> TestEntities
      {
        get { return _testEntities ?? (_testEntities = GetTestEntities()); }
      }
    
      protected IEnumerable<ModuleInfo> TestModuleInformation
      {
        get { return _testModuleInformation ?? (_testModuleInformation = GetTestModuleInformation()); }
      } 
      #endregion
    }
    

    这个测试基类现在在一定程度上模拟了应用程序正常启动时在 boostrapper 中通常发生的事情。每个插件中的(分辨率)测试现在只需要为表示插件的 Prism 模块提供(导出的)容器扩展列表和模块信息(除了实际分辨率测试!)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多