Click here to Skip to main content
Click here to Skip to main content
Go to top

MVVM using POCOs with .NET 4.0 and the DynamicViewModel Class

, 27 Dec 2010
Rate this:
Please Sign up or sign in to vote.
This post aims to provide a way to implement the Model View ViewModel (MVVM) architectural pattern using Plain Old CLR Objects (POCOs) while taking full advantage of .NET 4.0 DynamicObject Class. In order to apply the Model View ViewModel (MVVM) architectural pattern we need:An instance of the View

This post aims to provide a way to implement the Model View ViewModel (MVVM) architectural pattern using Plain Old CLR Objects (POCOs) while taking full advantage of .NET 4.0 DynamicObject Class. 

Mvvm-sl-expando

In order to apply the Model View ViewModel (MVVM) architectural pattern we need:

  1. An instance of the View, (ex.: a UserControl type).
  2. An instance of the ViewModel, which in most scenarios is a class implementing the INotifyPropertyChanged interface (or inherits from a base class getting the implementation for free).
  3. An instance of the Model inside the ViewModel class, for getting the properties to display (and format them if necessary) and also for invoking commands on the model.

    While we can not avoid step 1 (we need to have something to display to the user) and step 3 (we need to have something the user can read/edit), for basic scenarios we can try to avoid step 2. 

    Taking advantage of the .NET 4.0 and the DynamicObject Class, we can create a type deriving from the DynamicObject Class and specify dynamic behavior at run time. Furthermore, we can implement the INotifyPropertyChanged Interface on the derived type making it a good candidate for Data Binding.

Let's name our class, DynamicViewModel(Of TModel) Class. It must be able to:

  1. Accept references types (any class - a model is usually a class).
  2. Invoke public instance methods.
  3. Invoke public instance methods with arguments passed as CommandParameters.
  4. Get public instance properties.
  5. Set public instance properties.
  6. Notify callers when property change by raising the PropertyChanged event.
  7. If a property change results in chaning other properties, the caller must receive the notification for the other property changes too.

