Click here to Skip to main content
15,880,543 members
Articles / Desktop Programming / WPF

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

Rate me:
Please Sign up or sign in to vote.
3.31/5 (13 votes)
27 Dec 2009CPOL7 min read 40.1K   544   13   9
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.

Introduction

In today's world of WPF and WCF client-server applications, MVVM is 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

(Note: if you are familiar with the MVVM pattern already, skip to the Multiuse-Model View section).

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, read about the Model View ViewModel Design Pattern for WPF.

MVVM consists, basically, of 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:

Image 1

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

Image 2

In a typical WPF/WCF application, you would likely have a project that contains your Data Object Model, another project that contains 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:

Image 3

Notice that we have three 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 three 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), and they are not swappable in different contexts (Polymorphism). So, what's the big deal, you might ask. If you have 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 a column to your 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 one class to worry about. Not to mention 5 times the amount of time it'll take to make the change (not counting the time spent debugging the errors caused by what you forgot to change).

To summarize, here is a list of the 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:

Image 4

Whoa, what happened to all the projects? Now, we have 6 projects instead of 7, but only one 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 one 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:

C#
[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 four 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 emails 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 are 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 voila, 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:

C#
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, like:

C#
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:

C#
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:

C#
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 IMultiuseObjectServiceContract accordingly:

C#
[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).

Check out my blog http://andresusandi.blogspot.com. In my next articles, I'll demonstrate how to create an extensive MultiuseObject core Library.

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralInteresting approach Pin
Josh Smith26-Apr-10 13:21
Josh Smith26-Apr-10 13:21 
GeneralMy vote of 1 Pin
jmix9031-Mar-10 10:01
jmix9031-Mar-10 10:01 
QuestionWhat about.... Pin
basilard9911-Jan-10 10:25
basilard9911-Jan-10 10:25 
GeneralMy vote of 1 Pin
William E. Kempf11-Jan-10 9:16
William E. Kempf11-Jan-10 9:16 
GeneralI think the MVVM pattern is first and foremost about UI only Pin
Michael Epner29-Dec-09 6:15
Michael Epner29-Dec-09 6:15 
GeneralI have to think about it Pin
Yves28-Dec-09 15:02
Yves28-Dec-09 15:02 
QuestionEasy to unit test?? Pin
Kjetil Klaussen28-Dec-09 10:30
Kjetil Klaussen28-Dec-09 10:30 
GeneralA serious flaw Pin
aSarafian27-Dec-09 22:11
aSarafian27-Dec-09 22:11 
GeneralMy vote of 1 Pin
pikul27-Dec-09 14:15
pikul27-Dec-09 14:15 

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

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