Click here to Skip to main content
11,415,390 members (83,873 online)
Click here to Skip to main content

Combining RX and MVVM

, 2 Jun 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
A small MVVM framework using RX

Introduction

In one of my previous positions, I was lucky enough to be involved in a project that used Reactive Extensions in C# extensively.  I also have done a lot of UI (WPF) development using MVVM, so I decided to have a go at combining MVVM with RX.

To do this I wanted to write an application that is simple but to capture most of the aspects of MVVM. The application that will be implemented is an address book, which has the following features@:

  • All persons will be displayed in a list on the main page
  • Details of the selected person can be viewed/edited by clicking Details button on the item on list
  • Selected person can be deleted from the list by clicking on Delete button on the item on list
  • A new person can be added

The application will use Autofac as an IoC and all dependencies will be injected in constructors. Internally in the ViewModel the properties and commands should be observables. So I will start with the properties first. Since the properties can be read/write then they need to implement both IObservable and IObserver interfaces so we can push new values and also subscribe to the values. Fortunately RX has already an interface defined like that and it is called ISubject. So our property will extend ISubject interface and will have only one property called Value, look like this:

public interface IPropertySubject<T> : ISubject<T>  
{  
    T Value { get; set; }  
}  

And the implementation will look like:

public class PropertySubject<T> : IPropertySubject<T>  
{  
    private readonly Subject<T> _subject = new Subject<T>();  
    private T _value;  
    public void OnNext(T value)  
    {  
        SetValue(value);  
    }  
    private void SetValue(T value)  
    {  
        _value = value;  
        _subject.OnNext(value);              
    }  
    public void OnError(Exception error)  
    {  
        _subject.OnError(error);  
    }  
    public void OnCompleted()  
    {  
        _subject.OnCompleted();  
    }  
    public IDisposable Subscribe(IObserver<T> observer)  
    {  
        return _subject.Subscribe(observer);  
    }  
    public T Value  
    {  
        get { return _value; }  
        set { SetValue(value); }  
    }  
}  

From implementation we can see that we are using an instance of Subject class that is provided by RX. Subject implements both IObserver and IObservable. The value of the property can be set in two ways, either by using OnNext() method, or by simply setting the  Value property.

Next task is to implement the command. The command interface will implement ICommand and look like:

public interface ICommandObserver<T> : ICommand  
{  
    IObservable<T> OnExecute { get; }  
    IObserver<bool> SetCanExecute { get; }  
}  

Every time Execute() method is called on ICommand, OnExecute will push a new value of type T to an observable. SetCanExecute sets the value that will be returned by ICommand's CanExecute method and raising CanExecuteChanged event (enabling/disabling the UI component).

The implementation of ICommandObserver looks like:

public class CommandObserver<T> : ICommandObserver<T>  
{  
    private bool _canExecute;  
    private readonly ISubject<T> _executeSubject = new Subject<T>();  
    private readonly ISubject<bool> _canExecuteSubject = new Subject<bool>();  
    public CommandObserver() : this(true)  
    {  
    }  
    public CommandObserver(bool value)  
    {  
        _canExecute = value;  
        _canExecuteSubject.DistinctUntilChanged().Subscribe(v =>  
        {  
            _canExecute = v;  
            if (CanExecuteChanged != null)  
                CanExecuteChanged(this, EventArgs.Empty);  
        });  
    }  
    void ICommand.Execute(object parameter)  
    {  
        if(parameter is T)  
            _executeSubject.OnNext((T) parameter);  
        else  
            _executeSubject.OnNext(default(T));  
    }  
    bool ICommand.CanExecute(object parameter)  
    {  
        return _canExecute;  
    }  
    public event EventHandler CanExecuteChanged;  
    public IObservable<T> OnExecute  
    {  
        get { return _executeSubject.AsObservable(); }  
    }  
    public IObserver<bool> SetCanExecute  
    {  
        get { return _canExecuteSubject.AsObserver(); }  
    }  
}  

As you can see, one of the constructors takes a boolean value. This value is the initial value that will be returned by CanExecute() method (enabling/disabling the UI component). Another thing that happens in constructor is that, we only raise the event CanExecuteChanged if the value of pushed on SetCanExecute observer changes from true to false or vice versa. So basically even if we push a number of same values in the SetCanExecute the event is raised only once (another cool feature of RX using DistinctUntilChanged() method).

Now we create a base viewmodel that all our view models will inherit from. The class definition looks like:

public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable  
{  
    private readonly CompositeDisposable _disposables = new CompositeDisposable();  
    public event PropertyChangedEventHandler PropertyChanged;  
  
    public void OnPropertyChanged(string propertyName)  
    {  
        PropertyChangedEventHandler handler = PropertyChanged;  
        if (handler != null)   
            handler(this, new PropertyChangedEventArgs(propertyName));  
    }  
  
    public void ShouldDispose(IDisposable disposable)  
    {  
        _disposables.Add(disposable);     
    }  
  
    public void Dispose()  
    {  
        _disposables.Dispose();  
    }  
}  

