Caliburn Micro 无法在不同的程序集中找到视图模型的视图

CaliburnMicro cannot find view for view model in different assembly

最近我开始将我的一个项目重组为更小的程序集。在一个步骤中,我将我的视图和视图模型移动到单独的程序集中,同时将属于一起的视图和 VM 保留在一个公共程序集中。因此我的项目结构如下所示:

命名空间是这样的:

"Main" 包含 bootstrapper 和 d minimalistic vm and view to select 一个模块。每个演示者都包含该演示者所需的所有视图和视图模型。 "Core" 包含文件夹中每个项目使用的资产(例如元数据定义、导出接口等)

现在移动后 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>

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

Cannot find view for RpgTools.LocationPresenter.ViewModels.ModuleCViewModel.

如果我将模型移回 "Main",它就可以正常工作。因为它可能与引导程序有关,所以这里是它的完整代码:

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 模型显示我添加的每个项目中的每个模块,如果视图位于与 "Main"

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

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

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

但是现在我的所有模块都被加载了两次!这是我对代码所做的唯一更改。我做错了什么?

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

来自documentation

So, what is AssemblySource.Instance? This is the place that Caliburn.Micro looks for Views. You can add assemblies to this at any time during your application to make them available to the framework, but there is also a special place to do it in the Bootstrapper. Simply override SelectAssemblies like this:

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

All you have to do is return a list of searchable assemblies. By default, the base class returns the assembly that your Application exists in. So, if all your views are in the same assembly as your application, you don’t even need to worry about this. If you have multiple referenced assemblies that contain views, this is an extension point you need to remember.