Introduction
Every object in an LDAP directory is uniquely identified by a distinguished name (DN
). A distinguished name specifies the name of the object itself, along with the names of all its parent objects. Thus, a DN
identifies the object itself as well as its position in the tree.
The rules for representing distinguished names as strings are laid out in RFC 2253, and they actually get a little complex in places. Hence, this parser.
Background
I was rewriting some terrible ADSI code (that I wrote myself a few years ago, but I'm going to gloss over that little detail), and I noticed that the original code navigated the LDAP tree by getting an ADSI
object, reading its distinguishedName
property, then getting its parent object and reading that object's distinguishedName
property recursively. That procedure made me pretty uncomfortable because it involved a lot of back-and-forth with the server that wasn't really necessary. I figured it would be a simple thing to parse a distinguished name in code, thereby avoiding all the server-side processing. Well, it turns out that it was less simple than I expected, but it came out pretty well, so I thought I'd share the results with you folks.
Using the code
There are three main classes in this code:
DN
, which represents a full distinguished name
RDN
, which represents a relative distinguished name
RDNComponent
, which represents the individual components of a multivalued RDN
I don't think that multivalued RDN
s are even supported by Active Directory, but they're supported by the RFC, so they're supported by my parser.
You construct a DN
object by feeding it a distinguished name string
, like so:
DN myDN = new DN(@"CN=Pete Everett\, esq.,OU=People,DC=example,DC=com");
To print out a DN
object, you use its ToString()
method, as you'd expect.
Console.WriteLine(myDN.ToString());
But if you'd like more control over the formatting, you can specify categories of characters to escape.
Console.WriteLine(myDN.ToString(EscapeChars.None));
To get the parent object of a given DN
object, you can use its Parent
property.
DN myParentDN = myDN.Parent;
Console.WriteLine(myParentDN.ToString());
And to get a child of a given DN
object, you can use its GetChild()
method.
DN myChildDN = myParentDN.GetChild("CN=Mike");
Console.WriteLine(myChildDN.ToString());
You can also access the individual RDN
s of a given DN
object, like so:
Console.WriteLine(myChildDN.RDNs[2].ToString());
And you can get the type or value of a component of an RDN
, if you're inclined.
Console.WriteLine(myChildDN.RDNs[1].Components[0].ComponentValue);
Design Considerations
I wanted to make sure that each DN
(and its component parts) was immutable. This allowed me to do a couple of cute things up front, like calculate an object's hash code upon object creation (and use the hash code as a quick test for inequality) and pass references to the same underlying objects between different DN
objects. For example:
DN referenceDN = new DN("OU=Marketing,OU=People,DC=example,DC=com");
DN parentDN = referenceDN.Parent;
It's worth noting, though, that this only does the job halfway, because:
DN childDN = parentDN.GetChild("OU=Marketing");
Limitations
- This is my first crack at coding something to match an RFC. I think I've done a pretty good job of it, but it's possible that I've misinterpreted something in the spec. Send criticisms my way. (But be gentle. I'm a little sensitive.)
- Working with multibyte characters is tricky. The RFC specifies that multibyte characters should be allowed to be escaped as their individual bytes. So for example, Unicode character 0x266B (a musical note) should be allowed to be represented as \E2\99\AB (its three-byte UTF-8 encoding). The problem is that ADSI's
GetObject()
method (and anything built on top of it, like .NET's DirectoryServices stuff) doesn't support escaped multibyte characters. So if you feed it an escaped character, it'll choke. But if you feed an unescaped multibyte character to something that expects ASCII, it'll also choke. My workaround to this problem has been to let you choose which categories of characters to escape. (There's an example above.)
- I thought up as many strange test cases as I could and wrote a bunch of NUnit tests which kept me company throughout the development process. I've included the tests along with the code, so you can verify that any changes you might want to make won't break existing functionality. I'm sure, though, that I've done a half-hearted job of thinking of tests, and if you see any glaring omissions, please send them my way.
History
- March 7, 2005 - Initial posting
- March 26, 2009 - Fixed some
null
-reference bugs