Search code examples
c#wpfmvvmdevexpresscastle-windsor

(WPF) DevExpress mvvm + Castle Windsor. How to build POCO viewmodels?


In the DevExpress MVVM framework, you can define both standard ViewModels, inheriting from ViewModelBase. Or, you can define POCO ViewModels (see this link), that are better and more sophisticated.

To build such a "POCO" view model, you must use the ViewModelSource util. This will kind of make a standard dummy class into a POCO ViewModel.

namespace DataAbstractWPF.ViewModels
{
    [POCOViewModel]
    public class EntityKindViewModel : Interfaces.ICreateEntityKindViewModel
    {
    }
}

Then, in XAML, to instantiate or define such a POCO viewmodel, you must :

         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" 
         xmlns:ViewModels="clr-namespace:DataAbstractWPF.ViewModels"
         d:DataContext="{dxmvvm:ViewModelSource ViewModels:CreateEntityWizardViewModel}"

If you pass the ViewModel to the View dynamically or simply

         xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" 
         xmlns:ViewModels="clr-namespace:DataAbstractWPF.ViewModels"
         DataContext="{dxmvvm:ViewModelSource ViewModels:CreateEntityWizardViewModel}"

If you want the ViewModel to be instantiated directly by the XAML.

You can also create it by code

ViewModelSource.Create<LoginViewModel>();

Or by a factory that can be created by the framework.

var factory = ViewModelSource.Factory((string caption) => new LoginViewModel(caption));
factory("Login");

Now, using Castle Windsor, I register this :

container.Register(Component.For<Interfaces.ICreateEntityKindViewModel>().ImplementedBy<ViewModels.EntityKindViewModel>().LifestyleTransient());
container.Register(Component.For<Interfaces.ICreateEntityWizardViewModel>().ImplementedBy<ViewModels.CreateEntityWizardViewModel>().LifestyleTransient());
container.Register(Component.For<Interfaces.IMainWindowViewModel>().ImplementedBy<ViewModels.MainWindowViewModel>().LifestyleTransient());

container.Register(Component.For<Interfaces.ICreateEntityWizard>().ImplementedBy<Views.CreateEntityWizard>().LifestyleTransient());
container.Register(Component.For<Interfaces.IMainWindow>().ImplementedBy<Views.MainWindow>().LifestyleTransient());
container.Register(Component.For<Interfaces.ICreateEntityKind>().ImplementedBy<Views.EntityKind>().LifestyleTransient());

container.Register(Component.For<Interfaces.IShell>().ImplementedBy<Shell>().LifestyleTransient());

But now, of course, the ViewModelSource is completely bypassed, and the once beautiful POCO viewmodel is now merely a dummy useless object.

Now, my question is, how is it possible to use both ViewModelSource to create the POCO viewModels, and allow them to be injected by Castle Windsor ?

Thank you very much.


Solution

  • I found a solution for this :

    Create this helper class

    using System;
    using System.Collections.Generic;
    
    namespace DataAbstractWPF.Helpers
    {
        public class AttributeHelper
        {
    
            public static bool HasAttribute(Type implementation, Type attr)
            {
                object[] arr = implementation.GetCustomAttributes(true);
                List<object> list = new List<object>(arr);
                object attrib = list.Find(delegate (object o) { return ( attr.IsAssignableFrom(o.GetType()) ); });
                return attrib != null;
            }
    
        }
    }
    

    Create a component activator

    using System;
    using Castle.MicroKernel.ComponentActivator;
    using DevExpress.Mvvm.DataAnnotations;
    using DevExpress.Mvvm.Native;
    using DataAbstractWPF.Helpers;
    
    namespace DataAbstractWPF.Activators
    {
        class DXViewModelActivator : DefaultComponentActivator
        {
            public DXViewModelActivator(Castle.Core.ComponentModel model, Castle.MicroKernel.IKernelInternal kernel, Castle.MicroKernel.ComponentInstanceDelegate onCreation, Castle.MicroKernel.ComponentInstanceDelegate onDestruction)
                : base(model, kernel, onCreation, onDestruction)
            {
                Model.Implementation = TryGetPOCOType(Model.Implementation);
            }
    
            Type TryGetPOCOType(Type implementation)
            {
                if (AttributeHelper.HasAttribute(implementation, typeof(POCOViewModelAttribute)))
                    implementation = ViewModelSourceHelper.GetProxyType(implementation);
    
                return implementation;
            }
    
        }
    }
    

    Create an installer like so

    using Castle.MicroKernel.Registration;
    using Castle.MicroKernel.SubSystems.Configuration;
    using Castle.Windsor;
    using DataAbstractWPF.Helpers;
    using DevExpress.Mvvm.DataAnnotations;
    using DataAbstractWPF.Activators;
    
    namespace DataAbstractWPF.Bootstrapping
    {
        public class Installers : IWindsorInstaller
        {
            public void Install(IWindsorContainer container, IConfigurationStore store)
            {
    
                container.Register(Classes.FromThisAssembly()
                    .Where(type => AttributeHelper.HasAttribute(type, typeof(POCOViewModelAttribute)))
                    .Configure(r => r.Activator<DXViewModelActivator>())
                    .LifestyleTransient()
                    .WithServiceDefaultInterfaces()
                    );
    
                container.Register(Component.For<Interfaces.ICreateEntityWizard>().ImplementedBy<Views.CreateEntityWizard>().LifestyleTransient());
                container.Register(Component.For<Interfaces.IMainWindow>().ImplementedBy<Views.MainWindow>().LifestyleTransient());
                container.Register(Component.For<Interfaces.ICreateEntityKind>().ImplementedBy<Views.CreateEntityKind>().LifestyleTransient());
    
                container.Register(Component.For<Interfaces.IShell>().ImplementedBy<Shell>().LifestyleTransient());
            }
    
        }
    }
    

    And now in your constructors, the dependencies will be resolved.

    public partial class MainWindow : Window, Interfaces.IMainWindow
    { 
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        public MainWindow(Interfaces.IMainWindowViewModel context)
        {
            InitializeComponent();
            this.DataContext = context;
        }
    }