Thursday, October 8, 2009

MultiuseModel-View (MMV) Object modeling pattern with WPF and WCF: Is MVVM the antichrist?


Download sample MVVM project
Download sample MMV project


In today's world of WPF and WCF client-server applications, MVVM has been growing in popularity among multi-tier app developers. In this series of articles, I will discuss why I think MVVM is an abomination to object oriented programming and I will demonstrate a different way in which developers can write applications.

Model View ViewModel

To understand why MVVM is not the best way to go, we must first understand what MVVM is and how it works. For a more detailed description about MVVM see the Model View ViewModel Design Pattern for WPF.

MVVM consists basically of having a set of classes that define your data, a set of classes that define your behavior and a set of classes that define your looks and feel.

Let's take a simple address book application as an example:


Let's assume the application will use the following database table as the persistence storage:


In a typical WPF/WCF application you would likely have a project that contains your Data Object Model, another project that contain your Data Transport Object Model and converters between the data objects and the DTO objects, a project that contains your View Model and converters between the DTO objects and the VM objects and another project that contains your Views. Implementations might vary slightly across projects and developers, but for the most part a typical address book application might look like this:


Notice that we have 3 classes that are a representation of the data stored in the contact table (Contact, ContactDTO, and ContactViewModel). All these classes expose more or less the same number of properties, with the difference that the ContactDO class has logic pertaining to the database (most likely a map if you use NHibernate) ContactDTO is a (most likely SOAP) serializable object, and ContactVM contains ICommands and other view-related properties.

