Разрешить ViewModels в отдельной сборке с помощью ViewModelLocator в Prism 6

Я пытаюсь подключить DataContexts моих представлений для просмотра моделей из другой отдельной сборки.

Брайан Лагунас написал в своем блоге что-то для начала работы с новым ViewModelLocator от Prism. Однако его решение специально состоит в том, чтобы настроить соглашения, чтобы позволить ViewModelLocator разрешать типы моделей представлений.

Мой сценарий:

У меня есть основной проект (MyApplication.exe), содержащий Bootstrapper, Shell и представления. В другой отдельной сборке (MyApplication.Process.dll) у меня есть все модели представлений.

Основываясь на объяснениях Брайана, я попробовал следующее решение:

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, если имя сборки не заканчивается, например, словом "Процесс"?

Что вы предлагаете для меня?

3 ответа

Решение

Наконец-то я решил свою проблему, установив пользовательский распознаватель моделей представлений для поиска моделей представлений во всех добавленных каталогах сборок.

Решение

Прежде всего, я пытаюсь применить соглашение о локаторе модели вида призмы по умолчанию, если тогда не было найдено никакой модели представления, я начинаю применять свою собственную.

1- Я начинаю с получения всех сборок из AggregateCatalog.

2- Я получаю все неабстрактные экспортируемые типы, наследуемые от Prism BindableBase.

3- Я применяю делегат пользовательского соглашения, чтобы получить ожидаемую модель представления.

В моем случае пользовательское соглашение - это все типы, имеющие суффикс "ViewModel", а префикс - это имя типа представления: Пример: если имя представления - "UsersView", модель представления должна быть "UsersViewModel". Если имя представления - "Пользователи", модель представления также должна быть "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 * Является стандартным локатором модели представления призмы, его код точно такой же, как в ответе Барта. Я надеюсь, что это будет полезно для других.

Редактировать:

Я наконец решил проблему, создав новый пользовательский 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.

Ваш код разрешения типа 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 сборка, что вы должны сделать это для всех ваших других модулей. Придерживаться своего соглашения - самый простой путь.

Хорошая новость: если вам не нравится разрешение, основанное на соглашениях, вы можете переопределить разрешение, как вы уже сделали, и реализовать любое понравившееся вам разрешение, насколько сложным вы хотите, чтобы оно было. Ничто не мешает вам, например, сохранять словарь, отображающий представления в viewmodels (что мы фактически сделали для одного проекта). Заполнение этого словаря (или настройка другого способа разрешения) будет сделано в ModuleCatalog для каждого модуля.

Как насчет этого обходного пути? -Я действительно отказался от редактирования ViewModelLocator- создаю ViewModule в том же проекте и позволяю ему наследовать от другой ViewModel в другой сборке, базовая реализация находится в базовой ViewModel, и вы все равно можете связываться с ним и делать все, что хотите.

Я пометил все функции в базовом классе как виртуальные, чтобы я мог расширить их функциональность, если я хочу использовать некоторые специфичные для платформы компоненты ex. 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}");
        }
    }
}

это работает как шарм со мной. Я думаю, если вам нужен другой объект из других сборок, вы можете создать их экземпляры в базовом классе
Удачи

Другие вопросы по тегам