When we subscribe to an observable an IDisposable is returned. To unsubscribe from the observable we call Dispose() method on the returned IDisposable. Since all our properties and commands on our view models will be observables, everytime we subscribe to an observable we need to keep track of the returned disposable so when the viewmodel is disposed then we need to unsubscribe from all observables. We do this by adding the returned disposable (when subscribing) to the CompositeDisposable instance (_disposables object). CompositeDisposable is another class from RX which is a list of disposables and when Dispose() method is called on it, it will call Dispose() method of all items that it contains.

Next step is to create a property provider that will create a property or command that is declared in the viewmodels' interface. So the property provider will look like:

public interface IPropertyProvider<T> : IDisposable  
{  
    IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression);  
    IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression, K value);  
    IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression, IObservable<K> values);  
    ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression);  
    ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression, bool isEnabled);  
    ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression, IObservable<bool> isEnabled);  
}  

The interface declares three overloaded methods for creating properties. The first method returns a new instance of the right property type, the second method returns a new instance of the property and sets its initial value to the provided, and the third method returns an instance of the property and sets its value to the values of the provided observable (updating the value everytime a new element comes in the observable). The same is with the methods for creating the commands.

The implementation of the property provider interface looks like:

public class PropertyProvider<T> : IPropertyProvider<T>  
{  
    private readonly ViewModelBase _viewModel;  
    private readonly ISchedulers _schedulers;  
    private readonly CompositeDisposable _disposables = new CompositeDisposable();  
  
    public PropertyProvider(ViewModelBase viewModel, ISchedulers schedulers)  
    {  
        _viewModel = viewModel;  
        _schedulers = schedulers;  
        _viewModel.ShouldDispose(_disposables);  
    }  
  
    private PropertySubject<K> GetProperty<K>(Expression<Func<T, K>> expr)  
    {  
        var propertyName = ((MemberExpression) expr.Body).Member.Name;  
        var propSubject = new PropertySubject<K>();  
  
        _disposables.Add(propSubject.ObserveOn(_schedulers.Dispatcher)  
                                    .Subscribe(v => _viewModel.OnPropertyChanged(propertyName)));  
        return propSubject;  
    }  
  
    public void Dispose()  
    {  
        if(!_disposables.IsDisposed)  
            _disposables.Dispose();  
    }  
  
    public IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression)  
    {  
        return GetProperty(expression);  
    }  
  
    public IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression, K value)  
    {  
        var propSubject = GetProperty(expression);  
        propSubject.Value = value;  
        return propSubject;  
    }  
  
    public IPropertySubject<K> CreateProperty<K>(Expression<Func<T, K>> expression, IObservable<K> values)  
    {  
        var propSubject = GetProperty(expression);  
        _disposables.Add(values.Subscribe(v => propSubject.Value = v));  
        return propSubject;  
    }  
  
    public ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression)  
    {  
        return new CommandObserver<K>(true);  
    }  
  
    public ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression, bool isEnabled)  
    {  
       return new CommandObserver<K>(isEnabled);  
    }  
  
    public ICommandObserver<K> CreateCommand<K>(Expression<Func<T, ICommand>> expression, IObservable<bool> isEnabled)  
    {  
        var cmd = new CommandObserver<K>(true);  
        _disposables.Add(isEnabled.Subscribe(cmd.SetCanExecute));  
        return cmd;  
    }  
}  

From the implementation we can see that PropertyProvider takes an instance of ViewModelBase and ISchedulers in its constructor. ISchedulers  (the definition and implementation are defined further down) is just a wrapper around different schedulers (Thread, ThreadPool, Task, Dispatcher, ...) that are provided by RX.

As we can see from the GetProperty<>(...) method, everytime a new property subject is created, we subscribe to it so whenever a new value is pushed to it, the PropertyChanged event is fired on the view model automatically, so on our view models we don't need to worry about raising the PropertyChanged event. Another cool feature that is provided by RX is that since we are observing on dispatcher scheduler (using ObserveOn(...) method provided by RX) we can change the value of the property on any thread and the property will be updated on the dispatcher. So no need for the ugly check InvokeRequired or CheckAccess() and BeginInvoke().

Now, instead of creating concrete instances of the PropertyProvider class in our viewmodels for the properties, we are going to defer the creation of the property provider to a factory class and then inject this factory on the constructor of our viewmodels. This allows us when we write unit tests to fake the factory, and are also we adhere to SRP (Single Responsibility Principle) on the viewmodel (a pattern that I use in my projects, especially when using IoC).

So the interface for the factory looks like:

public interface IPropertyProviderFactory  
{  
    IPropertyProvider<T> Create<T>(ViewModelBase viewModelBase);  
}  

and the implementation looks like:

public class PropertyProviderFactory : IPropertyProviderFactory  
{  
    private readonly ISchedulers _schedulers;  
  
    public PropertyProviderFactory(ISchedulers schedulers)  
    {  
        _schedulers = schedulers;  
    }  
  
    public IPropertyProvider<T> Create<T>(ViewModelBase viewModelBase)  
    {  
        return new PropertyProvider<T>(viewModelBase, _schedulers);  
    }  
}  

The next thing to implement is a messaging bus that will be used by viewmodels to communicate with each other (similar to Event Aggregator Pattern). The interface of the messaging bus is very simple

