Создайте универсальную ViewModelFactory в WPF/CaliburnMicro/AutoFac.

У меня есть приложение WPF, использующее Caliburn.Micro и AutoFac.

В Bootstrapper.Configure() я регистрирую свои Views и ViewModels как:

protected override void Configure()
{
    var builder = new ContainerBuilder();
    //  register view models
    builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
        .Where(type => type.Name.EndsWith("ViewModel"))
        .AsSelf()
        .InstancePerDependency();
    //  register views
    builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
        .Where(type => type.Name.EndsWith("View"))
        .AsSelf()
        .InstancePerDependency();

    builder.Register<IWindowManager>(c => new WindowManager()).InstancePerLifetimeScope();

    _container = builder.Build();
}

    protected override IEnumerable<Assembly> SelectAssemblies()
{
    return new[]
    {
        typeof (MainViewModel).Assembly, // assembly that holds all the ViewModels
        typeof (MainView).Assembly // assembly that holds all the Views
    };
}

Это хорошо работает, представления и модели представления хорошо отображаются в соответствии с этим соглашением, например. при создании экземпляра RandomViewModel отображается RandomView.

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

Например, когда у меня есть сущности UserEntity, OrderEntity, AccountEntity и т. д., я бы создал классы:

//base classes
abstract class EditViewModelBase<T> : IEditViewModelBase where T: IEntity
abstract class ListViewModelBase<T> : IListViewModelBase where T: IEntity

//implementations
class UserEditViewModel : EditViewModelBase<UserEntity> {...}
class OrderEditViewModel : EditViewModelBase<OrderEntity> {...}
class AccountEditViewModel : EditViewModelBase<AccountEntity> {...}
...
class UserListViewModel : ListViewModelBase<UserEntity> {...}
class OrderListViewModel : ListViewModelBase<OrderEntity> {...}
class UserListViewModel : ListViewModelBase<UserEntity> {...}
...

Теперь я хочу создать ViewModelFactory для общего поиска моделей представления:

class ViewModelFactory
{
    public IEditViewModelBase CreateEditViewModel<T>()
    {
        //this method should do :
        // if typeof(T) == typeof(UserEntity) return new UserEditViewModel();
        // if typeof(T) == typeof(OrderEntity) return new OrderEditViewModel();
        // if typeof(T) == typeof(UserEntity) return new UserEditViewModel();
        ...
    }

    public IListViewModelBase CreateListViewModel<T>()
    {
        //this method should do :
        // if typeof(T) == typeof(OrderEntity) return new OrderListViewModel();
        ...
    }
}

Как мне решить эту проблему, чтобы мне не приходилось менять ViewModelFactory каждый раз, когда я добавляю новые Entity и ViewModels в свое решение? Я думал о том, чтобы зациклиться на контейнере, используя приемы отражения, такие как IsInstanceOf и Activator.CreateInstance, но я не могу заставить его работать...


person hcd    schedule 18.09.2015    source источник


Ответы (2)


Я думаю, что проще всего это сделать с помощью библиотеки Auto Factory, внутри которой используется AutoFac. Вы можете решить это следующим образом:

using AutoFactory;
class ViewModelFactory
{
    private IAutoFactory<IEditViewModelBase> _editFactory = Factory.Create<IEditViewModelBase>();
    private IAutoFactory<IListViewModelBase> _listFactory = Factory.Create<IListViewModelBase>();

    public IEditViewModelBase CreateEditViewModel<T>()
    {
        return _editFactory.SeekPart(t => t.BaseType.GetGenericArguments()[0].Name == typeof(T).Name);
    }

    public IListViewModelBase CreateListViewModel<T>()
    {
        return _listFactory.SeekPart(t => t.BaseType.GetGenericArguments()[0].Name == typeof(T).Name);
    }
}

Или, если вы хотите сделать это напрямую с AutoFac, вы можете сделать, например:

using Autofac.Builder;
using Autofac.Features.Metadata
class ViewModelFactory
{
    private IContainer _container;
    private IEnumerable<Meta<Lazy<IEditViewModelBase>>> _editParts;

    public ViewModelFactory()
    {
        var builder = new ContainerBuilder();
        builder.RegisterAssemblyTypes(this.GetType().Assembly)
           .Where(t => typeof(IEditViewModelBase).IsAssignableFrom(t))
           .As<IEditViewModelBase>()
           .WithMetadata("type", t => t.BaseType.GetGenericArguments()[0]);
        _container = builder.Build();
        _editParts = _container.Resolve<IEnumerable<Meta<Lazy<IEditViewModelBase>>>>();
    }

    public IEditViewModelBase CreateEditViewModel<T>()
    {
        return _editParts.FirstOrDefault(p => p.Metadata["type"] as Type == typeof(T)).Value.Value;
    }
    ...
}
person thepirat000    schedule 18.09.2015
comment
Я немного боролся с этим, потому что, по-видимому, фабричный класс должен быть объявлен в той же сборке, что и ViewModels. Сначала я сделал это в своем основном исполняемом файле, рядом с загрузчиками, потому что мне не нужны ссылки Autofac в моей сборке ViewModels ... но теперь я переместил фабрику в свой проект ViewModels, и это работает как шарм. Большое спасибо !!! - person hcd; 19.09.2015
comment
Обратите внимание, что вы также можете передать сборку модели представления в качестве параметра методу AutoFactory Factory.Create или методу AutoFac Builder.RegisterAssemblyTypes. - person thepirat000; 19.09.2015
comment
Да, я только что заметил это и перемещаю его обратно в свою исполняемую сборку :) Я видел, что мои ViewModels теперь используются повторно. Каждый раз, когда я делаю CreateViewModel, я возвращаю один и тот же экземпляр, что не так хорошо, потому что у меня есть Приложение MDI, и мне нужны отдельные экземпляры, например. для редактирования 2 пользовательских объектов на разных вкладках - person hcd; 19.09.2015

я думаю

public class Bootstrapper
{
  public IContainer Bootstrap()
  {
    var builder = new ContainerBuilder();

    builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
    builder.RegisterType<MessageDialogService>().As<IMessageDialogService>();

    builder.RegisterType<FileDataService>().As<IDataService>();
    builder.RegisterType<fLookupProvider>().As<ILookupProvider<f>>();
    builder.RegisterType<fGroupLookupProvider>().As<ILookupProvider<fGroup>>();
    builder.RegisterType<fDataProvider>().As<IfDataProvider>();

    builder.RegisterType<fEditViewModel>().As<IfEditViewModel>();
    builder.RegisterType<NavigationViewModel>().As<INavigationViewModel>();
    builder.RegisterType<MainViewModel>().AsSelf();

    return builder.Build();
  }
}
person zinderud    schedule 01.03.2016