使用Prism 6中的ViewModelLocator在分离的程序集中解析ViewModel

时间:2015-12-30 18:04:39

标签: c# .net mvvm prism viewmodellocator

我正在尝试将我的视图的DataContexts连接到另一个分离的程序集中查看模型。

Brian Lagunas在他的博客上写了Getting Started with Prism’s new ViewModelLocator的内容,但是,他的解决方案专门用于定制约定以允许ViewModelLocator解析视图模型类型。

我的情景:

我有主项目(MyApplication.exe)包含Bootstrapper,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 Modularity,如果程序集名称不以单词“ Process ”结尾,那么上面的解决方案将不起作用?

你对我有什么建议?

3 个答案:

答案 0 :(得分: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中完成。

答案 1 :(得分:1)

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

解决方案

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

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

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

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

在我的例子中,自定义约定是所有具有后缀&#34; ViewModel&#34;并且前缀是视图类型名称: 例: 如果视图名称是&#34; UsersView&#34;视图模型应该是&#34; UsersViewModel&#34;。 如果视图名称是&#34;用户&#34;视图模型也应该是&#34; UsersViewModel&#34;。

代码:

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 *是默认的棱镜视图模型定位器,其代码与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,我就会在Bootstrapper上创建它的实例:

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);
    }

最后,我只需在Bootstrapper中配置视图模型定位器,如下所示:

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

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

请注意,方法 GetViewTypeFromViewModelType GetViewTypeFromViewName 正在搜索实现名为 IView 的界面的所有视图。这只是一个空接口,用于将我的视图与同一程序集中的其他类区分开来。如果有人使用这个mvvmTypeLocator,那么他必须创建自己的接口并实现mvvmTypeLocator应该可以发现的所有视图。

答案 2 :(得分:1)

这个解决方法怎么样? - 我真的放弃了编辑ViewModelLocator-在同一个项目中创建一个ViewModule并让它从另一个程序集中的另一个ViewModel继承,核心实现在ViewModel基础中,你仍然可以绑定它并做你想做的一切。

我将Base类中的所有函数标记为虚拟,因此如果我想使用某些特定于平台的组件,我可以扩展它们的功能。 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}");
        }
    }
}

它对我来说是一种魅力。 我想如果你需要来自其他程序集的另一个对象,你可以在基类中创建它们的实例 祝你好运