J’ai découvert le pattern MVVM récemment et grâce à Eric qui a publié un très beau tuto sur le sujet, à lire ici. Je pars d’ailleurs de son exemple pour illustrer la mise en œuvre de l’AOP dans le cadre d’utilisation de ce pattern.

L’AOP (Aspect Oriented Programming) est bien utile dans beaucoup de cas de figure en particulier pour automatiser certaines taches techniques et permettre de factoriser du code. Dans le pattern MVVM vous devez, après avoir setté une propriété, notifier un listener comme quoi vous venez de modifier cette propriété. Pour cela votre View-Model doit implémenter l’interface “INotifyPropertyChanged“. Cette notification se prête bien à un traitement par AOP et donc éviter d’écrire systématiquement : ” NotifyPropertyChanged(”MaProperty”);”.


namespace GalleryMVVM
{
	public class GalleryViewModel : ViewModel
    {
      ...
        public bool IsLoaded
        {
            get
            {
                return isLoaded;
            }
            set
            {
                isLoaded = value;

                NotifyPropertyChanged("IsLoaded");
            }
        }

        public ImageBrush NewPictureIB
        {
            get
            {
                return newPictureIB;
            }
            set
            {
                if (value != null)
                {
                    newPictureIB = value;

                    NotifyPropertyChanged("NewPictureIB");
                }
            }
        }

        public ImageBrush OldPictureIB
        {
            get
            {
                return oldPictureIB;
            }
            set
            {
                if (value != null)
                {
                    oldPictureIB = value;

                    NotifyPropertyChanged("OldPictureIB");
                }
            }
        }

        public double Percent
        {
            get
            {
                return percent;
            }
            set
            {
                percent = value;

                NotifyPropertyChanged("Percent");
            }
        }

        public string Paging
        {
            get
            {
                return paging;
            }
            set
            {
                paging = value;

                NotifyPropertyChanged("Paging");
            }
        }

        public Picture CurrentPicture
        {
            get
            {
                return currentPicture;
            }
            set
            {
                if (currentPicture != null)
                {
                    OldPictureIB = NewPictureIB;
                }

                currentPicture = value;

                if (!wc.IsBusy)
                {
                    Percent = 0;

                    wc.OpenReadAsync(new Uri(currentPicture.Url));

                    IsLoaded = false;

                    NotifyPropertyChanged("CurrentPicture");
                }
            }
        }

        public Visibility RightArrowVisibility
        {
            get
            {
                return rightArrowVisibility;
            }
            set
            {
                rightArrowVisibility = value;

                NotifyPropertyChanged("RightArrowVisibility");
            }
        }

        public Visibility LeftArrowVisibility
        {
            get
            {
                return leftArrowVisibility;
            }
            set
            {
                leftArrowVisibility = value;

                NotifyPropertyChanged("LeftArrowVisibility");
            }
        }

        public bool DisplayPageNumbers
        {
            get
            {
                return displayPageNumbers;
            }
            set
            {
                displayPageNumbers = value;

                NotifyPropertyChanged("DisplayPageNumbers");
            }
        }

        public Stretch ImageFillMode
        {
            get
            {
                return imageFillMode;
            }
            set
            {
                imageFillMode = value;

                NotifyPropertyChanged("ImageFillMode");
            }
        }
    }
.....
}

On peut déjà améliorer la syntaxe en utilisant une lambda expression :


namespace GalleryMVVM.ViewModels
{
    public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
    {

         public event PropertyChangedEventHandler PropertyChanged = delegate { };

        public void OnPropertyChanged(Expression> expression)
        {
            string propertyName = PropertyName.For(expression);
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        …
    }
}

On pourrait alors réécrire le View-Model comme suit :


namespace GalleryMVVM
{
	public class GalleryViewModel : ViewModel
    {
      ...
        public bool IsLoaded
        {
            get
            {
                return isLoaded;
            }
            set
            {
                isLoaded = value;
                OnPropertyChanged(() => isLoaded);
            }
        }

      .....
    }
.....
}

Il existe plusieurs techniques d’AOP (Static, Runtime…). Dans cette exemple je vais m’appuyer sur les DynamicProxy2 de Castle.

Pour commencer, supprimer le binding dans le Xaml :

....
	
		
	
	

Qui devient :

....
    

Puis dans le code behind on créer un proxy sur notre ViewModel et on va lui ajouter un intercepteur :

namespace GalleryMVVM
{
	public partial class MainView : UserControl
	{
		public MainView()
		{
            InitializeComponent();

            var proxyGenerator = new ProxyGenerator(new DefaultProxyBuilder());
            //var data = proxyGenerator.CreateClassProxy(new GalleryMVVM.Aop.PropertyChangedInterceptor());
            var data = proxyGenerator.CreateInterfaceProxyWithTarget(new GalleryViewModel(), new GalleryMVVM.Aop.PropertyChangedInterceptor());
            //var data = proxyGenerator.CreateClassProxy(typeof(GalleryViewModel), new PropertyChangedInterceptor());
            this.LayoutRoot.DataContext = data;

		}
	}
}

Le code de l’intercepteur :

namespace GalleryMVVM.Aop
{
    public class PropertyChangedInterceptor : IInterceptor
    {
        private const string SetToken = "set_";

        public void Intercept(IInvocation invocation)
        {
            invocation.Proceed();

            if (invocation.Method.Name.StartsWith(SetToken) && invocation.Proxy is ViewModel)
            {
                var methodInfo = invocation.Method;
                var model = (ViewModel)invocation.Proxy;
                model.NotifyPropertyChanged(methodInfo.Name.Substring(SetToken.Length));
            }
        }
    }
}