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...

1 comment:

Piotr PakuĊ‚a said...

Consider the scenario where you only one server has access to AD. You create DirectoryEntry object, get Path from it, and send path to another server. How can you get DirectoryEntry object on server which don't have access to AD basing only on Path property?