public interface IMessageBus  
{  
    IDisposable Subscribe<T>(Action<T> action);  
    void Publish<T>(T item);  
}  

So if we want to publish a message we use the Publish(...) method by providing the instance of message type (messages are declared as classes), and if we want to listen to a particular type of a message we subscribe to a relevant type of message and pass an action that will be executed when the message is published.

The last thing left before we can move to our application is signature and implementation of the ISchedulers interface which looks like this:

public interface ISchedulers  
{  
    IScheduler Dispatcher { get; }  
    IScheduler NewThread { get; }  
    IScheduler NewTask { get; }  
    IScheduler ThreadPool { get; }  
    IScheduler Timer { get; }  
}  
  

And the implementation:

public class Schedulers : ISchedulers  
{  
    public IScheduler Dispatcher  
    {  
        get { return DispatcherScheduler.Instance; }  
    }  
  
    public IScheduler NewThread  
    {  
        get { return Scheduler.NewThread; }  
    }  
  
    public IScheduler NewTask  
    {  
        get { return Scheduler.ThreadPool; }  
    }  
  
    public IScheduler ThreadPool  
    {  
        get { return Scheduler.ThreadPool; }  
    }  
  
    public IScheduler Timer  
    {  
        get { return Scheduler.Immediate; }  
    }  
}  

As mentioned before, the reason for defining ISchedulers interface and not using the RX schedulers is that when we write unit tests, we can fake ISchedulers interface and return test scheduler.

Now we are left with implementing the application itself. Firstly, we will create a window that will be used as a dialog window to display the details of the person. This window is a standard WPF window. But we want to make this window generic enough so it can display any viewmodel that we pass to it, so we will need to modify the constructor. The definition of the class (on code behind) looks like :

public partial class DialogWindow : Window  
{  
    public DialogWindow(ViewModelBase viewModel, ISchedulers schedulers)  
    {  
        InitializeComponent();  
        DataContext = viewModel;  
        var closeable = viewModel as ICloseable;  
        if (closeable != null)  
            closeable.Close.ObserveOn(schedulers.Dispatcher).Subscribe(r =>  
                {  
                    CloseResult = r;  
                    Close();  
                });  
    }  
    public bool CloseResult { get; private set; }   
}  

And the XAML looks like:

<Window x:Class="Test_Mvvm.DialogWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="clr-namespace:Test_Mvvm.Views"  
        xmlns:viewModels="clr-namespace:Test_Mvvm.ViewModels"  
        Title="DialogWindow" Height="300" Width="300" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"   
        ResizeMode="NoResize" Background="#FF737D81">  
    <Window.Resources>  
        <DataTemplate DataType="{x:Type viewModels:PersonViewModel}">  
            <views:PersonDetailsView />  
        </DataTemplate>  
    </Window.Resources>  
  <Grid>  
        <ContentControl Content="{Binding}" />  
    </Grid>  
</Window>  

From the implementation we can see that constructor takes two parameters, the first one is an instance of a  ViewModelBase and the second on is an instance of ISchedulers. Then we set the window's DataContext to the viewmodel, and in XAML we define a DataTemplate that will be used for rendering. In our case we only have the PersonViewModel to be displayed. Then we check if the provided viewmodel implement ICloseable interface, which is delcared as :

public interface ICloseable  
{  
    IObservable<bool> Close { get; }  
}  

and if the viewmodel implements the interface, then the windows subscribes to it's Close observable property and whenever a value is pushed to the Close observable the windows will close. This is to enable the viewmodel to close the window since the window does not know anything about our viewmodels or views (it uses the defined data template to render the viewmodel and nothing else).  So, in our case when user clicks on Cancel or Save buttons on the details view, then the dialog needs to be closed. Again we are using schedulers to schedule the closing of the window to happen on the dispatcher scheduler, thus allowing us to push a value to Close observable from any thread.

Now we will start implementing the viewmodels for our application, I will start with the person viewmodel interface. The interface is defined as:

public interface IPersonViewModel : IDisposable  
{  
    string Name { get; }  
    string Surname { get; }  
    int Age { get; }  
    string Address1 { get; }  
    string Address2 { get; }  
    string Address3 { get; }  
    string City { get; }  
    ICommand EditCommand { get; }  
    ICommand SaveCommand { get; }  
    ICommand DeleteCommand { get; }  
    ICommand CloseCommand { get; }  
}  

Next step is to create design data that can be used by Visual Studio WPF designer or Expression Blend. The design data is a class that implements IPersonViewModel interface and it is a simple class with some initial values set. The  implementation of the design data class looks like:

public class DPersonViewModel : IPersonViewModel  
{  
    public DPersonViewModel()  
    {  
        Name = "Joe";  
        Surname = "Bloggs";  
        Age = 40;  
        Address1 = "Flat 1";  
        Address2 = "Calder court";  
        Address3 = "253 Rotherhithe Street";  
        City = "London";  
    }  
  
    public string Name { get; set; }  
    public string Surname { get; set; }  
    public int Age { get; set; }  
    public string Address1 { get; set; }  
    public string Address2 { get; set; }  
    public string Address3 { get; set; }  
    public string City { get; set; }  
    public ICommand EditCommand { get; set; }  
    public ICommand SaveCommand { get; set; }  
    public ICommand DeleteCommand { get; set; }  
    public ICommand CloseCommand { get; set; }  
  
