Reading Multivalued Attributes

So far we've been dealing with attributes that have a single value. Active Directory, however, allows a single attribute to have multiple values, which comes in handy for attributes that hold data that can contain one or more values of the same data type.

A good example is telephone numbers. Many people have multiple phone numbers at which they can be called. The IADsUser interface defines the TelephoneNumber property, which can return a list of each number the person has in their directory entry. In Table 6-5, the objectClass attribute contains multiple values. Some of the common multivalued attributes associated with the user object are member, memberOf, otherHomePhone, otherLoginWorkstations, otherMailbox, otherTelephone, postOfficeBox, and seeAlso.

The objectClass attribute contains an array of string variants that correspond to the class object that the current object inherits from. Listing 6-1, from the MultiValued.bas sample on the companion CD, shows how a Visual Basic program can enumerate each of the values of the objectClass attribute.

 Option Explicit
Public Sub Main()
Dim objRootDSE As IADs
Dim objADs As IADs
Dim strADsPath As String
Dim varClass As Variant
Dim varClasses As Variant
` Connect to the LDAP server's root object
Set objRootDSE = GetObject("LDAP://RootDSE")
` Form a path to a directory entry
strADsPath = "LDAP://" & objRootDSE.Get("defaultNamingContext")
` Bind to the object
Set objADs = GetObject(strADsPath)
` Use the Get method to access a multivalued attribute
varClasses = objADs.Get("objectClass")
If IsArray(varClasses) Then
    ` Enumerate and display each value for this attribute
    For Each varClass In varClasses
        ` Display the value
        Debug.Print varClass     Next
Else
    ` Attribute only contains a single attribute
    Debug.Print varClasses
End If
End Sub

Listing 6-1 MultiValued.bas showing how multivalued attributes are read in Visual Basic.

In C++, reading multivalued attributes gets a little more complex because the C and C++ languages do little to protect the developer from exceeding the boundaries of an array. ADSI uses the Automation SAFEARRAY data type to hold the values in a multivalued attribute, as well as the upper and lower bounds of the array. You can use the SafeArray functions to retrieve each element in the array. Listing 6-2, from the MultiValue.cpp sample on the companion CD, shows how to read multivalued attributes using a SAFEARRAY data type.

 int _tmain(int /* argc */, _TCHAR /* **argv */, _TCHAR /* **envp */)
{
    IADs *pobjRootDSE;  // Pointer to RootDSE
    IADs *pobjIADs;     // Pointer to object interface
    HRESULT hResult;    // COM result code
    // Initalize COM
    CoInitialize( NULL );
    // Get the Active Directory RootDSE object
    hResult = ADsGetObject( 
        L"LDAP://RootDSE", IID_IADs, (void**) &pobjRootDSE );
    // Ensure binding success before dereferencing pointer
    if ( SUCCEEDED( hResult ) )
        {
        // Distinguished name of domain directory
        _variant_t varRoot;
        // Use Get method to retrieve default naming context 
        // (directory partition)
        hResult = pobjRootDSE->Get( L"defaultNamingContext", &varRoot );
        // No longer need the RootDSE object
        pobjRootDSE->Release();
        // Form a ADsPath string to the domain directory partition
        _bstr_t bstrADsPath = _bstr_t( L"LDAP://" ) + 
             _bstr_t( varRoot.bstrVal );         // Display information
        _tprintf( _T("Binding to object at %s\n"), 
            (const char *)bstrADsPath );
        // Get a pointer to the domain object
        hResult = ADsGetObject( 
            bstrADsPath, IID_IADs, (void**) &pobjIADs );
        // Ensure binding success before dereferencing pointer
        if ( SUCCEEDED ( hResult ) )
            {
            // Value(s) for objectClass
            _variant_t varClasses;
            // Retrieve the objectClass attribute using the Get method
            hResult = pobjIADs->Get ( L"objectClass", &varClasses );
            if ( SUCCEEDED ( hResult ) )
                {
                // Display label
                _tprintf( _T("objectClass: ") );
                // Does this attribute contain multiple values?
                // Use the Automation macro to check for an array
                if ( V_ISARRAY( &varClasses ) )
                    {
                    // Create a safe array
                    SAFEARRAY *saClasses = V_ARRAY( &varClasses );
                    // Setup the upper and lower boundries of the array
                    long lMin;
                    long lMax;
                    SafeArrayGetLBound( saClasses, 1, &lMin );
                    SafeArrayGetUBound( saClasses, 1, &lMax );
                    // Enumerate all the values of the array
                    long lIndex;
                    for ( lIndex = lMin; 
                        lIndex <= lMax; 
                        lIndex++ )
                        {
                        // Create a variant to hold the current value
                        _variant_t varClass;
                        // Use the Automation helper function to 
                        // get the value
                        hResult = SafeArrayGetElement( 
                            saClasses, &lIndex, &varClass );
                        if ( SUCCEEDED( hResult ) )
                            {
                            // Display the line
                            _tprintf ( _T("%s, "), (const char *)
                                _bstr_t( varClass ) );
                            }
                        }
                    // Terminate the line
                    _tprintf ( _T("\n") );
                    }
                else
                    {
                    // Display the single value for this attribute
                    _tprintf ( _TEXT("%s\n"), (const char *)
                        _bstr_t( varClasses ) );
                    }
                }
            // Object no longer needed
            pobjIADs->Release();
            }
        }
    // Unload COM
    CoUninitialize();
    // Return the result of any failures
    return hResult;
}

Listing 6-2 MultiValue.cpp showing how multivalued attributes are read in C++.

Not all attributes can accept multiple values. The attribute definition in the schema determines whether a particular attribute can contain multiple values. You can determine whether an attribute contains multiple values dynamically by calling the IsArray function in Visual Basic or the Automation macro VT_ISARRAY in C++.

You can check the attribute's schema entry to see whether the attribute you are working with allows multiple values. Here is some code from the Visual Basic version of the IADsProperties sample that enumerates the abstract schema container, checking each defined property for whether it is multivalued. If it is, it's added to a list box for display. (I'll discuss enumerating containers in more detail later in the chapter in the section "Enumerating Containers." I'll discuss the abstract schema container in Chapter 9.)

 `================================================
