使用Prism 6的ViewModelLocator解析位于不同程序集中的ViewModels

3

我正在尝试将我的视图的DataContext连接到另一个独立程序集中的视图模型。Brian Lagunas在他的博客中写了一些关于Prism's new ViewModelLocator入门的内容,但是他的解决方案是专门定制约定以允许ViewModelLocator解析视图模型类型。

我的情况:

我有一个主项目(MyApplication.exe),其中包含引导程序、Shell和视图,在另一个分离的程序集(MyApplication.Process.dll)中,我有所有的视图模型。

根据Brian的解释,我尝试了以下解决方案:

protected override void ConfigureViewModelLocator()
    {
        base.ConfigureViewModelLocator();

        ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType =>
                {
                    var viewName = viewType.FullName;
                    var viewAssemblyName = viewType.Assembly.GetName().Name;

                    var viewModelNameSuffix = viewName.EndsWith("View") ? "Model" : "ViewModel";

                    var viewModelName = viewName.Replace("Views", "ViewModels") + viewModelNameSuffix;
                    viewModelName = viewModelName.Replace(viewAssemblyName, viewAssemblyName + ".Process");
                    var viewModelAssemblyName = viewAssemblyName + ".Process";
                    var viewModelTypeName = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}, {1}",
                        viewModelName,
                        viewModelAssemblyName);

                    return Type.GetType(viewModelTypeName);                        
                });
    }

上面的解决方案是正确的,但我不知道这是否是最好的方法?
我想要的只是告诉 Prism ViewModelLocator 在哪些程序集中查找视图模型,我的意思是与 Caliburn.Micro 相同的方法(在所有已注册的程序集中查找视图模型)。
如果我的应用程序支持 Prism 模块化,那么如果程序集名称不以“Process”结尾,上述解决方案将无法工作?
你对我有什么建议?
3个回答

2

你的解决ViewModel类型的代码肯定没问题。如果你查看Prism的代码库,你会注意到一种相似的方式,使用轻微的反射和字符串替换。

static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
        viewType =>
        {
            var viewName = viewType.FullName;
            viewName = viewName.Replace(".Views.", ".ViewModels.");
            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
            var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
            return Type.GetType(viewModelName);
        };

使用约定的类型解析的问题在于,您必须遵循默认约定或创建自己的约定并坚持所选的约定。这意味着,如果您在第一个模块中选择将视图模型放在.Process程序集中,则应该在所有其他模块中执行此操作。坚持您的约定是最简单的方法。
好消息是:如果您不喜欢基于约定的解析,您可以像您已经做的那样覆盖解析,并实现任何类型的解析,无论您希望它有多复杂。没有什么能阻止您例如保留映射视图到视图模型的字典(我们实际上为一个项目做到了这一点)。填充此字典(或设置另一种解析方式)将在每个模块的ModuleCatalog中完成。

因为我的应用程序是模块化的,我无法确保其他开发人员应该完全按照我现在使用的模式进行应用,因此,我正在寻找一种新的方法来通过搜索所有已注册的程序集目录中继承自BindableBase并具有后缀“ViewModel”的类型来解决视图模型。我很快会分享我的解决方案。谢谢。 - SomeCode.NET

1
这个解决方法怎么样?-我真的放弃了编辑ViewModelLocator-在同一项目中创建一个ViewModule,并让它继承另一个程序集中的ViewModel,基本实现在基础ViewModel中,你仍然可以绑定并进行任何想做的操作。
我将基类中的所有函数标记为virtual,这样如果我想使用某些特定于平台的组件(如IRegionManager),就可以扩展它们的功能。
这是来自平台项目(WPF)的代码。
namespace PrismApplicationTest0.ViewModels
{
    public class ViewAViewModel : ViewAViewModelBase
    {
        private readonly IRegionManager _regionManager;

        public ViewAViewModel(IEventAggregator eventAggregator,IRegionManager regionManager) : base(eventAggregator)
        {
            _regionManager = regionManager;
        }

        protected override void UpdateMethod()
        {
            // After completing the core functionality
            base.UpdateMethod();

            // Switch to another page using platform specific region manager
            _regionManager.RequestNavigate(RegionNames.ContentRegion,"ViewB");
        }
      }
}

这是来自 PCL(可移植类库)的代码。
using System;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;

namespace MainModule.ViewModels
{
    public abstract class ViewAViewModelBase : BindableBase
    {
        private readonly IEventAggregator _eventAggregator;
        private string _firstName;
        private string _lastName;
        private DateTime? _lastUpdated;

        public string FirstName
        {
            get { return _firstName; }
            set { SetProperty(ref _firstName, value); }
        }

        public string LastName
        {
            get { return _lastName; }
            set { SetProperty(ref _lastName, value); }
        }

        public DateTime? LastUpdated
        {
            get { return _lastUpdated; }
            set { SetProperty(ref _lastUpdated, value); }
        }

        public DelegateCommand UpdateCommand { get; private set; }

        public ViewAViewModelBase(IEventAggregator eventAggregator)
        {

            _eventAggregator = eventAggregator;
            UpdateCommand =
            new DelegateCommand(UpdateMethod, CanUpdateMethod)
            .ObservesProperty(() => FirstName)
            .ObservesProperty(() => LastName);
        }

        protected bool CanUpdateMethod()
        {
            return !String.IsNullOrEmpty(_lastName) && !String.IsNullOrEmpty(_firstName);
        }

        protected virtual  void UpdateMethod()
        {

            LastUpdated = DateTime.Now;
            _eventAggregator.GetEvent<Events.UpdatedAggEvent>().Publish($"User {FirstName}");
        }
    }
}

对我来说它工作得很好。 我猜如果你需要其他程序集中的另一个对象,你可以在基类中创建它们的实例。
祝你好运