    public void Dispose()  
    {  
    }  
}  

Now we need to build the views. We have two different views for the PersonViewModel, one will be to display/edit details and the other will be to show the person data in the list. We will start first with the list item and it will be called PersonSummaryView. The xaml for the view will look like this:

<UserControl x:Class="Test_Mvvm.Views.PersonSummaryView" 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:dd="clr-namespace:Test_Mvvm.DesignData" 
            mc:Ignorable="d" d:DataContext="{d:DesignInstance Type=dd:DPersonViewModel, IsDesignTimeCreatable=True}" 
            d:DesignHeight="55" d:DesignWidth="500" BorderBrush="DarkGray" BorderThickness="1" Margin="5" Background="#33FFFFFF" > 
    <UserControl.Resources> 
        <ControlTemplate x:Key="LinkButton" TargetType="{x:Type Button}"> 
            <TextBlock HorizontalAlignment="Center" Margin="0" Text="{TemplateBinding Content}"  
                      Cursor="Hand" TextWrapping="Wrap" VerticalAlignment="Center"  
                      TextDecorations="Underline" Foreground="#FF0025BA" /> 
        </ControlTemplate> 
    </UserControl.Resources>  

    <Grid Margin="5"> 
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center"> 
            <TextBlock Text="{Binding Name}" FontSize="13" FontWeight="Bold" /> 
            <TextBlock Text=" " FontSize="13" FontWeight="Bold" /> 
            <TextBlock Text="{Binding Surname}" FontSize="13" FontWeight="Bold"/> 
        </StackPanel> 
        <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,70,0" Width="130"> 
            <TextBlock Text="{Binding Address1}" FontSize="8" HorizontalAlignment="Center" /> 
            <TextBlock Text="{Binding Address2}" FontSize="8" HorizontalAlignment="Center"/> 
            <TextBlock Text="{Binding Address3}" FontSize="8" HorizontalAlignment="Center"/> 
            <TextBlock Text="{Binding City}" FontSize="8" HorizontalAlignment="Center"/> 
        </StackPanel>  

        <Button Template="{StaticResource LinkButton}" Command="{Binding EditCommand}"  
                Content="Details" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,3,5,0" />  

        <Button Template="{StaticResource LinkButton}" Command="{Binding DeleteCommand}"   
                Content="Delete" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,5,3" />     
    </Grid> 
</UserControl>  

 

And the control inside Visual Studio designer looks like

and as we can see design data which are bound to the different controls and are displayed in the designer making styling the control very easy. The design data for the view is set using d:DataContext="{d:DesignInstance Type=dd:DPersonViewModel, IsDesignTimeCreatable=True}" at the beginning. 

Next, we design the view to display person's details. Again we will use the same design data to style our view. After styling, the xaml for the view looks like:

<UserControl x:Class="Test_Mvvm.Views.PersonDetailsView"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:dd="clr-namespace:Test_Mvvm.DesignData"  
            mc:Ignorable="d"    
            d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance Type=dd:DPersonViewModel, IsDesignTimeCreatable=True}"                
            Height="260" Width="330">  
    <Grid Margin="5">  
        <Grid.RowDefinitions>  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="30" />  
            <RowDefinition Height="85*" />  
        </Grid.RowDefinitions>  
        <Grid.ColumnDefinitions>  
            <ColumnDefinition Width="80" />  
            <ColumnDefinition Width="*" />  
        </Grid.ColumnDefinitions>  
        <TextBlock Text="Name :" Grid.Column="0" Grid.Row="0" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" />  
  
        <TextBlock Text="Surname :" Grid.Column="0" Grid.Row="1" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Surname, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" />  
  
        <TextBlock Text="Address1 :" Grid.Column="0" Grid.Row="2" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Address1, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" />  
  
        <TextBlock Text="Address2 :" Grid.Column="0" Grid.Row="3" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Address2, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="3" VerticalAlignment="Center" />  
  
        <TextBlock Text="Address3 :" Grid.Column="0" Grid.Row="4" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Address3, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" />  
  
        <TextBlock Text="City :" Grid.Column="0" Grid.Row="5" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding City, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="5" VerticalAlignment="Center" />  
  
        <TextBlock Text="Age :" Grid.Column="0" Grid.Row="6" VerticalAlignment="Center"/>  
        <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="6" VerticalAlignment="Center" />  
  
        <Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="1" Grid.Row="7" VerticalAlignment="Bottom" HorizontalAlignment="Right"   
                Width="80" Margin="0,0,95,0"/>  
        <Button Content="Cancel" Command="{Binding CloseCommand}" Grid.Column="1" Grid.Row="7" VerticalAlignment="Bottom" HorizontalAlignment="Right"   
                Width="80" Margin="0,0,5,0" />  
    </Grid>  
</UserControl>  

 

And the control on the designer will look like:

The next step is to define the data model for the person. The model is used to retreived the data from the repository (database) and send the data to repository.  The data model will look like :

public class Person  
{  
    public string Name { get; set; }  
    public string Surname { get; set; }  
    public int Age { get; set; }  
    public string Address1 { get; set; }  
    public string Address2 { get; set; }  
    public string Address3 { get; set; }  
    public string City { get; set; }  
}  

