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.EmitCall(OpCodes.Call, method, null);
                    il.Emit(OpCodes.Ldstr, property.Name);
                    il.EmitCall(OpCodes.Call, parentType.GetMethod("OnPropertyChange", BindingFlags.NonPublic | BindingFlags.Instance), null);
                    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.Ldfld, commandField);
                    commandGetMethodIL.Emit(OpCodes.Brfalse, commandNullLabel);
                    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.Emit(OpCodes.Ldfld, commandField);

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

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

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