CaliburnMicro无法在不同的装配中找到视图模型的视图

时间:2015-04-05 14:24:05

标签: c# mvvm caliburn.micro

最近我开始将我的一个项目重新组织成更小的程序集。在一个步骤中,我将视图和视图模型移动到单独的程序集中,同时将视图和VM保持在一个通用程序集中。因此我的项目结构如下:

  • 主要
  • 核心
  • 模块< - 文件夹
    • CharacterPresenter
    • LocationPresenter

命名空间是这样的:

  • RpgTools
      • 的ViewModels
      • 浏览
    • LocationPresenter
      • 的ViewModels
      • 浏览
    • 其他项目遵循相同的系统。

"主"包含bootstrapper和d minimalistic vm以及用于选择模块的视图。每个演示者都包含该演示者所需的所有视图和视图模型。 "核心"包含文件夹中每个项目使用的资产(例如元数据定义,导出接口等)

现在移动后,Caliburn.Micro找不到视图模型的视图,无论它多么简单。以下是视图模型和视图的示例:

namespace RpgTools.LocationPresenter.ViewModels
{
    using System.ComponentModel.Composition;
    using RpgTools.Core.Contracts;

    [RpgModuleMetadata(Name = "Module C")]
    [Export(typeof(IRpgModuleContract))]
    public class ModuleCViewModel :IRpgModuleContract
    {
    }
}

<UserControl x:Class="RpgTools.LocationPresenter.Views.ModuleCView"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
            <TextBlock Text="ModuleC" />
    </Grid>
</UserControl>

每次加载模块时都会出现以下错误:

  

无法找到RpgTools.LocationPresenter.ViewModels.ModuleCViewModel的视图。

如果我将模型移回&#34; Main&#34;它工作得很好。因为它可能是引导程序的一部分,所以它的完整代码是:

namespace RpgTools.Main
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Windows;

    using Caliburn.Micro;

    using RpgTools.Core.Contracts;

    /// <summary>The MEF bootstrapper.</summary>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
    public class MefBootstrapper : BootstrapperBase
    {
        /// <summary>The composition container.</summary>
        private CompositionContainer compositionContainer;

        /// <summary>Initialises a new instance of the <see cref="MefBootstrapper"/> class.</summary>
        public MefBootstrapper()
        {
            // this.CheckModuleDirectory();

            this.Initialize();
        }

        /// <summary>Override to configure the framework and setup your IoC container.</summary>
        protected override void Configure()
        {
            // Get the modules from the module directory
            // ToDo: Implement dynamic loading from modules directory.
            DirectoryCatalog dirCatalog = new DirectoryCatalog(@".");

            // Create a combinable catalog
            // ReSharper disable once RedundantEnumerableCastCall
            AggregateCatalog catalog = new AggregateCatalog(AssemblySource.Instance.Select(s => new AssemblyCatalog(s)).OfType<ComposablePartCatalog>());
            catalog.Catalogs.Add(dirCatalog);

            // Create a new composition container.
            // ReSharper disable once RedundantEnumerableCastCall
            this.compositionContainer = new CompositionContainer();

            // Create a new composition container.
            this.compositionContainer = new CompositionContainer(catalog);

            CompositionBatch compositionBatch = new CompositionBatch();

            // Add window manager to composition batch.
            compositionBatch.AddExportedValue<IWindowManager>(new ToolsWindowManager());

            // Add EventAggregator to composition batch.
            compositionBatch.AddExportedValue<IEventAggregator>(new EventAggregator());

            // Add the container itself.
            compositionBatch.AddExportedValue(this.compositionContainer);

            // Compose the container.
            this.compositionContainer.Compose(compositionBatch);
        }

        /// <summary>Override this to provide an IoC specific implementation.</summary>
        /// <param name="service">The service to locate.</param>
        /// <param name="key">The key to locate.</param>
        /// <returns>The located service.</returns>
        protected override object GetInstance(Type service, string key)
        {
            // Check if the contract is null or an empty string, if so return the contract name from the service itself.
            string contractName = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(service) : key;

            // Get a collection of exported values with the goven contract name. 
            IList<object> exports = this.compositionContainer.GetExportedValues<object>(contractName).ToList();

            if (exports.Any())
            {
                return exports.First();
            }

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contractName));
        }

        /// <summary>Override this to provide an IoC specific implementation</summary>
        /// <param name="serviceType">The service to locate.</param> 
        /// <returns>The located services.</returns>
        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return this.compositionContainer.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        /// <summary>Override this to provide an IoC specific implementation.</summary>
        /// <param name="instance"> The instance to perform injection on.</param>
        protected override void BuildUp(object instance)
        {
            this.compositionContainer.SatisfyImportsOnce(instance);
        }

        /// <summary>Override this to add custom behaviour to execute after the application starts.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The args.</param>
        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            this.DisplayRootViewFor<IShell>();
        }

        /// <summary>Checks if the modules directory exists and if not create it.</summary>
        private void CheckModuleDirectory()
        {
            if (!Directory.Exists(@".\Modules"))
            {
                Directory.CreateDirectory(@".\Modules");
            }
        }
    }
}

我可能会补充说模块发现工作正常。我的ShellView模型显示我添加的每个项目中的每个模块都很好,如果视图位于与#34; Main&#34;

不同的程序集中,则视图的加载不起作用。

我通过使用以下代码覆盖SelectAssemblies()方法修复了原始问题:

protected override IEnumerable<Assembly> SelectAssemblies()
{
    var assemblies = Directory.GetFiles(ModuleDirectory, "*.dll", SearchOption.AllDirectories).Select(Assembly.LoadFrom).ToList();
    assemblies.Add(Assembly.GetExecutingAssembly());
    return assemblies;
}

然而现在我的所有模块都被加载了两次!这是我所做代码中唯一的变化。我做错了什么?

1 个答案:

答案 0 :(得分:8)

您需要覆盖SelectAssemblies中的Bootstrapper以包含包含您的视图的程序集。默认情况下,Caliburn Micro仅包含Bootstrapper定义的程序集。

来自documentation

  

那么,AssemblySource.Instance是什么?这是Caliburn.Micro寻找视图的地方。您可以在应用程序期间随时向其添加程序集,以使它们可供框架使用,但在Bootstrapper中还有一个特殊的位置。只需覆盖这样的SelectAssemblies:

protected override IEnumerable<Assembly> SelectAssemblies()
{
    return new[] {
        Assembly.GetExecutingAssembly()
    };
}
  

您所要做的就是返回一个可搜索的程序集列表。默认情况下,基类返回应用程序所在的程序集。因此,如果所有视图与应用程序位于同一程序集中,则甚至不必担心这一点。如果您有多个包含视图的引用程序集,则这是您需要记住的扩展点。