Now, before we dive in and implement the person viewmodel, lets first describe the tasks that this viewmodel will handle. First, the viewmodel will take a data model and populate the properties with the model values. Then, when user clicks on Details button on the summary view (PersonSummeryView) the viewmodel should bring up a dialog with the details so the user can view/edit the details. If the user changes detials on the details dialog and clicks on Save button, then the model should be updated and the model should be saved into a repository. Otherwise if the user clicks on Cancel button then all the changes will be reverted and the dialog will be closed.

Next, when user clicks on Delete button on summary view the item should be deleted from the repository and the item should be removed from the list. Now, because the viewmodel does not have any knowledge about the container (in our case the list), it does not know how to delete itself an remove itself from the list. So instead of passing the parent viewmodel to it and tightly coupling them together, the viewmodel will publish a message to MessageBus and whoever listens for the message has the knowledge necessary to delete the viewmodel and remove it from the list.

So the implementation of the viewmodel looks like:

public class PersonViewModel : ViewModelBase, IPersonViewModel, ICloseable   
{  
    private Person _personModel;  
    private readonly IPropertySubject<string> _name;  
    private readonly IPropertySubject<string> _surname;  
    private readonly IPropertySubject<string> _address1;  
    private readonly IPropertySubject<int> _age;  
    private readonly IPropertySubject<string> _address2;  
    private readonly IPropertySubject<string> _address3;  
    private readonly IPropertySubject<string> _city;  
    private readonly ICommandObserver<Unit> _editCommand;  
    private readonly ICommandObserver<Unit> _saveCommand;  
    private readonly ICommandObserver<Unit> _deleteCommand;  
    private readonly ISubject<bool> _closeSubject = new Subject<bool>();  
    private readonly ICommandObserver<Unit> _closeCommand;  
    public PersonViewModel(IPropertyProviderFactory providerFactory,   
                            IMessageBus messageBus,   
                            IDialogService dialogService,  
                            Person personModel)  
    {  
        _personModel = personModel;  
        var pp = providerFactory.Create<IPersonViewModel>(this);  
        // creating properties  
        _name = pp.CreateProperty(i => i.Name, personModel.Name);  
        _surname = pp.CreateProperty(i => i.Surname, personModel.Surname);  
        _age = pp.CreateProperty(i => i.Age, personModel.Age);  
        _address1 = pp.CreateProperty(i => i.Address1, personModel.Address1);  
        _address2 = pp.CreateProperty(i => i.Address2, personModel.Address2);  
        _address3 = pp.CreateProperty(i => i.Address3, personModel.Address3);  
        _city = pp.CreateProperty(i => i.City, personModel.City);  
        // creating commands  
        _editCommand = pp.CreateCommand<Unit>(i => i.EditCommand);  
        _saveCommand = pp.CreateCommand<Unit>(i => i.SaveCommand, !string.IsNullOrEmpty(personModel.Name) && !string.IsNullOrEmpty(personModel.Surname));  
        _deleteCommand = pp.CreateCommand<Unit>(i => i.DeleteCommand);  
        _closeCommand = pp.CreateCommand<Unit>(i => i.CloseCommand);  
        ShouldDispose(_name.CombineLatest(_surname, (n, s) => !string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(s))  
                .Subscribe(_saveCommand.SetCanExecute));  
        ShouldDispose(_saveCommand.OnExecute.Subscribe(_ =>  
            {  
                UpdateModel();  
                _closeSubject.OnNext(true);  
            }));  
        ShouldDispose(_editCommand.OnExecute.Subscribe(_ => dialogService.ShowViewModel("Edit Person", this)));  
        ShouldDispose(_deleteCommand.OnExecute.Subscribe(_ => messageBus.Publish(new DeleteMessage<PersonViewModel>(this))));  
        ShouldDispose(_closeCommand.OnExecute.Subscribe(_ =>  
            {  
                ResetData();  
                _closeSubject.OnNext(false);  
            }));  
    }  
    public string Name  
    {  
        get { return _name.Value; }  
        set { _name.Value = value; }  
    }  
    public string Surname  
    {  
        get { return _surname.Value; }  
        set { _surname.Value = value; }  
    }  
    public int Age  
    {  
        get { return _age.Value; }  
        set { _age.Value = value; }  
    }  
    public string Address1  
    {  
        get { return _address1.Value; }  
        set { _address1.Value = value; }  
    }  
    public string Address2  
    {  
        get { return _address2.Value; }  
        set { _address2.Value = value; }  
    }  
    public string Address3  
    {  
        get { return _address3.Value; }  
        set { _address3.Value = value; }  
    }  
    public string City  
    {  
        get { return _city.Value; }  
        set { _city.Value = value; }  
    }  
    public ICommand EditCommand  
    {  
        get { return _editCommand; }  
    }  
    public ICommand SaveCommand  
    {  
        get { return _saveCommand; }  
    }  
    public ICommand DeleteCommand  
    {  
        get { return _deleteCommand; }  
    }  
    public ICommand CloseCommand  
    {  
        get { return _closeCommand; }  
    }  
    IObservable<bool> ICloseable.Close  
    {  
        get { return _closeSubject; }  
    }  
    private void UpdateModel()  
    {  
        _personModel = this.ToModel();  
    }  
    private void ResetData()  
    {  
        Name = _personModel.Name;  
        Surname = _personModel.Surname;  
        Address1 = _personModel.Address1;  
        Address2 = _personModel.Address2;  
        Address3 = _personModel.Address3;  
        City = _personModel.City;  
        Age = _personModel.Age;  
    }  
}  