The DynamicViewModel(Of TModel) Class: 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace DynamicViewModel
{
    public sealed class DynamicViewModel<TModel>
        : DynamicObject, INotifyPropertyChanged where TModel : class
    {
        /// <span class="code-SummaryComment"><summary>
</span>        /// Dictionary that holds information about the TModel public
        /// instance methods.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><remarks>
</span>        /// CA1810: Initialize reference type static fields inline.
        /// http://msdn.microsoft.com/en-us/library/ms182275(v=VS.100).aspx
        /// <span class="code-SummaryComment"></remarks>
</span>        private static readonly IDictionary<String, MethodInfo> s_methodInfos
            = GetPublicInstanceMethods();

        /// <span class="code-SummaryComment"><summary>
</span>        /// Dictionary that holds information about the TModel public
        /// instance properties.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><remarks>
</span>        /// CA1810: Initialize reference type static fields inline.
        /// http://msdn.microsoft.com/en-us/library/ms182275(v=VS.100).aspx
        /// <span class="code-SummaryComment"></remarks>
</span>        private static readonly IDictionary<String, PropertyInfo> s_propInfos
            = GetPublicInstanceProperties();

        private readonly TModel m_model;

        /// <span class="code-SummaryComment"><summary>
</span>        /// Dictionary that holds information about the current 
        /// values of the TModel public instance properties.
        /// <span class="code-SummaryComment"></summary>
</span>        private IDictionary<String, Object> m_propertyValues;

        /// <span class="code-SummaryComment"><summary>
</span>        /// Initializes a new instance of the 
        /// <span class="code-SummaryComment"><see cref="DynamicViewModel&lt;TModel&gt;"/> class.
</span>        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="model">The model.</param>
</span>        public DynamicViewModel(TModel model)
        {
            m_model = model;
            NotifyChangedProperties();
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Initializes a new instance of the 
        /// <span class="code-SummaryComment"><see cref="DynamicViewModel&lt;TModel&gt;"/> class.
</span>        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="delegate">The @delegate.</param>
</span>        public DynamicViewModel(Func<TModel> @delegate)
            : this(@delegate.Invoke()) { }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Provides the implementation for operations that invoke a member.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="binder">Provides information about the dynamicoperation.
</span>        /// <span class="code-SummaryComment"></param>
</span>        /// <span class="code-SummaryComment"><param name="args">The arguments that are passed to the 
</span>        /// object member during the invoke operation.<span class="code-SummaryComment"></param>
</span>        /// <span class="code-SummaryComment"><param name="result">The result of the member invocation.</param>
</span>        /// <span class="code-SummaryComment"><returns>
</span>        /// true if the operation is successful; otherwise, false.
        /// <span class="code-SummaryComment"></returns>
</span>        public override Boolean TryInvokeMember(InvokeMemberBinder binder,
            Object[] args, out Object result)
        {
            result = null;

            MethodInfo methodInfo;
            if (!s_methodInfos.TryGetValue(binder.Name,
                out methodInfo)) { return false; }

            methodInfo.Invoke(m_model, args);
            NotifyChangedProperties();
            return true;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets the property value of the member.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="binder">The binder.</param>
</span>        /// <span class="code-SummaryComment"><param name="result">The result of the get operation. 
</span>        /// For example, if the method is called for a property,
        /// you can assign the property value to 
        /// <span class="code-SummaryComment"><paramref name="result"/>.</param>
</span>        /// <span class="code-SummaryComment"><returns>True with the result is set.</returns>
</span>        public override Boolean TryGetMember(GetMemberBinder binder, out Object result)
        {
            var propertyValues = Interlocked.CompareExchange(
                ref m_propertyValues, GetPropertyValues(), null);

            if (!propertyValues.TryGetValue(binder.Name,
                out result)) { return false; }

            return true;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Sets the property value of the member.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="binder">The binder.</param>
</span>        /// <span class="code-SummaryComment"><param name="value">The value to set to the member. For example, 
</span>        /// for sampleObject.SampleProperty = "Test", where sampleObject is 
        /// an instance of the class derived from the 
        /// <span class="code-SummaryComment"><see cref="T:System.Dynamic.DynamicObject"/> class, 
</span>        /// the <span class="code-SummaryComment"><paramref name="value"/> is "Test".</param>
</span>        /// <span class="code-SummaryComment"><returns>True with the result is set.</returns>
</span>        public override Boolean TrySetMember(SetMemberBinder binder, Object value)
        {
            PropertyInfo propInfo = s_propInfos[binder.Name];
            propInfo.SetValue(m_model, value, null);

            NotifyChangedProperties();
            return true;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Setting a property sometimes results in multiple properties
        /// with changed values too. For ex.: By changing the FirstName
        /// and the LastName the FullName will get updated. This method
        /// compares the m_propertyValues dictionary with the one that
        /// is obtained inside this method body. For each changed prop
        /// the PropertyChanged event is raised, notifying the callers.
        /// <span class="code-SummaryComment"></summary>
</span>        public void NotifyChangedProperties()
        {
            Interlocked.CompareExchange(
                ref m_propertyValues, GetPropertyValues(), null);

            // Store the previous values in a field.
            IDictionary<String, Object> previousPropValues
                = m_propertyValues;

            // Store the  current values in a field.
            IDictionary<String, Object> currentPropValues
                = GetPropertyValues();

            // Since we will be raising the PropertyChanged event
            // we want the caller to bind in the current values
            // and not the previous.
            m_propertyValues
                = currentPropValues;

            foreach (KeyValuePair<String, Object> propValue
                in currentPropValues.Except(previousPropValues))
            {
                RaisePropertyChanged(propValue.Key);
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets the public instance methods of the TModel type.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><returns>
</span>        /// A dictionary that holds information about TModel public
        /// instance properties.
        /// <span class="code-SummaryComment"></returns>
</span>        private static IDictionary<String, MethodInfo> GetPublicInstanceMethods()
        {
            var methodInfoDictionary = new Dictionary<String, MethodInfo>();
            MethodInfo[] methodInfos = typeof(TModel).GetMethods(
                BindingFlags.Public | BindingFlags.Instance);
            foreach (MethodInfo methodInfo in methodInfos)
            {
                if (methodInfo.Name.StartsWith("get_") ||
                    methodInfo.Name.StartsWith("set_")) { continue; }
                methodInfoDictionary.Add(methodInfo.Name, methodInfo);
            }

            return methodInfoDictionary;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets the public instance properties of the TModel type.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><returns>
</span>        /// A dictionary that holds information about TModel public
        /// instance properties.
        /// <span class="code-SummaryComment"></returns>
</span>        private static IDictionary<String, PropertyInfo> GetPublicInstanceProperties()
        {
            var propInfoDictionary = new Dictionary<String, PropertyInfo>();
            PropertyInfo[] propInfos = typeof(TModel).GetProperties(
                BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo propInfo in propInfos)
            {
                propInfoDictionary.Add(propInfo.Name, propInfo);
            }

            return propInfoDictionary;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets the property values about the TModel public instance properties.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><returns>A dictionary that holds information about the current 
</span>        /// values of the TModel public instance properties.<span class="code-SummaryComment"></returns>
</span>        private IDictionary<String, Object> GetPropertyValues()
        {
            var bindingPaths = new Dictionary<String, Object>();
            PropertyInfo[] propInfos = typeof(TModel).GetProperties(
                BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo propInfo in propInfos)
            {
                bindingPaths.Add(
                    propInfo.Name,
                    propInfo.GetValue(m_model, null));
            }

            return bindingPaths;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Raises the property changed event.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="propertyName">Name of the property.</param>
</span>        private void RaisePropertyChanged(String propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Raises the <span class="code-SummaryComment"><see cref="E:PropertyChanged"/> event.
</span>        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> 
</span>        /// instance containing the event data.<span class="code-SummaryComment"></param>
</span>        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler temp =
                Interlocked.CompareExchange(ref PropertyChanged, null, null);

            if (temp != null)
            {
                temp(this, e);
            }
        }

        #region INotifyPropertyChanged Members

        /// <span class="code-SummaryComment"><summary>
</span>        /// Occurs when a property value changes.
        /// <span class="code-SummaryComment"></summary>
</span>        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
} 

The sample application for this post comes with a simple ContactView which has no specific viewModel but instead uses the DynamicViewModel(Of TModel) class.

Here is how the sample application looks: 

DynamicMvvmApp

The DynamicViewModel(Of TModel) Class is able to update the View which binds to an instance of this class. 

Here is what the sample application does: 

  1. Changing the First Name will result in changing the Full Name and the Reversed Full Name.
  2. The same rules apply when chaning the Last Name. 
  3. The hyper-link is enabled only if the user presses the Clear Names button. 
  4. The Clear Names button is enabled only when the Full Name text is not empty.

Here is the POCO model class that I have used:

using System;

public sealed class ContactDetails
{
    public String FirstName
    {
        get
        {
            return m_firstName;
        }

        set
        {
            m_firstName = value;

            SetFullName();
        }
    }

    public String LastName
    {
        get
        {
            return m_lastName;
        }

        set
        {
            m_lastName = value;

            SetFullName();
        }
    }

    public String FullName
    {
        get;
        set;
    }

    // (Less important members not shown)
} 

As you notice, this class does not implement any interface or base class. In fact, this class can be used successfully in ORM scenarios too (when you need to bind on the same classes that are used in your mappings).

Binding to methods 

 <!--<span class="code-comment"> Adding CommandBindings from XAML: --></span>
 <StackPanel.CommandBindings>
     <CommandBinding
         Command="{x:Static m:ContactView.ClearNamesCommand}" />
     <CommandBinding
         Command="{x:Static m:ContactView.NavigateUriCommand}" />
 </StackPanel.CommandBindings>
 
<!--<span class="code-comment"> Binding a button click to a method with no arguments: --></span>
 <Button
     Content="Clear Names"
     Command="{x:Static m:ContactView.ClearNamesCommand}" />

<!--<span class="code-comment"> Binding a hyperlink to a method passing the an argument via CommandParameter: --></span>
<Hyperlink
    Command="{x:Static m:ContactView.NavigateUriCommand}"
    CommandParameter="http://nikosbaxevanis.com"
    NavigateUri="nikosbaxevanis.com">nikosbaxevanis.com</Hyperlink> 

Binding to properties 

 <TextBox
     Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}"/> 

    Finally, I would like to show how the View's DataContext is initialized properly to accept the DynamicViewModel(Of TModel) Class wrapper around the model class:  

Wiring view commands with methods of the model  

internal partial class ContactView : UserControl
{
    public static readonly RoutedCommand ClearNamesCommand  = new RoutedCommand();
    public static readonly RoutedCommand NavigateUriCommand = new RoutedCommand();
 
    public ContactView()
    {
        InitializeComponent();
 
        // Create a new instance. Once created
        // do not call methods directly on this
        // object. (Use the dynamic viewModel).
        var instance  = new ContactDetails() {
            FirstName = "Nikos",
            LastName  = "Baxevanis"
        };
 
        dynamic viewModel = new DynamicViewModel<ContactDetails>(instance);
 
        // Wire the ClearNamesCommand from the view to the viewModel.
        CommandManager.RegisterClassCommandBinding(typeof(ContactView),
            new CommandBinding(
                ClearNamesCommand,
                (sender, e) => { viewModel.ClearFullName(); },
                (sender, e) => { e.CanExecute = !String.IsNullOrWhiteSpace(viewModel.FullName); }));
 
        // Wire the NavigateUriCommand from the view to the viewModel.
        CommandManager.RegisterClassCommandBinding(typeof(ContactView),
            new CommandBinding(
                NavigateUriCommand,
                (sender, e) => { viewModel.NavigateTo(e.Parameter); },
                (sender, e) => { e.CanExecute = String.IsNullOrWhiteSpace(viewModel.FullName); }));
 
        DataContext = viewModel;
    }
} 

   Notice that wiring between the ICommand Interface and the model class is done outside the dynamic ViewModel wrapper using the CommandManager Class which acts as a mediator between the View and the ViewModel. This give us the flexibility to define static reusable commands or specific commands for each view (as I've done above).

The project is hosted on CodePlex. 

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author


Comments and Discussions

 
GeneralCollections Pinmembermaxabbr2-Feb-11 0:48 
GeneralRe: Collections PinmemberNikos Baxevanis6-Feb-11 8:38 
GeneralLooks like it already was here PinmemberYury Goltsman12-Jan-11 23:53 
GeneralRe: Looks like it already was here PinmemberNikos Baxevanis13-Jan-11 21:41 
GeneralRe: Looks like it already was here PinmemberYury Goltsman14-Jan-11 9:54 
GeneralRe: Looks like it already was here PinmemberNikos Baxevanis15-Jan-11 0:22 
GeneralMy vote of 5 PinmemberLaurenCL4-Jan-11 6:50 
GeneralRe: My vote of 5 PinmemberNikos Baxevanis13-Jan-11 21:58 
GeneralMy vote of 5 Pinmemberrbdavidson4-Jan-11 4:43 
GeneralRe: My vote of 5 [modified] PinmemberNikos Baxevanis13-Jan-11 21:52 
GeneralMy vote of 5 PinmemberCollin Jasnoch4-Jan-11 3:31 
GeneralRe: My vote of 5 PinmemberNikos Baxevanis13-Jan-11 21:53 
GeneralMy vote of 5 PinmemberMartin Lottering27-Dec-10 10:35 
GeneralRe: My vote of 5 PinmemberNikos Baxevanis13-Jan-11 21:53 

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 | Mobile
Web02 | 2.8.140916.1 | Last Updated 28 Dec 2010
Article Copyright 2010 by Nikos Baxevanis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid