Thursday, March 6, 2008

.NET Journal :: Serialization of DirectoryServices members

I recently had a request from one of my clients to redo some active directory tied classes so that they could be serialized to a distributed caching system. I found nearly zip regarrding this approach in docs and on the web, so I went about creating a test project to see what could be done.

The basic problem is this: each class involved must be marked with the [Serializable] attibute and viola, .NET does the rest. Unfortunately, you can't always do this. For instance, the following (highly simplified) class:

using System;
using System.DirectoryServices;
using
System.Runtime.Serialization;

namespace SerializeTest
{
[Serializable]
public class Account
{
private DirectoryEntry _de;
private string _sDN = null;

public Account(string dn)
{
_sDN = dn;
//simplified - never hard code this stuff :)
_de = new DirectoryEntry(@"LDAP://demo.mycompany.com/" + _sDN);
}

public DirectoryEntry DirectoryEntry { get { return _de; } }
public string DN { get { return _sDN; } }
}
}

will generate runtime errors if you try to serialize it because System.DirectoryServices.DirectoryEntry is not marked as serializable. Well guess what? I can't mark that class, it's not mine. To solve this dilema, the member must be marked as non serializable, using the atttribute [NonSerialized]:

[NonSerialized] private DirectoryEntry _de;

That's well and good, but how do we get it back when we deserialize? To do this, we have to save the state in a standard variable, such as a string, and recreate the object when the class is deserialized.

This can be done by implementing the IDeserializationCallback interface. Implementing an interface looks very much like inheriting a class, so don't let the terminology scare you. We simply need to add to our class definition:

[Serializable]
public class Account : IDeserializationCallback
{ ... }

This interface requires you to implement a method called OnDeserialization and this is where we will "recreate" or DirectoryEntry object:

public virtual void OnDeserialization(Object sender)
{
_de = new DirectoryEntry(_sAdsiPath);
}

Since _sAdsiPath was serialized, we still have access to it upon deserialization, so we simply feed it back into our _de member variable and we're all set. The resultant code:


using System;
using System.DirectoryServices;
using System.Runtime.Serialization;

namespace SerializeTest
{
[Serializable]
public class Account : IDeserializationCallback
{
[NonSerialized] private DirectoryEntry _de;
private string _sAdsiPath = null;
private string _sDN = null;

public Account(string dn)
{
_sDN = dn;
_de = new DirectoryEntry(@"LDAP://demo.mycompany.com/" + _sDN);
_sAdsiPath = _de.Path;
}

public DirectoryEntry DirectoryEntry { get { return _de; } }
public string DN { get { return _sDN; } }

public virtual void OnDeserialization(Object sender)
{
_de = new DirectoryEntry(_sAdsiPath);
}
}
}

Happy Hacking*

Doug

*ethically of course...