Tuesday, January 5, 2010

MultiuseModel-View (MMV) Object modeling pattern with WPF and WCF: The Foundation



Download sample ObservableObject project



In my previous article I went over how to break up a multiuse object into logical parts. In this article I will talk about an ObservableObject: an object that notifies WPF of changes.



The ObservableObject Class


There are two type of members that you can bind in a WPF app: properties and methods. Requirements for binding properties is fairly straight forward: one must implement INotifyPropertyChanged. To bind a method, on the other hand, we must actually have a property of type ICommand for each method we want to bind to.


Implementing INotifyPropertyChanged simply consists of raising the PropertyChanged event every time a property changes. The problem lies in making a child class of ObservableObject to raise the event without having the developer of the child class call it himself/herself. The best solution for this problem is to use Reflection.Emit to construct a child class of the templated class and override the properties' set method to raise the event after executing the template's set method logic.


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);
                }

With this in mind, adding ICommands for methods becomes just as easy: we can use the same TypeBuilder to add new properties of type ICommand for each public method.



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);
                }

Now for all of this to work we need to make sure two things happen: first the consumer of the template object should actually get an instance of the derived type. And second, since the consumer is actually using a child class of the type he/she declared, we must ensure that reflection and typing compatibility is achieved. To do this, we must first of all provide a means of instantiating a new object (with a New() static method) and we must also override the GetType() of the child type we have created to return an instance of the template's Type class.


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);

public static T New()
        {
            return ((T)Activator.CreateInstance(CreateOverridenType(typeof(T))));
        }

In my next article, I'll demonstrate how to create an enterprise level DatabaseObject and DatabaseCollection classes.




No comments:

Post a Comment