09 June 2014

Using Fody.PropertyChanged for automatic PropertyChangedEvents and model-to-viewmodel communication

When you are working with MVVM, you are faced with a couple of interesting challenges. Usually you have something like this:

You see a lot of properties with a lot of repetitive code. Of course, you can use snippets to make that easier, but still. It looks cluttered.

using GalaSoft.MvvmLight;

namespace MyApp.Logic.ViewModels
{
  public class DummyViewModel : ViewModelBase
  {
    private string myProperty;
    public string MyProperty
    {
      get { return myProperty; }
      set
      {
        if (myProperty != value)
        {
          myProperty = value;
          RaisePropertyChanged(() => MyProperty);
        }
      }
    }

    private string yourProperty;
    public string YourProperty
    {
      get { return yourProperty; }
      set
      {
        if (yourProperty != value)
        {
          yourProperty = value;
          RaisePropertyChanged(() => YourProperty);
        }
      }
    }

    public void DoSomething()
    {}
  }
}

And it usually gets more cluttered, as people tend to put what amounts to business logic into the viewmodel. I used to make a viewmodel as a wrapper around a model, like this.

namespace MyApp.Logic.Models
{
  public class DummyModel
  {
    public string YourProperty { get; set; }
    public string MyProperty { get; set; }

    public void DoSomeBusinessThing()
    { }
  }
}
and then something like this, also created via snippets
using GalaSoft.MvvmLight;
using MyApp.Logic.Models

namespace MyApp.Logic.ViewModels
{
  public class DummyViewModel : ViewModelBase
  {
    public DummyViewModel()
    {
    }

    public DummyViewModel(DummyModel model)
    {
      Model = model;
    }
    public DummyModel Model { get; set; }

    public string MyProperty
    {
      get { return Model.MyProperty; }
      set
      {
        if (Model.MyProperty != value)
        {
          Model.MyProperty = value;
          RaisePropertyChanged(() => MyProperty);
        }
      }
    }

    public string YourProperty
    {
      get { return Model.YourProperty; }
      set
      {
        if (Model.YourProperty != value)
        {
          Model.YourProperty = value;
          RaisePropertyChanged(() => YourProperty);
        }
      }
    }
  }
}
Nice. But now you have a different problem. I you call DoSomeBusinessThing and that affects "MyProperty" or "YourProperty" on the model in some way, how is the viewmodel supposed to know and fire a PropertyChanged?

Enter Fody - a great toolkit. It is basically an extensible toolkit for “weaving” .NET assemblies. It accepts plugins to decide what is actually injected. The most useful for our problem is the PropertyChanged.Fody.Which is also available as a Nuget package that you can easily add to your project. You then take your good old DummyObject and add the attribute ImplementPropertyChanged to it.

using PropertyChanged;

namespace Travalyzer.Logic.ViewModels
{
  [ImplementPropertyChanged]
  public class DummyModel
  {
    public string YourProperty { get; set; }
    public string MyProperty { get; set; }

    public void DoSomeBusinessThing()
    { }
  }
}

and boom. If something affects a property, the property will fire PropertyChanged. PropertyChanged.Fody will add all kinds of smart logic to your model, as described here. Ain't that cool, you don't even have to make a viewmodel anymore.

Well, sorry to rain on your parade, but if you now start to add commands to your enhanced model and properties or methods that are only used for display (for instance, a formatted date) you are basically polluting your business layer. It’s like adding business logic to your viewmodel, but coming from the opposite direction. Yes, you can now bind directly to model properties and they will fire a PropertyChanged, but business logic is business logic and thou shalt not pollute that with methods indented to deliver data for display and UI interaction. That is what viewmodels are for. And your viewmodel may still need to do something so it still may need to be notified. After some puzzling I came up with the following. First, I define a base class for my models with minimal baggage:

using System.ComponentModel;
using GalaSoft.MvvmLight.Threading;
using PropertyChanged;

namespace WpWinNl.MvvmLight
{
  [ImplementPropertyChanged]
  public class BaseNotifyingModel : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
      if (PropertyChanged != null)
      {
        DispatcherHelper.CheckBeginInvokeOnUI(() => 
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
      }
    }
  }
}

I don’t even use MVVMLight’s ViewmodelBase, but a very simple INotifyPropertyChanged implementation. PropertyChanged.Fody is apparently smart enough to leave that part of the class alone if it’s already implemented. But of course I do use other smart things by Laurent Bugnion – the DispatcherHelper. Mind you, this requires it being initialized in App.Xaml.cs. The most important thing is that the PropertyChanged event can be used by the viewmodel to track possible changes in the model. I use this in the following base view model, which is used on MVVMLight ViewModelBase:

using System.ComponentModel;
using GalaSoft.MvvmLight;
using PropertyChanged;

namespace WpWinNl.MvvmLight
{
  [ImplementPropertyChanged]
  public class TypedViewModelBase<T> : ViewModelBase where T : BaseNotifyingModel 
  {
    private T model;

    public TypedViewModelBase()
    {
    }

    public TypedViewModelBase(T model)
    {
      Model = model;
    }

    public T Model
    {
      get { return model; }
      set
      {
        if (value != model)
        {
          if (model != null)
          {
            model.PropertyChanged -= ModelPropertyChanged;
          }
          model = value;
          if (model != null)
          {
            model.PropertyChanged += ModelPropertyChanged;
          }
        }
      }
    }

    protected virtual void ModelPropertyChanged(object sender, 
       PropertyChangedEventArgs e)
    {
    }
  }
}

imageIf you derive your models from BaseNotifyingModel and your viewmodels from TypedViewModelBase you will have a model that automatically fires INotifyPropertyChanged on any property that is changed – so it can be used in data binding – and you will have viewmodels that will automatically be notified of any change in their model – and by overriding ModelPropertyChanged you can act on that.