1

我最终通过设置自定义视图模型解析器来解决我的问题,以在所有添加的程序集目录中搜索视图模型。

解决方案

首先,我尝试应用默认的Prism视图模型定位器约定,如果没有找到视图模型,则开始应用自定义的约定。

1- 我从AggregateCatalog中获取所有程序集。

2- 获取所有非抽象的从Prism BindableBase继承的导出类型。

3- 应用自定义约定委托以获取预期的视图模型。

在我的情况下,自定义约定是所有类型都具有后缀“ViewModel”,前缀是视图类型名称: 例如: 如果视图名称为“UsersView”,则视图模型应为“UsersViewModel”。 如果视图名称为“Users”,则视图模型也应为“UsersViewModel”。

代码:

ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType =>
                {
                    // The default prism view model type resolver as Priority 
                    Type viewModelType = this.GetDefaultViewModelTypeFromViewType(viewType);
                    if (viewModelType != null)
                    {
                        return viewModelType;
                    }

                    // IF no view model found by the default prism view model resolver

                    // Get assembly catalogs
                    var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

                    // Get all exported types inherit from BindableBase prism class
                    var bindableBases =
                        assemblyCatalogs.Select(
                            c =>
                            ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                                .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
                                .Select(t => t)).SelectMany(b =>
                                    {
                                        var types = b as IList<Type> ?? b.ToList();
                                        return types;
                                    }).Distinct() ;

                    // Get the type where the delegate is applied
                    var customConvention = new Func<Type, bool>(
                        (Type t) =>
                            {
                                const string ViewModelSuffix = "ViewModel";
                                var isTypeWithViewModelSuffix = t.Name.EndsWith(ViewModelSuffix);
                                return (isTypeWithViewModelSuffix)
                                       && ((viewType.Name.EndsWith("View") && viewType.Name + "Model" == t.Name)
                                           || (viewType.Name + "ViewModel" == t.Name));
                            });

                    var resolvedViewModelType = bindableBases.FirstOrDefault(customConvention);
                    return resolvedViewModelType;
                });

方法 * GetDefaultViewModelTypeFromViewType * 是默认的prism视图模型定位器,其代码与Bart's answer中完全相同。

希望这对其他人有所帮助。

编辑:

最终我通过创建一个新的自定义MvvmTypeLocator解决了问题:

public interface IMvvmTypeLocator
{
    #region Public Methods and Operators

    Type GetViewModelTypeFromViewType(Type viewType);

    Type GetViewTypeFromViewModelType(Type viewModelType);

    Type GetViewTypeFromViewName(string viewName);

    #endregion
}

实现方式:
public class MvvmTypeLocator: IMvvmTypeLocator
{
    private AggregateCatalog AggregateCatalog { get; set; }

    public MvvmTypeLocator(AggregateCatalog aggregateCatalog)
    {
        this.AggregateCatalog = aggregateCatalog;
    }

    public Type GetViewModelTypeFromViewType(Type sourceType)
    {
        // The default prism view model type resolver as Priority 
        Type targetType = this.GetDefaultViewModelTypeFromViewType(sourceType);
        if (targetType != null)
        {
            return targetType;
        }

        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
            {
                const string TargetTypeSuffix = "ViewModel";
                var isTypeWithTargetTypeSuffix = t.Name.EndsWith(TargetTypeSuffix);
                return (isTypeWithTargetTypeSuffix)
                       && ((sourceType.Name.EndsWith("View") && sourceType.Name + "Model" == t.Name)
                           || (sourceType.Name + "ViewModel" == t.Name));
            });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    public Type GetViewTypeFromViewModelType(Type sourceType)
    { 
        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(IView)))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
            {
                const string SourceTypeSuffix = "ViewModel";
                var isTypeWithSourceTypeSuffix = t.Name.EndsWith(SourceTypeSuffix);
                return (isTypeWithSourceTypeSuffix)
                       && ((sourceType.Name.EndsWith("View") && t.Name + "Model" == sourceType.Name)
                           || (t.Name + "ViewModel" == sourceType.Name));
            });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    public Type GetViewTypeFromViewName(string viewName)
    {
        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && typeof(IView).IsAssignableFrom(t) && t.Name.StartsWith(viewName))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
                {
                    return t.Name.EndsWith("View");
                });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    private Type GetDefaultViewModelTypeFromViewType(Type viewType)
    {
        var viewName = viewType.FullName;
        viewName = viewName.Replace(".Views.", ".ViewModels.");
        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
        var viewModelName = String.Format(
            CultureInfo.InvariantCulture,
            "{0}{1}, {2}",
            viewName,
            suffix,
            viewAssemblyName);
        return Type.GetType(viewModelName);
    }
}

这个自定义类型定位器使用AggregateCatalog来搜索所有程序集目录中的目标类型。当然,我会在Bootstrapper上配置AggregateCatalog后创建它的实例:
protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginViewModel).Assembly));
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginView).Assembly));
        this.mvvmTypeLocator = new MvvmTypeLocator(this.AggregateCatalog);
    }

在最后,我只需要像下面这样在引导程序中配置视图模型定位器:
protected override void ConfigureViewModelLocator()
    {
        base.ConfigureViewModelLocator();

        ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType => this.mvvmTypeLocator.GetViewModelTypeFromViewType(viewType));
    }

请注意,方法GetViewTypeFromViewModelTypeGetViewTypeFromViewName正在搜索所有实现名为IView的接口的视图。 这只是我用来将我的视图与同一程序集中的其他类区分开来的空接口。 如果有人使用此mvvmTypeLocator,则必须创建自己的接口并实现所有应由mvvmTypeLocator发现的视图。

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