When I see this, what immediately pops to mind is my programming 201 class back in college: fundamentals of programming, when we talked about the four basic principles of object oriented programming (for those of you who don't remember, they are: Encapsulation, Abstraction, Inheritance, and Polymorphism). I don't know about you but I certainly don't see any of these principles being applied in the MVVM pattern. We have 3 classes that do almost the same thing (there goes Encapsulation), they are not tied together whatsoever (tough luck Abstraction), they do not share their properties with each other (oops - too bad Inheritance) they are not swappable in different contexts (Polymorphism). So what's the big deal? you might ask. if you ever worked on an MVVM application I'm sure you've noticed (as I have) that adding a new piece of data (property) or functionality (methods, classes, etc) to an existing object model is a huge pain in the ass, time consuming, cumbersome and most importantly there is a high chance of forgetting something and producing faulty code.Think about it: if you want to add 1 column to our database table you would have to change at least 5 classes (sometimes more depending on the project). The chances of forgetting something or screwing something up are 500% higher than if you only had 1 class to worry about. Not to mention 5 times the amount of time it'll take to make the change (not counting time spending debugging the errors caused by what you forgot to change).

To summarize, here is a list of Pros and Cons for MVVM:

Pros:

  • Hard separation between logic (view model) and display (view).
  • Easy to unit test UI logic.
  • Leverages WPF technologies such as binding and commanding.

Cons:

  • Does not conform to Object Oriented Programming standards.
  • It is too complex for simple UI operations.
  • For large applications it requires a large amount of metadata generation.
  • Requires duplication of code.
  • It is complex to maintain.

The Multiuse-Model View approach

So we know that MVVM is quite the opposite of what we want as object oriented developers, it is hard to maintain, and requires duplication of code. But the question is: what IS a feasible object oriented solution to this problem? the answer is MMV: a pattern that uses Encapsulation, Abstraction, Inheritance, and Polymorphism to transport data all the way from the database to the view.

Let's take our Address Book example one more time. Given the same contact table, let's draw a new solution:


Woha, what happened to all the projects? now we have 6 projects instead of 7 but only 1 class that represents the contact table. We still have a public and a private web server, we have the very same shell application and the very same view as in the MVVM pattern, but we now have 1 project for the entire data model (plus one “core” project that we will get to later). So let's take a closer look at the Contact class:

[Table(Name="Contact_tbl")]
public class Contact : MultiuseObject<Contact>
{
 public virtual int ContactId { get; set; }
 public virtual string FirstName { get; set; }
 public virtual string LastName { get; set; }
 public virtual string Email { get; set; }

 public void SendEMail()
 {
  Process.Start(string.Format("mailto:{0}", Email));
 }
}

This is a simple class with 4 properties and a method to perform some action particular to a contact (in this case, from an object model point of view, we want to be able to send emais to the contacts in our database). A couple of things pop right out by simply glancing at this:

  • First of all we see the TableAttribute for the class. For the time being ignore this since it is specific to retrieving data from the database. This might not be necessary depending on the flavor of persistence library that you like the most (for example, if you use NHibernate you would have either a mapping class or an XML file).
  • Second, we notice that all of our relevant properties as declared as virtual. This ties to our third point.
  • Third, this class inherits from a generic MultiuseObject class (where all the magic happens).

Notice how much cleaner and simpler to read this one class (that encapsulates everything you need) is. If you wanted to add or remove a column from the database, all you would have to do is to add a corresponding property to this class and viola! You’re done; the MultiuseObject class takes care of getting and saving the object from the database, it takes care of sending objects and collections of objects from the private server to the public server and from the public server to the client, and it even implements INotifyPropertyChanged and creates ICommands for you. How does it do all of this? Well, let’s take a look at our sample MultiuseObject<t> class:

public abstract class MultiuseObject<T> : INotifyPropertyChanged
{
    private const string cExtendedTypesAssemblyName = "MMV.ExtendedTypes";
    private const string cExtendedTypesModuleName = "MMV.ExtendedTypes";
    private const string cExtendedTypesNamePostfix= "<>Extended";
    private const string cExtendedTypesCommandPostfix = "Command";

    // every time the CLR loads a type derived from MultiuseObject, we'll create a new type that adds needed features for WPF (such as NotifyPropertyChanged and Commands)
    static MultiuseObject()
    {
        CreateOverridenType(typeof(T));
        // this is to ensure that the newly created assembly gets properly loaded by WCF's DataContractSerializer on deserialization.
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { return(GetExtendedTypesAssembly()); };
    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChange(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion


    /// <summary>
    /// provides functionality to derived objects to get all data related to the type from the database.
    /// </summary>
    public static List<T> GetAllFromDB()
    {
        List<T> returning = new List<T>();

        // note: here you can use any persistance library to dynamically populate a list of all objects
        using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["MMVSample"].ConnectionString))
        {
            connection.Open();
            using (SqlCommand command = new SqlCommand(string.Format("select * from {0}", ((TableAttribute)typeof(T).GetCustomAttributes(typeof(TableAttribute), true)[0]).Name), connection))
            {
                SqlDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    // the trick here is to return an instance of the dynamically derived type.
                    T c = (T)Activator.CreateInstance(CreateOverridenType(typeof(T))); 
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        object value = reader.GetValue(i);
                        if (!(value is DBNull))
                            typeof(T).GetProperty(reader.GetName(i)).SetValue(c, value, null);
                    }
                    returning.Add(c);
                }
            }
        }
        return (returning);
    }

    /// <summary>
    /// provides functionality to derived objects to call GetAllObjectsFromDB from a client app with 
    /// no access to the database. (for example, the public server)
    /// </summary>
    public static List<T> GetAllFromPrivateServer()
    {
        ServiceClient<IMultiuseObjectPrivateServiceContract> client = new ServiceClient<IMultiuseObjectPrivateServiceContract>();
        return (client.ContractChannel.GetAll(typeof(T)).Cast<T>().ToList());
    }

    /// <summary>
    /// provides functionality to derived objects to call GetAllObjectsFromDB from a client app with 
    /// no access to the database and no access to the private server. (for example, the WPF client app)
    /// </summary>
    public static List<T> GetAllFromPublicServer()
    {
        ServiceClient<IMultiuseObjectPublicServiceContract> client = new ServiceClient<IMultiuseObjectPublicServiceContract>();
        return (client.ContractChannel.GetAll(typeof(T)).Cast<T>().ToList());
    }

    /// <summary>
    /// Looks for or creates a new AssemblyBuilder and a corresponding module to store our dynamically derived types.
    /// </summary>
    private static AssemblyBuilder GetExtendedTypesAssembly()
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.Contains(cExtendedTypesAssemblyName));
        if (assemblies.Count() > 0)
            return ((AssemblyBuilder)assemblies.First());
        else
        {
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName() { Name = cExtendedTypesAssemblyName }, AssemblyBuilderAccess.RunAndSave);
            assemblyBuilder.DefineDynamicModule(cExtendedTypesModuleName, true);
            return (assemblyBuilder);
        }
    }

    /// <summary>
    /// This is the key method for encapsulating WPF functionality without redundancy. This method overrides virtual properties to call
    /// NotifyPropertyChanged, it creates ICommands for public methods, and it shadows the GetType method for serialization compatibility.
    /// </summary>
    private static Type CreateOverridenType(Type parentType)
    {
        string childTypeName = parentType.Namespace + "." + parentType.Name + cExtendedTypesNamePostfix;

        AssemblyBuilder assemblyBuilder = GetExtendedTypesAssembly();
        ModuleBuilder moduleBuilder = assemblyBuilder.GetDynamicModule(cExtendedTypesModuleName);

        Type childType = moduleBuilder.GetType(childTypeName);
        if (childType == null)
        {
            TypeBuilder typeBuilder = moduleBuilder.DefineType(childTypeName, parentType.Attributes, parentType);

            // shadow GetType
            // some serializers (DataContractSerializer for example) use
            // GetType to validate if the deserialized type is the same as
            // the alleged return type. If they are not equal they thrown an exception.
            // to bypass this we override GetType to return the value of the base type.
            MethodInfo method = typeof(object).GetMethod("GetType", BindingFlags.Public | BindingFlags.Instance , null, new Type[] { }, null);
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name, method.Attributes, typeof(Type), method.GetParameters().Select(pi => pi.ParameterType).ToArray());
            ILGenerator il = methodBuilder.GetILGenerator();
            LocalBuilder locAi = il.DeclareLocal(typeof(ArgIterator));

            il.Emit(OpCodes.Ldtoken, parentType);
            il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static), null);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            // we create an ICommand property and a backing DelegateCommand field for each public method (we exclude backing methods for properties and events).
            foreach (MethodInfo mi in parentType.GetMethods(BindingFlags.Instance | BindingFlags.Public).Where(methodInfo => !methodInfo.Name.StartsWith("get_") && !methodInfo.Name.StartsWith("set_") && !methodInfo.Name.StartsWith("add_") && !methodInfo.Name.StartsWith("remove_")))
            {
                FieldBuilder commandField = typeBuilder.DefineField("_" + mi.Name + cExtendedTypesCommandPostfix, typeof(DelegateCommand), FieldAttributes.Private);

                MethodBuilder commandGetMethod = typeBuilder.DefineMethod("get_" + mi.Name + cExtendedTypesCommandPostfix, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(ICommand), Type.EmptyTypes);
                ILGenerator commandGetMethodIL = commandGetMethod.GetILGenerator();

                var commandNullLabel = commandGetMethodIL.DefineLabel();
                var defaultLabel = commandGetMethodIL.DefineLabel();

                commandGetMethodIL.Emit(OpCodes.Nop);
                commandGetMethodIL.Emit(OpCodes.Ldarg_0);
                commandGetMethodIL.Emit(OpCodes.Ldfld, commandField);
                commandGetMethodIL.Emit(OpCodes.Ldnull);
                commandGetMethodIL.Emit(OpCodes.Ceq);
                commandGetMethodIL.Emit(OpCodes.Brfalse, commandNullLabel);
                commandGetMethodIL.Emit(OpCodes.Ldarg_0);
                commandGetMethodIL.Emit(OpCodes.Ldarg_0);
                commandGetMethodIL.Emit(OpCodes.Ldftn, mi);
                commandGetMethodIL.Emit(OpCodes.Newobj, typeof(Action).GetConstructor(new Type[] { typeof(object), typeof(IntPtr) }));
                commandGetMethodIL.Emit(OpCodes.Newobj, typeof(DelegateCommand).GetConstructor(new Type[] { typeof(Action) }));
                commandGetMethodIL.Emit(OpCodes.Stfld, commandField);
                commandGetMethodIL.MarkLabel(commandNullLabel);
                commandGetMethodIL.Emit(OpCodes.Ldarg_0);
                commandGetMethodIL.Emit(OpCodes.Ldfld, commandField);
                commandGetMethodIL.Emit(OpCodes.Ret);

                PropertyBuilder commandProperty = typeBuilder.DefineProperty(mi.Name + cExtendedTypesCommandPostfix, PropertyAttributes.HasDefault, typeof(ICommand), null);
                commandProperty.SetGetMethod(commandGetMethod);
            }

            // we override the virtual properties to call OnPropertyChanged after calling the base class implementation.
            foreach (PropertyInfo property in parentType.GetProperties())
            {
                method = parentType.GetMethod("set_" + property.Name);
                methodBuilder = typeBuilder.DefineMethod(method.Name, method.Attributes, method.ReturnType, method.GetParameters().Select(pi => pi.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();
                locAi = il.DeclareLocal(typeof(ArgIterator));

                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.EmitCall(OpCodes.Call, method, null);
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldstr, property.Name);
                il.EmitCall(OpCodes.Call, parentType.GetMethod("OnPropertyChange", BindingFlags.NonPublic | BindingFlags.Instance), null);
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ret);
                typeBuilder.DefineMethodOverride(methodBuilder, method);
            }
            childType = typeBuilder.CreateType();
        }
        return (childType);
    }
}