` Display multivalued attributes in list box
`================================================
Dim objADsSchema As IADsContainer
Dim objADsProperty As IADsProperty
` Clear the list box contents
lstProperties.Clear
` Get the abstract schema container
Set objADsSchema = GetObject("LDAP://Schema")
` Filter only properties
objADsSchema.Filter = Array("property")
` Enumerate the schema container
For Each objADsProperty In objADsSchema
    ` Check if multivalued
    If objADsProperty.MultiValued Then
        ` Add the name to the list
        lstProperties.AddItem objADsProperty.Name
    End If
Next

The GetEx Method

A major hassle in dealing with a multivalued attribute is that you must take one of two possible code paths to handle it: one for a single value, or another for multiple values. Wouldn't it be nice if you could use the same code for both single and multivalued attributes? The GetEx method solves this problem quite nicely.

GetEx performs the same function as Get, but it returns the data differently. The Get method returns a binary string for single values and a variant array for multiple values. The GetEx method always returns a variant array. Your code does not have to check for an array and can use the array code for single values as well as multiple values. The following code, from the GetExMethod.bas sample on the companion CD, demonstrates this:

 ` The GetEx method always returns a variant array even for single values
varClasses = objADs.GetEx("objectClass")

` Enumerate and display each value for this attribute
For Each varClass In varClasses
    ` Display the value
    Debug.Print varClass
Next

So if GetEx is more consistent, why does ADSI bother providing the Get method? For simplicity reasons. With GetEx, you always get an array in return, but that requires your code to enumerate the array, even if just one value exists.

Always use GetEx for multivalued attributes.



MicrosoftR WindowsR 2000 Active DirectoryT Programming
MicrosoftR WindowsR 2000 Active DirectoryT Programming
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net