Collection Class Usage
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.
- If the attribute does not exist on the object, it returns null/Nothing.
- If the attribute contains a single value, it returns an Object that represents a scalar value of the type as marshaled by the .NET Framework (see Table 6.1).
Table 6.1. LDAP Attribute Syntaxes with Their Matching Programmatic Data Types
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
(Access-Point) which is not used and has no marshaling defined
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
- If the attribute contains multiple values, it returns an Object that contains a single-dimensional array of similarly typed objects.
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.