We need to know just a few basic things about using the value collection classes in order to be productive. Let's examine this from a task-centric perspective. In each of these examples, assume we have a DirectoryEntry called entry and a SearchResult called result pointing to the same object in the directory.
Getting Single Values
This is probably the thing we will do most often. Typically, we will either use the Value property or access the first element in the collection:
object name = entry.Properties["name"].Value; name = entry.Properties["name"][0]; name = result.Properties["name"][0];
Note that the first approach is slightly safer if we have not already checked for a null value, as the Value property will return null/Nothing. The other approaches will throw an exception if the attribute is null, as the array will not have a value at index 0.
Checking for Null Values
If we try to imagine an object in an LDAP directory as a row in a SQL database, the schema for the table would typically allow many null column values. The vast majority of attributes in most LDAP classes are optional and it is very common for objects to contain only a small fraction of the attributes allowed by the schema. This analogy is a little flimsy, and we explain why in the sidebar LDAP and Null Values, later in this chapter. However, it is useful for our purposes here.
As a result, we will be checking for null values frequently. This is just good defensive programming and will contribute greatly to the stability of our applications in production.
With PropertyValueCollection, we can do this by using the Value property:
if (entry.Properties["description"].Value != null) { //do something interesting }
An alternate approach is to check the Count property:
if (entry.Properties["description"].Count > 0) { //do something interesting }
Things change a bit with ResultPropertyValueCollection. Since it does not have a Value property, we cannot check it for a null value. This is also a situation where there are differences between versions 1.x and 2.0 of the framework. Namely, in version 1.x, a ResultPropertyValueCollection instance was not created if the attribute was not returned in the search, so checking a property like Count would generate a NullReferenceException. This behavior has changed in version 2.0, and a Result-PropertyValueCollection instance will be returned, making it safe to check the Count property without fear of an exception.
Of course, it is less than ideal to have to remember how value collections will behave based on the version of the framework we are using, and it leads to fragile code. Instead, we should always check the ResultPropertyCollection or PropertyValueCollection first:
if (result.Properties.Contains("description")) { //do something interesting } if (entry.Properties.Contains("description")) { //do something interesting }
It turns out that since the Contains method works for both Result-PropertyCollection and PropertyCollection classes, it tends to be easier to use this method consistently without needing to remember the details of either class or the differences due to versions of the framework. For this reason, we generally recommend using the Contains method, as it will work well in all circumstances and scenarios.
Checking for Multiple Values
Many attributes in LDAP allow multiple values and the basic design of the value collections assumes that any attribute may contain multiple values. In order to find out if an attribute actually contains multiple values, we can use the Count property:
if (entry.Properties["memberOf"].Count > 1) { //do something interesting } if (result.Properties.Contains("memberOf")) { if (result.Properties["memberOf"].Count > 1) { //do something interesting } }
We would do something similar with a SearchResult/ResultPropertyValueCollection.
Another option is simply to enumerate the collection with foreach:
foreach (string groupDN in entry.Properties["memberOf"]) { //do something interesting } if (result.Properties.Contains("memberOf")) { foreach (string groupDN in result.Properties["memberOf"]) { //do something interesting } }
Using the Value Property
One of the primary differences between PropertyValueCollection and ResultPropertyValueCollection is that the former contains a Value property. The Value property does one of three different things when reading an attribute value.
Syntax Name |
Object(DS-DN) |
LDAP Syntax |
2.5.5.1 |
OM Syntax |
127 |
ADSI Type |
ADSTYPE_DN_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Standard distinguished name (DN) syntax |
Syntax Name |
String(Object-Identifier) |
LDAP Syntax |
2.5.5.2 |
OM Syntax |
6 |
ADSI Type |
ADSTYPE_CASE_IGNORE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Contains only digits and "." |
Syntax Name |
String(Teletex) |
LDAP Syntax |
2.5.5.4 |
OM Syntax |
20 |
ADSI Type |
ADSTYPE_CASE_IGNORE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Case insensitive for searching; Teletex characters only |
Syntax Name |
String(Printable) |
LDAP Syntax |
2.5.5.5 |
OM Syntax |
19 |
ADSI Type |
ADSTYPE_PRINTABLE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Case sensitive for searching; printable characters only |
Syntax Name |
String(IA5) |
LDAP Syntax |
2.5.5.5 |
OM Syntax |
22 |
ADSI Type |
ADSTYPE_PRINTABLE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Case sensitive for searching; IA5 string |
Syntax Name |
String(Numeric) |
LDAP Syntax |
2.5.5.6 |
OM Syntax |
18 |
ADSI Type |
ADSTYPE_NUMERIC_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Contains only digits; rarely used in Active Directory |
Syntax Name |
Object(DN-Binary) |
LDAP Syntax |
2.5.5.7 |
OM Syntax |
127 |
ADSI Type |
ADSTYPE_DN_WITH_BINARY |
COM Type |
VT_DISPATCH (IADsDNWithBinary) |
DirectoryEntry |
System.__ComObject |
DirectorySearcher |
System.String |
Notes |
Also Object(OR-Name); used for associating a GUID with DN |
Syntax Name |
Boolean |
LDAP Syntax |
2.5.5.8 |
OM Syntax |
1 |
ADSI Type |
ADSTYPE_BOOLEAN |
COM Type |
VT_BOOL |
DirectoryEntry |
System.Boolean |
DirectorySearcher |
System.Boolean |
Notes |
Used for standard Boolean values |
Syntax Name |
Integer |
LDAP Syntax |
2.5.5.9 |
OM Syntax |
2 |
ADSI Type |
ADSTYPE_INTEGER |
COM Type |
VT_I4 |
DirectoryEntry |
System.Int32 |
DirectorySearcher |
System.Int32 |
Notes |
Used for standard signed integers |
Syntax Name |
Enumeration |
LDAP Syntax |
2.5.5.9 |
OM Syntax |
10 |
ADSI Type |
ADSTYPE_INTEGER |
COM Type |
VT_I4 |
DirectoryEntry |
System.Int32 |
DirectorySearcher |
System.Int32 |
Notes |
Used for enumerated values |
Syntax Name |
String(Octet) |
LDAP Syntax |
2.5.5.10 |
OM Syntax |
4 |
ADSI Type |
ADSTYPE_OCTET_STRING |
COM Type |
VT_UI1|VT_ARRAY |
DirectoryEntry |
System.Byte[] |
DirectorySearcher |
System.Byte[] |
Notes |
Used for arbitrary binary data |
Syntax Name |
Object(Replica-Link) |
LDAP Syntax |
2.5.5.10 |
OM Syntax |
127 |
ADSI Type |
ADSTYPE_OCTET_STRING |
COM Type |
VT_VARIANT |
DirectoryEntry |
System.Byte[] |
DirectorySearcher |
System.Byte[] |
Notes |
Used by the system only for replication |
Syntax Name |
String(UTC-Time) |
LDAP Syntax |
2.5.5.11 |
OM Syntax |
23 |
ADSI Type |
ADSTYPE_UTC_TIME |
COM Type |
VT_DATE |
DirectoryEntry |
System.DateTime |
DirectorySearcher |
System.DateTime |
Notes |
Used for date values; stored relative to UTC |
Syntax Name |
String(Generalized-Time) |
LDAP Syntax |
2.5.5.11 |
OM Syntax |
24 |
ADSI Type |
ADSTYPE_UTC_TIME |
COM Type |
VT_DATE |
DirectoryEntry |
System.DateTime |
DirectorySearcher |
System.DateTime |
Notes |
Used for date values; time zone information is included |
Syntax Name |
String(Unicode) |
LDAP Syntax |
2.5.5.12 |
OM Syntax |
64 |
ADSI Type |
ADSTYPE_CASE_IGNORE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Case insensitive for searching; contains any Unicode character |
Syntax Name |
Object(Presentation-Address) |
LDAP Syntax |
2.5.5.13 |
OM Syntax |
127 |
ADSI Type |
ADSTYPE_CASE_IGNORE_STRING |
COM Type |
VT_BSTR |
DirectoryEntry |
System.String |
DirectorySearcher |
System.String |
Notes |
Not really used in Active Directory either |
Syntax Name |
Object(DN-String) |
LDAP Syntax |
2.5.5.14 |
OM Syntax |
127 |
ADSI Type |
ADSTYPE_DN_WITH_STRING |
COM Type |
VT_DISPATCH (IADsDNWithString) |
DirectoryEntry |
System.__ComObject |
DirectorySearcher |
System.String |
Notes |
Not used in Active Directory schema; also defined as Object |
Syntax Name |
String(NT-Sec-Desc) |
LDAP Syntax |
2.5.5.15 |
OM Syntax |
66 |
ADSI Type |
ADSTYPE_NT_SECURITY_DESCRIPTOR |
COM Type |
VT_DISPATCH (IADsSecurityDescriptor) |
DirectoryEntry |
System.__ComObject |
DirectorySearcher |
System.Byte[] |
Notes |
Contains Windows security descriptors |
Syntax Name |
Interval/LargeInteger |
LDAP Syntax |
2.5.5.16 |
OM Syntax |
65 |
ADSI Type |
ADSTYPE_LARGE_INTEGER |
COM Type |
VT_DISPATCH (IADsLargeInteger) |
DirectoryEntry |
System.__ComObject |
DirectorySearcher |
System.Int64 |
Notes |
Both types have same syntaxes, but Interval is treated as unsigned |
Syntax Name |
String(Sid) |
LDAP Syntax |
2.5.5.17 |
OM Syntax |
4 |
ADSI Type |
ADSTYPE_OCTET_STRING |
COM Type |
VT_UI1|VT_ARRAY |
DirectoryEntry |
System.Byte[] |
DirectorySearcher |
System.Byte[] |
Notes |
Contains Windows security identifiers |
For example, let's take the member attribute on the group class. It is defined as syntax 2.5.5.1, which is represented in .NET as System.String (see Table 6.1). It is defined in the schema as multivalued and optional. As such, the Value property might return null, a System.String, or an array of System.String objects, depending on whether the group has zero, one, or multiple members.
This makes the Value property especially useful for getting attribute values directly, as we have seen in the earlier examples and throughout the book. It can also be used for writing attribute values, where it is even more useful.
Part I: Fundamentals
Introduction to LDAP and Active Directory
Introduction to .NET Directory Services Programming
Binding and CRUD Operations with DirectoryEntry
Searching with the DirectorySearcher
Advanced LDAP Searches
Reading and Writing LDAP Attributes
Active Directory and ADAM Schema
Security in Directory Services Programming
Introduction to the ActiveDirectory Namespace
Part II: Practical Applications
User Management
Group Management
Authentication
Part III: Appendixes
Appendix A. Three Approaches to COM Interop with ADSI
Appendix B. LDAP Tools for Programmers
Appendix C. Troubleshooting and Help
Index