As we can see, everything happens in a constructor, so if we start disecting the constructor we can see that initially we create the properties and we set the initial values from the data model:

 _name = pp.CreateProperty(i => i.Name, personModel.Name);  
 _surname = pp.CreateProperty(i => i.Surname, personModel.Surname);  
 _age = pp.CreateProperty(i => i.Age, personModel.Age);  
 _address1 = pp.CreateProperty(i => i.Address1, personModel.Address1);  
 _address2 = pp.CreateProperty(i => i.Address2, personModel.Address2);  
 _address3 = pp.CreateProperty(i => i.Address3, personModel.Address3);  
 _city = pp.CreateProperty(i => i.City, personModel.City);  

then we create the commands :

 _editCommand = pp.CreateCommand<Unit>(i => i.EditCommand);  
 _saveCommand = pp.CreateCommand<Unit>(i => i.SaveCommand, !string.IsNullOrEmpty(personModel.Name) && !string.IsNullOrEmpty(personModel.Surname));  
 _deleteCommand = pp.CreateCommand<Unit>(i => i.DeleteCommand);  
 _closeCommand = pp.CreateCommand<Unit>(i => i.CloseCommand);  

As we can see, save command initially will be enabled if the Name and Surname properties on the datamodel are not null or empty.

In the next line we combine _name and _surname observables using CombineLatest() method, and then we subscribe to the result by providing save command's SetCanExecute property (which is an observer). So anytime a new value is pushed on either name or surname observable, the provided function will get the values of both observables and will return another value (in our case it will produce a boolean value depending if the values of the name and surname are not null or empty). Then the result produced by the function will be pushed into an observable, which in turn will be pushed ino SetCanExecute thus enabling/disabling the control depending on the result.

Next, on the following piece of code :

ShouldDispose(_saveCommand.OnExecute.Subscribe(_ =>  
    {  
        UpdateModel();  
        _closeSubject.OnNext(true);  
    }));  

we subscribe to _saveCommand.OnExecute and anytime  the SaveCommand  command is executed (by clicking on Save button on details view in our case) we update the data model and push a value to Close observable thus closing the dialog.

On the next line :

ShouldDispose(_editCommand.OnExecute.Subscribe(_ => dialogService.ShowViewModel("Edit Person", this)));  

we subscribe to _editCommand.OnExecute and whenever the edit command is executed (by clicking Details button on summary view) then we, insead of creating the dialog ourselves in the viewmodel, we delegate it to the dialog service (by calling ShowViewModel() method). In this way, as we said before when we write unit tests we can fake the dialog service and we are also adhering to SRP principle in our viewmodel.

then on the next line :

ShouldDispose(_deleteCommand.OnExecute.Subscribe(_ => messageBus.Publish(new DeleteMessage<PersonViewModel>(this))));  

we are subscribing to the delete command and whenever it gets executed (by clicking on Delete button on the summary view) then we will publish a new DeleteMessage on the message bus. So whoever is listening to this message can react on it.

And lastly, on the next block:

ShouldDispose(_closeCommand.OnExecute.Subscribe(_ =>  
    {  
        ResetData();  
        _closeSubject.OnNext(false);  
    }));  

we are subscribing to Close command, so whenver the user clicks on Close button on the details view, then we are reverting property values and closing the dialog (by pushing a value to Close observable defined in ICloseable interface and implemented in this viewmodel).

The next step is to define and implement dialog service. The definition of the dialog service looks like :

public interface IDialogService  
{  
    void ShowInfo(string title, string message);  
    MessageBoxResult ShowWarning(string title, string message);  
    void ShowError(string title, string message);  
    bool ShowViewModel(string title, ViewModelBase viewModel);  
}  

And the implementation looks like :

public class DialogService : IDialogService  
{  
    private readonly ISchedulers _schedulers;  
    public DialogService(ISchedulers schedulers)  
    {  
        _schedulers = schedulers;  
    }  
    public void ShowInfo(string title, string message)  
    {  
        MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);  
    }  
    public MessageBoxResult ShowWarning(string title, string message)  
    {  
        return MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning);  
    }  
    public void ShowError(string title, string message)  
    {  
        MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);  
    }  
    public bool ShowViewModel(string title, ViewModelBase viewModel)  
    {  
        var win = new DialogWindow(viewModel, _schedulers) { Title = title };  
        win.ShowDialog();  
        return win.CloseResult;  
    }  
}  

Now we need to define and implement the repository that will hold person objects. The definition of the repository looks like:

public interface IPersonRepository  
{  
    IObservable<IEnumerable<Person>> GetAllPersons();  
    IObservable<bool> Save(Person person);  
    IObservable<bool> Delete(Person person);  
}  