To use a multiuse object, we can simply call one of its methods from the appropriate application level. For example, to bind the main view of our contact application we call the Contact class as such:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
  
        MainView mv = new MainView();
        mv.DataContext = Contact.GetAllFromPublicServer();
        mv.Show();
    }
}

This is what the public server will do in turn:

public class AddressBookDataService : IMultiuseObjectPublicServiceContract
{
    public List<object> GetAll(Type returnObjectType)
    {
        return ((IEnumerable)returnObjectType.GetMethod("GetAllFromPrivateServer", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy).Invoke(null, null)).Cast<object>().ToList();
    }
}

And the private server will call the GetAllFromDB method like this:

public class AddressBookDataService : IMultiuseObjectPrivateServiceContract
{
    public List<object> GetAll(Type returnObjectType)
    {
        return ((IEnumerable)returnObjectType.GetMethod("GetAllFromDB", BindingFlags.Static  BindingFlags.Public  BindingFlags.FlattenHierarchy).Invoke(null, null)).Cast<object>().ToList();
    }
}

If we want to add more functionality to our multiuse objects, all we have to do is implement the desired behavior in the MultiuseObject class and modify the IMultiuseObjectServiceContract accordingly:

[ServiceContract]
public interface IMultiuseObjectServiceContract
{
    [OperationContract]
    List<object> GetAll(Type returnObjectType);
}

As you can see, we can use the same WPF View that you would normally use on an MVVM app but with a cleaner and nicer object model behind it.

To recap, the Pros and Cons of MMV are as follows:

Pros:

  • Hard separation between logic (view model) and display (view).
  • Easy to unit test UI logic.- Leverages WPF technologies such as binding and commanding.
  • Conforms to Object Oriented Programming standards.
  • Requires minimum amount of code to extend.
  • It is easy to maintain.

Cons:

  • Requires a complex core library.
  • Requires the application stack to be all Microsoft-based products. (it is obviously not compatible with Java or other server-side technologies)

In my next article I'll demonstrate how to create an extensive MultiuseObject core Library.




No comments:

Post a Comment