Now of course this is all very nice and very architecty, but how would this ever be useful? Suppose you have a list of business object containing a location. When the user selects it, you want to display it’s street address. You can get that using the MapLocationFinder API. If you have a few 100 points, and you want to get all the addresses – this can take quite some time. So you want to do this on-demand, as displayed in the Windows Phone demo solution.

 

So I created this model:

using System;
using System.Linq;
using Windows.Devices.Geolocation;
using Windows.Services.Maps;
using WpWinNl.MvvmLight;

namespace FodyDemo.Models
{
  public class LocationData : BaseNotifyingModel
  {
    public LocationData()
    {
      TimeStamp = DateTime.Now;
    }

    public int Id { get; set; }

    public bool HasLocation { get; set; }

    public Geopoint Position { get; set; }

    public DateTimeOffset TimeStamp { get; set; }

    private MapLocation locationInfo;

    public MapLocation LocationInfo
    {
      get
      {
        if (locationInfo == null)
        {
          lock (this)
          {
            MapLocationFinder.FindLocationsAtAsync(Position).
AsTask().ContinueWith(p => { var firstLocationData = p.Result.Locations; if (firstLocationData != null) { LocationInfo = firstLocationData.FirstOrDefault(); HasLocation = true; } }); } } return locationInfo; } set { locationInfo = value; } } } }

Basically, only if the LocationInfo property is accessed it is actually retrieved. In real life, I would probably implement this using a “LoadLocation” method or something, but whatever – this works as well. If the location is found, not only the LocationInfo is set, but also the HasLocation property, which makes the checkbox light up. In the list, the following properties are bound like this: 

<TextBlock Text="{Binding Model.Id}" FontSize="15"
VerticalAlignment="Center"></TextBlock> <TextBlock Text="{Binding DateAndTime}" Grid.Column="1" FontSize="15"
VerticalAlignment="Center"></TextBlock> <CheckBox IsChecked="{Binding Model.HasLocation}" IsEnabled="False"
Grid.Column="2" ></CheckBox

You can see HasLocation is bound directly to the Model, still it gets a PropertyChanged event fired courtesy of Fody. The DataAndTime property apparently coming from the ViewModel, but Location Property is left alone. for now. The panel at the bottom, where the selected object is displayed, is like this

<TextBlock Text="{Binding Model.Id}" FontSize="15" ></TextBlock>
<TextBlock Text="{Binding DateAndTime}" Grid.Row="1" FontSize="15"></TextBlock>
<TextBlock Text="{Binding LocationName}" Grid.Row="2"
FontSize="15"></TextBlock>

In the the viewmodel it looks like this:

using System.ComponentModel;
using System.Globalization;
using System.Windows.Input;
using Windows.Services.Maps;
using FodyDemo.Messages;
using FodyDemo.Models;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using WpWinNl.MvvmLight;

namespace FodyDemo.ViewModels
{
  public class LocationViewModel : TypedViewModelBase<LocationData>
  {

    public LocationViewModel()
    {
    }

    public LocationViewModel(LocationData ld) : base(ld)
    {
    }

    public string DateAndTime
    {
      get
      {
        return Model != null ? 
          Model.TimeStamp.ToString("dd-MM-yyyy hh:mm:ss", 
            CultureInfo.InvariantCulture) : string.Empty;
      }
    }

    public string LocationName
    {
      get {
        return Model != null && Model.LocationInfo != null ? 
        GetFormattedAdress(Model.LocationInfo.Address) : string.Empty; }
    }

    private string GetFormattedAdress(MapAddress a)
    {
      return string.Format("{0} {1} {2} {3}", a.Street, a.StreetNumber,
a.Town, a.Country); } public ICommand SelectCommand { get { return new RelayCommand( () => Messenger.Default.Send(new SelectedObjectMessage(this))); } } protected override void ModelPropertyChanged(object sender,
PropertyChangedEventArgs e) { if (e.PropertyName == "LocationInfo") { RaisePropertyChanged(() => LocationName); } } } }

You can see the LocatioName property shows a neatly formatted address – and gets a manual RaisePropertyChanged when the ModelPropertyChanged is fired from the model (once again courtesy of Fody) – it checks if that is indeed the “LocationInfo” property and boom – the UI knows it must update because a property from the model is changed, even tough that property is not directly bound to the property.

Thus you can use the viewmodel as it is intended – a place to bind model and view together, a converter on steroids as Josh Smith put is so aptly as early as 2008. A simple base class will add some minimal extra functionality to your business class to make it play within MVVM – but it will still keep it clean.

Important detail – if you use the MVVMLight DispatcherHelper, you must initialize it first. I usually do this in the App.xaml.cs.

The demo solution, which contains some more code, can be found here. Credits go to the equally smart as sarcastic-in-a-fun-way Scott Lovegrove, who first put me on track with Fody quite some time ago, but only recently I was smart enough to actually take his advice and try it out. My base class for models was based on this Fody sample – but of course I thought I was smarter that that and opted for events rather than MVVMLight messages, it being a more lightweight solution. I like to reserve the Messenger for inter-viewmodel communication.

If you did indeed read all the way through here – congratulations. This is a very architect’s article, not some fun thing to make a cool new user control. But once in a while I start pondering about things like this, to keep my code cleaner, and make things better. If I haven’t bored you to death with it, you might be one of the very rare species that has some architectural genes hidden in your DNA too. I like to think I have, although I am more of a micro-architect, not one that only draws enormous complex diagrams for a living ;)

1 comment:

Jarno Peschier said...

Architectural genes: check.
Nice article.
Must play with Fody some time.