As we can see from the definition for the interface, all the methods are declared as observables. This allows us to subscribe to the method and react when we get the data making the methods asynchronous. For the purpose of this application we will only hold the data on the list in memory, but for a real application we would implment this interface so the data would be stored in a database. The implementation of the repository interface looks like:

public class PersonRepository : IPersonRepository  
{  
    private readonly List<Person> _persons;  
    public PersonRepository()  
    {  
        _persons = new List<Person>  
            {  
                new Person { Name = "Joe", Surname = "Blogs", Address1 = "Address1", Address2 = "Address2", Address3 = "Address3", Age = 21, City = "London"},  
                new Person { Name = "Mary", Surname = "Jones", Address1 = "Address1", Address2 = "Address2", Address3 = "Address3", Age = 28, City = "New York"},  
            };  
    }  
    public IObservable<IEnumerable<Person>> GetAllPersons()  
    {  
        return ToObservable(() =>  
            {  
                IEnumerable<Person> cl = _persons;  
                Thread.Sleep(1000);  
                return cl;  
            });  
    }  
    public IObservable<bool> Save(Person person)  
    {  
        return ToObservable(() =>  
            {  
                _persons.Add(person);  
                Thread.Sleep(1000);  
                return true;  
            });  
    }  
    public IObservable<bool> Delete(Person person)  
    {  
        return ToObservable(() =>  
            {  
                var cl = _persons.FirstOrDefault(c => c.Name == person.Name && c.Surname == person.Surname);  
                if (cl != null)  
                {  
                    _persons.Remove(cl);  
                    return true;  
                }  
                return false;  
            });  
    }  
    private IObservable<T> ToObservable<T>(Func<T> func)  
    {  
        return func.ToAsync()();  
    }  
}  

As we can see from the implementation of the private method ToObservable<>(), RX has another cool feature that can convert a function to an async functions that returns an observable. To mimic a long running process we also have added a Thread.Sleep for 1 second when we call GetAllPersons() and Save() methods.

The next step is to define main viewmodel. The main viewmodel will be responsible for getting all persons from the repository and displaying them in a list view, adding a new person, and deleting the appropriate person from the list. So the definition looks like :

public interface IMainViewModel : IDisposable  
{  
    ObservableCollection<IPersonViewModel> Clients { get; }  
    ICommand AddNewClientCommand { get; }  
    bool IsBusy { get; }  
}  

And the implementation looks like :

public class MainViewModel : ViewModelBase, IMainViewModel  
{  
    private readonly ICommandObserver<Unit> _addNewClientCommand;  
    private readonly IPropertySubject<bool> _isBusy;  
    public MainViewModel(IPropertyProviderFactory providerFactory,   
                                    IMessageBus messageBus,   
                                    ISchedulers schedulers,  
                                    IPersonViewModelFactory personViewModelFactory,  
                                    IDialogService dialogService,  
                                    IPersonRepository personRepository)  
    {  
        var pp = providerFactory.Create<IMainViewModel>(this);  
        _isBusy = pp.CreateProperty(i => i.IsBusy, false);  
        _addNewClientCommand = pp.CreateCommand<Unit>(i => i.AddNewClientCommand);  
        Clients = new ObservableCollection<IPersonViewModel>();  
        IsBusy = true;  
        ShouldDispose(personRepository.GetAllClients()          
                                        .ObserveOn(schedulers.Dispatcher)  
                                        .Subscribe(c =>  
                                            {  
                                                foreach(var item in  c.Select(personViewModelFactory.Create))  
                                                    Clients.Add(item);  
                                                IsBusy = false;  
                                            }));  
        ShouldDispose(messageBus.Subscribe<DeleteMessage<PersonViewModel>>(m =>  
            {  
                var msg = string.Format("Are you sure you want to delete {0} {1}?", m.ViewModel.Name, m.ViewModel.Surname);  
                      
                if (dialogService.ShowWarning("Delete", msg) == MessageBoxResult.OK)  
                {  
                    IsBusy = true;  
                    ShouldDispose(personRepository.Delete(m.ViewModel.ToModel())  
                                                    .ObserveOn(schedulers.Dispatcher)  
                                                    .Subscribe(_ =>  
                                                        {  
                                                            Clients.Remove(m.ViewModel);  
                                                            IsBusy = false;  
                                                        }));  
                }                                
            }));  
        ShouldDispose(_addNewClientCommand.OnExecute.Subscribe(p =>  
            {  
                var newClient = personViewModelFactory.Create(new Person());  
                if (dialogService.ShowViewModel("New person", newClient as ViewModelBase))  
                {  
                    IsBusy = true;  
                    ShouldDispose(personRepository.Save(newClient.ToModel())  
                                                    .ObserveOn(schedulers.Dispatcher)  
                                                    .Subscribe(_ =>  
                                                        {  
                                                            IsBusy = false;  
                                                            Clients.Add(newClient);  
                                                        }));  
                }  
            }));  
    }  
    public ObservableCollection<IPersonViewModel> Clients { get; private set; }  
    public bool IsBusy  
    {  
        get { return _isBusy.Value; }  
        set { _isBusy.Value = value; }  
    }  
    public ICommand AddNewClientCommand  
    {  
        get { return _addNewClientCommand; }  
    }  
}  

