Tuesday, October 17, 2006

Enter Supervising Presenter, exit MVP

This june I wrote about how we used model reference data and the Model-View-Presenter pattern in a WinForms container control with pluggable views. What I did not say was that we did not implement pure MVP, but rather a modified version that fitted better with WinForms data binding and object data sources.

Just about the same time in june, Martin Fowler retired the MVP pattern for just about the same reasons, and the new data binding friendly pattern is the Supervising Controller. The MVP pattern has actually been split in two, the other pattern being the Passive View.

So if you're still using MVP, be sure to catch up on the new patterns.

Wednesday, October 04, 2006

.NET deep clone - IsClone<T> using CloneFormatter

In some previous posts, I have written about how to implement an entity row state mechanism using .NET deep clone and an IsDirty method based on comparing two objects to see if they are clones of eachother. The IsClone<T> method was based on comparing the byte stream created by the BinaryFormatter, just as the Clone<T> method uses the BinaryFormatter to do deep cloning.

[UPDATE] A simple way to implement IsDirty when using IProperyNotificationChanged in data-binding enabled entity objects.

As I wrote in my last post, using the BinaryFormatter and the [NonSerialized] attribute in the IsClone<T> method is OK as long as you do not interfere with other stuff that use the BinaryFormatter, such as .NET remoting. The same applies to using the XmlFormatter, which will affect e.g. web-services. Thus, a separate formatter was needed to support the 'is clone' logic and fix the null/nothing string and the decimal problems. Enter the CloneFormatter based on the code in this article about Serialization Formatters.

I have made these changes to create the CloneFormatter:

private void WriteSerializableMembers(object obj, long objId)
{
System.Reflection.MemberInfo[] mi = FormatterServices.GetSerializableMembers(obj.GetType());
if (mi.Length > 0)
{
object[] od = FormatterServices.GetObjectData(obj, mi);
for (int i = 0; i < mi.Length; ++i)
{
System.Reflection.MemberInfo member = mi[i];
object data = od[i];
//KJELLSJ: do not serialize when marked with "CloneNonSerializedAttribute"
if (IsMarkedCloneNonSerialized(member) == true) continue;


if (member.MemberType == MemberTypes.Field)
{
FieldInfo fi = (FieldInfo)member;
if (data == null)
{
//KJELLSJ: ensure that null/nothing string gets "normalized"
if (fi.FieldType == typeof(string)) data = String.Empty;
}
else
{
//KJELLSJ: ensure that decimal gets "normalized"
if (fi.FieldType == typeof(decimal)) data = Convert.ToDecimal(Convert.ToDouble(data));
}
}
WriteMember(member.Name, data);
}
}
}



// Is the type attributed with [CloneNonSerialized]?
private bool IsMarkedCloneNonSerialized(MemberInfo mi)
{
object[] attributes = mi.GetCustomAttributes(typeof(CloneNonSerializedAttribute), false);
return attributes.Length > 0;
}


void ReadMember(long oid, MemberInfo mi, object o, SerializationInfo info)
{
//KJELLSJ: do not deserialize when marked with "CloneNonSerializedAttribute"
if (IsMarkedCloneNonSerialized(mi) == true) return;

// Read member name.
. . .
}

In addition, a new attribute was needed to be able to exclude class members from the 'is clone' logic:

[AttributeUsage(AttributeTargets.Field, AllowMultiple=false)]
public class CloneNonSerializedAttribute : System.Attribute
{
public CloneNonSerializedAttribute()
{
//only purpose is for marking fields as not-serialized by the CloneFormatter
}
}


Apply the [CloneNonSerialized] attribute to any class member that should not be part of the 'is clone' comparison.

The new IsClone<T> looks like this:

public static bool IsClone<T>(T sourceA, T sourceB)
{
if (sourceA == null sourceB == null)
return false;

MemoryStream streamA = new MemoryStream();
MemoryStream streamB = new MemoryStream();

IFormatter formatter = new CloneFormatter(); formatter.Serialize(streamA, sourceA);
//must create new formatter to reset the ObjectIDGenerator counter
formatter = new CloneFormatter();
formatter.Serialize(streamB, sourceB);

if (streamA.Length != streamB.Length) return false;

byte[] hashA = streamA.GetBuffer();
byte[] hashB = streamB.GetBuffer();

if (hashA.Length != hashB.Length) return false;

for (int i = 0; i < hashA.Length; i++)
{
if (hashA[i] != hashB[i]) return false;
}

//if here, objects are equal
return true;
}


Note that I have dropped the MD5 hashing in favor of plain byte-by-byte comparison as suggested by Nuri in a comment to the previous version. This should perform better, even if the for loop now gets longer that max 16 bytes.

Note also that the Clone<T> method still uses the BinaryFormatter to do the cloning. Use the CloneFormatter only for the IsClone<T> logic.

Note: serializing is not the most performant way to do cloning, read more at Anders Norås' blog.

Thursday, September 28, 2006

.NET Serialization: Custom Formatter, IFormatter

This spring I wrote about how to use cloning and serialization to implement an entity row-state mechanism. We used the BinaryFormatter in the 'clone' and 'is dirty' methods as web-services was the protocol between our tiers (XML serialization), and .NET remoting was not to be used (binary serialization).

Of course, this "absolute requirement" has now changed and using the BinaryFormatter and the NonSerialized attribute is no longer an option as all properties/fields must now be available through remoting. It was time for a custom formatter as the BinaryFormatter class is sealed/NotInheritable.

As the help topics on IFormatter contains only API level details, I though I should share this excellent documentation and working code with you: Serialization Formatters (published by Universität Karlsruhe).

The article also explains the little known ISerializationSurrogate mechanism; this allows you to provide your own serialization mechanism for specific classes/object types/value types, even when using one of the standard formatters. This is typically used to serialize classes that are not marked as [Serializable]. It can also be used to provide specific handling of null/nothing strings and for normalizing the serialization of decimal values. The former is a problem for the .IsDirty mechanism because a null/nothing string is different from a ""/String.Empty string, while the latter is a problem because the decimal value 1 might be different from the value 1.0 as the binary representation might change.

You might also find this Advanced Serialization MSDN article useful.

[UPDATE] Serializing object graphs that comprises inheritance requires some extra work. If you need to serialize inherited composite objects, the XmlInlcude(typeof(DerivedClass)) class attribute is the best solution when derived classes are know at compile-time. If you need to serialize "unkown" derived objects, the CodeProject XmlSerializer and 'not expected' Inherited Types article provides a really simple solution (the comments section includes a Generics NodeSerializer<T>).

Some of the MSDN help topics lacks working, real-life examples of how to implement an interface or abstract class; which is too bad and makes developers waste a lot of time in "the desert of desperation". I had the same problem last year looking for a working example of how to implement a SoapExtension. I wish Microsoft could do a litte more to help developers fall into "the pit of success".

Monday, September 25, 2006

MSCRM 3: Move contact, retain history

My Objectware colleage Bjarne Gram has published an article that shows how to implement a pure client-side solution to move a contact to a new company while retaining all historical info by linking back to the former employer and a deactivated version of the contact.

Read the article over at Barney's blog.