So if we disect the constructor again, we will see that at the beginning we create properties:

_isBusy = pp.CreateProperty(i => i.IsBusy, false);  
_addNewClientCommand = pp.CreateCommand<Unit>(i => i.AddNewClientCommand);  
Clients = new ObservableCollection<IPersonViewModel>();  

On next block of code we retreive all persons from the repository :

ShouldDispose(personRepository.GetAllClients()          
                              .ObserveOn(schedulers.Dispatcher)  
                              .Subscribe(c =>  
                                {  
                                    foreach(var item in  c.Select(personViewModelFactory.Create))  
                                        Clients.Add(item);  
                                        
                                    IsBusy = false;  
                                }));  


Then, on the next block we subscribe to DeleteMessage on message bus, and whenever delete message is published on the message bus, we react to it by deleting the selected person from the list:

ShouldDispose(messageBus.Subscribe<DeleteMessage<PersonViewModel>>(m =>  
    {  
        var msg = string.Format("Are you sure you want to delete {0} {1}?", m.ViewModel.Name, m.ViewModel.Surname);  
   
        if (dialogService.ShowWarning("Delete", msg) == MessageBoxResult.OK)  
        {  
            IsBusy = true;  
            ShouldDispose(personRepository.Delete(m.ViewModel.ToModel())  
                                          .ObserveOn(schedulers.Dispatcher)  
                                          .Subscribe(_ =>  
                                            {  
                                                Clients.Remove(m.ViewModel);  
                                                IsBusy = false;  
                                            }));  
        }                       
    }));  

And lastly, we subscribe to _addNewClient command, so whenever the user clicks on the button, we display the dialog for the user to enter the details, and once the details have been entered and the user clicks on Save button, then the new person is stored in the repository:

ShouldDispose(_addNewClientCommand.OnExecute.Subscribe(p =>  
    {  
        var newClient = personViewModelFactory.Create(new Person());  
        if (dialogService.ShowViewModel("New person", newClient as ViewModelBase))  
        {  
            IsBusy = true;  
            ShouldDispose(personRepository.Save(newClient.ToModel())  
                                          .ObserveOn(schedulers.Dispatcher)  
                                          .Subscribe(_ =>  
                                            {  
                                                IsBusy = false;  
                                                Clients.Add(newClient);  
                                            }));  
        }  
    }));  

The last thing left to do in our app is to register the types with the IoC container, and set the DataContext of the main window. We do this in code-behind in App.xaml. Our code-behind code looks like:

public partial class App : Application  
{  
    private IContainer _container;  
    private readonly ContainerBuilder _containerBuilder;  
    public App()  
    {  
        _containerBuilder = new ContainerBuilder();  
        // Register MVVM dependencies  
        containerBuilder.RegisterType<PropertyProviderFactory>().As<IPropertyProviderFactory>().SingleInstance();  
        containerBuilder.RegisterType<Schedulers>().As<ISchedulers>().SingleInstance();  
        containerBuilder.RegisterType<MessageBus>().As<IMessageBus>().SingleInstance();  
        //Register ViewModels  
        _containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().SingleInstance();  
        _containerBuilder.RegisterType<PersonViewModelFactory>().As<IPersonViewModelFactory>().SingleInstance();  
        _containerBuilder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();  
        _containerBuilder.RegisterType<PersonRepository>().As<IPersonRepository>().SingleInstance();  
    }  
    protected override void OnStartup(StartupEventArgs e)  
    {  
        try  
        {  
            ShutdownMode = ShutdownMode.OnMainWindowClose;  
            _container = _containerBuilder.Build();  
            var mainWindow = new MainWindow { DataContext = _container.Resolve<IMainViewModel>() };  
            MainWindow = mainWindow;  
            mainWindow.Show();  
            base.OnStartup(e);  
        }  
        catch (Exception ex)  
        {  
            MessageBox.Show(ex.Message);  
        }  
    }  
}  

The application that was build as an example is very simple but it give you a glimpse of the features of RX. RX is a very powerful framework which can simplify a lot of things in the programming world especially asynchronous programming.

 

History

N/A

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Fitim Skenderi
Software Developer (Senior) KOS-LIGHTS.COM
Sweden Sweden
No Biography provided
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionRx XAML Pin
benmiao14-Jan-15 5:44
memberbenmiao14-Jan-15 5:44 
AnswerRe: Rx XAML Pin
Fitim Skenderi14-Jan-15 23:47
memberFitim Skenderi14-Jan-15 23:47 
QuestionParameter expression in CreateCommand Pin
valeoBG21-Oct-14 4:28
membervaleoBG21-Oct-14 4:28 
AnswerRe: Parameter expression in CreateCommand Pin
Fitim Skenderi21-Oct-14 6:44
memberFitim Skenderi21-Oct-14 6:44 
QuestionReactiveUI Pin
Sam Gerené3-Jun-14 3:02
memberSam Gerené3-Jun-14 3:02 
AnswerRe: ReactiveUI Pin
Fitim Skenderi6-Jun-14 5:25
memberFitim Skenderi6-Jun-14 5:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150427.4 | Last Updated 2 Jun 2014
Article Copyright 2014 by Fitim Skenderi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid