Programming Active Directory


To develop programs for Active Directory, you can either use the classes from the System .DirectoryServices or the System.DirectoryServices.Protocols namespaces. In the namespace System.DirectoryServices, you can find classes that wrap Active Directory Service Interfaces (ADSI) COM objects to access the Active Directory.

ADSI is a programmatic interface to directory services. It defines some COM interfaces that are implemented by ADSI providers. This means that the client can use different directory services with the same programmatic interfaces. The .NET Framework classes in the System.DirectoryServices namespace make use of ADSI.

Figure 42-8 shows some ADSI Providers (LDAP, IIS, and NDS) that implement COM interfaces such as IADs and IUnknown. The assembly System.DirectoryServices makes use of the ADSI providers.

image from book
Figure 42-8

Classes from the namespace System.DirectoryServices.Protocols make use of Directory Services Markup Language (DSML) Services for Windows. With DSML, standardized Web service interfaces are defined by the OASIS group (www.oasis-open.org/committees/dsml).

To use the classes from the System.DirectoryServices namespace, you have to reference the System.DirectoryServices assembly. With the classes in this assembly you can query objects, view and update properties, search for objects, and move objects to other container objects. In the code segments that follow a little later in this section, you use a simple C# console application that demonstrates the functionality of the classes in the System.DirectoryServices namespace.

This section covers the following:

  • Classes in the System.DirectoryServices namespace

  • The process of connecting to the Active Directory (binding)

  • Getting directory entries and creating new objects and updating existing entries

  • Searching Active Directory

Classes in System.DirectoryServices

The following table shows the major classes in the System.DirectoryServices namespace.

Open table as spreadsheet

Class

Description

DirectoryEntry

This class is the main class of the System.DirectoryServices namespace. An object of this class represents an object in the Active Directory store. This class is used to bind to an object and to view and to update properties. The properties of the object are represented in a PropertyCollection. Every item in the PropertyCollection has a PropertyValueCollection.

DirectoryEntries

DirectoryEntries is a collection of DirectoryEntry objects. The Children property of a DirectoryEntry object returns a list of objects in a DirectoryEntries collection.

DirectorySearcher

This class is the main class used for searching for objects with specific attributes. To define the search the SortOption class and the enumerations SearchScope, SortDirection, and ReferalChasingOption can be used. The search results in a SearchResult or a SearchResultCollection. You also get ResultPropertyCollection and ResultPropertyValueCollection objects.

Binding

To get the values of an object in Active Directory, you have to connect to the Active Directory service. This connecting process is called binding. The binding path can look like this:

 LDAP://dc01.thinktecture.com/OU=Development, DC=thinktecture, DC=Com

With the binding process, you can specify these items:

  • The protocol specifies the provider to be used.

  • The server name of the domain controller.

  • The port number of the server process.

  • The distinguished name of the object; this identifies the object you want to access.

  • The username and password, if the user who is allowed to access the Active Directory is different from the current logged-on user.

  • An authentication type can also be specified if encryption is needed.

The following subsections discuss these options in more detail.

Protocol

The first part of a binding path specifies the ADSI provider. The provider is implemented as a COM server; for identification a progID can be found in the registry directly under HKEY_CLASSES_ROOT. The providers that are available with Windows XP are listed in the following table.

Open table as spreadsheet

Provider

Description

LDAP

LDAP Server, such as the Exchange directory and Windows 2000 Server or Windows Server 2003 Active Directory Server.

GC

GC is used to access the global catalog in Active Directory. It can be used for fast queries.

IIS

With the ADSI provider for IIS it’s possible to create new Web sites and to administer them in the IIS catalog.

NDS

This progID is used to communicate with Novell Directory Services.

NWCOMPAT

With NWCOMPAT, you can access old Novell directories, such as Novell Netware 3.x.

Server Name

The server name follows the protocol in the binding path. The server name is optional if you are logged on to an Active Directory domain. Without a server name, serverless binding occurs; this means that Windows Server 2003 tries to get the “best” domain controller in the domain that’s associated with the user doing the bind. If there’s no server inside a site, the first domain controller that can be found will be used.

A serverless binding might look like this: LDAP://OU=Sales, DC=Thinktecture, DC=Local.

Port Number

After the server name, you can specify the port number of the server process by using the syntax :xxx. The default port number for the LDAP server is port 389: LDAP://dc01.sentinel.net:389. The Exchange server uses the same port number as the LDAP server. If the Exchange server is installed on the same system - for example, as a domain controller of Active Directory - a different port can be configured.

Distinguished Name

The fourth part that you can specify in the path is the distinguished name (DN). The distinguished name is a unique name that identifies the object you want to access. With Active Directory, you can use LDAP syntax that is based on X.500 to specify the name of the object.

This is an example of a distinguished name:

  CN=Christian Nagel, OU=Consultants, DC=thinktecture, DC=local 

This distinguished name specifies the common name (CN) of Christian Nagel in the organizational unit (OU) called Consultants in the domain component (DC) called thinktecture of the domain thinktecture.local. The part specified to the right is the root object of the domain. The name has to follow the hierarchy in the object tree.

You can find the LDAP specification for the string representation of distinguished names in RFC 2253 at www.ietf.org/rfc/rfc2253.txt.

Relative Distinguished Name

A relative distinguished name (RDN) is used to reference objects within a container object. With an RDN the specification of OU and DC is not needed, because a common name is enough. CN=Christian Nagel is the relative distinguished name inside the organizational unit. A relative distinguished name can be used if you already have a reference to a container object and if you want to access child objects.

Default Naming Context

If a distinguished name is not specified in the path, the binding process will be made to the default naming context. You can read the default naming context with the help of rootDSE. LDAP 3.0 defines rootDSE as the root of a directory tree on a directory server. For example:

  LDAP://rootDSE 

or

  LDAP://servername/rootDSE 

By enumerating all properties of the rootDSE, you can get the information about the defaultNamingContext that will be used when no name is specified. schemaNamingContext and configurationNamingContext specify the required names to be used to access the schema and the configuration in the Active Directory store.

The following code is used to get all properties of rootDSE:

  using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/rootDSE";    de.Username = @"explorer\christian";    de.Password = "password";    PropertyCollection props = de.Properties;    foreach (string prop in props.PropertyNames)    {       PropertyValueCollection values = props[prop];       foreach (string val in values)       {          Console.Write(prop + ": ");          Console.WriteLine(val);       }    } } 

This program shows the default naming context (defaultNamingContext DC=explorer, DC=local), the context that can be used to access the schema (CN=Schema, CN=Configuration, DC=explorer, DC=local), and the naming context of the configuration (CN=Configuration, DC=explorer, DC=local), as shown in Figure 42-9.

image from book
Figure 42-9

Object Identifier

Every object has a globally unique identifier (GUID). A GUID is a unique 128-bit number as you may already know from COM development. You can bind to an object using the GUID. This way you always get to the same object, regardless of whether the object was moved to a different container. The GUID is generated at object creation and always remains the same.

You can get to a GUID string representation with DirectoryEntry.NativeGuid. This string representation can then be used to bind to the object.

This example shows the path name for a serverless binding to bind to a specific object represented by a GUID:

  LDAP://<GUID=14abbd652aae1a47abc60782dcfc78ea> 

Username

If a different user from the one of the current process must be used for accessing the directory (maybe this user doesn’t have the required permissions to access Active Directory), explicit user credentials must be specified for the binding process. Active Directory has multiple ways to specify the username.

Downlevel Logon

With a downlevel logon the username can be specified with the pre-Windows 2000 domain name:

  domain\username 

Distinguished Name

The user can also be specified by a distinguished name of a user object, for example:

  CN=Administrator, CN=Users, DC=thinktecture, DC=local  

User Principal Name

The user principal name (UPN) of an object is defined with the userPrincipalName attribute. The system administrator specifies this with the logon information in the Account tab of the User properties with the Active Directory Users and Computers tool. Note that this is not the e-mail address of the user.

This information also uniquely identifies a user and can be used for a logon:

  Nagel@thinktecture.local 

Authentication

For secure encrypted authentication, the authentication type can also be specified. The authentication can be set with the AuthenticationType property of the DirectoryEntry class. The value that can be assigned is one of the AuthenticationTypes enumeration values. Because the enumeration is marked with the [Flags] attribute, multiple values can be specified. Some of the possible values are where the data sent is encrypted; ReadonlyServer, where you specify that you need only read access; and Secure for secure authentication.

Binding with the DirectoryEntry Class

The System.DirectoryServices.DirectoryEntry class can be used to specify all the binding information. You can use the default constructor and define the binding information with the properties Path, Username, Password, and AuthenticationType, or pass all the information in the constructor:

  DirectoryEntry de = new DirectoryEntry(); de.Path = "LDAP://platinum/DC=thinktecture, DC=local"; de.Username = "nagel@thinktecture.local"; de.Password = "password"; // use the current user credentials DirectoryEntry de2 = new DirectoryEntry(                             "LDAP://DC=thinktecture, DC=local"); 

Even if the construction of the DirectoryEntry object is successful, this doesn’t mean that the binding was a success. Binding will happen the first time a property is read to avoid unnecessary network traffic. At the first access of the object, you can see if the object exists and if the specified user credentials are correct.

Getting Directory Entries

Now that you know how to specify the binding attributes to an object in Active Directory, you can move on to read the attributes of an object. In the following example, you read the properties of user objects.

The DirectoryEntry class has some properties to get information about the object: the Name, Guid, and SchemaClassName properties. The first time a property of the DirectoryEntry object is accessed, the binding occurs and the cache of the underlying ADSI object is filled. (This is discussed in more detail shortly.) Additional properties are read from the cache, and communication with the server isn’t necessary for data from the same object.

In the following example, the user object with the common name Christian Nagel in the organizational unit Wrox Press is accessed:

  using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/CN=Christian Nagel, " +              "OU=thinktecture, DC=explorer, DC=local";    Console.WriteLine("Name: " + de.Name);    Console.WriteLine("GUID: " + de.Guid);    Console.WriteLine("Type: " + de.SchemaClassName);    Console.WriteLine();    //... } 

Tip 

To have this code running on your machine, you must change the path to the object to access including the server name.

An Active Directory object holds much more information, with the information available depending on the type of the object; the Properties property returns a PropertyCollection. Each property is a collection itself, because a single property can have multiple values; for example, the user object can have multiple phone numbers. In this case, you go through the values with an inner foreach loop. The collection returned from properties[name] is an object array. The attribute values can be strings, numbers, or other types. Here, just the ToString() method is used to display the values.

  Console.WriteLine("Properties: "); PropertyCollection properties = de.Properties; foreach (string name in properties.PropertyNames) {    foreach (object o in properties[name])    {       Console.WriteLine(name + ": " + o.ToString());    } } 

In the resulting output, you can see all attributes of the user object Christian Nagel (see Figure 42-10). otherTelephone is a multivalue property that has many phone numbers. Some of the property values just display the type of the object, System.__ComObject; for example, lastLogoff, lastLogon, and nTSecurityDescriptor. To get the values of these attributes, you have to use the ADSI COM interfaces directly from the classes in the System.DirectoryServices namespace.

image from book
Figure 42-10

Tip 

Chapter 23, “COM Interoperability,” explains how to work with COM objects and interfaces.

Access a Property Directly by Name

With DirectoryEntry.Properties, you can access all properties. If a property name is known, you can access the values directly:

  foreach (string homePage in de.Properties["wWWHomePage"])    Console.WriteLine("Home page: " + homePage); 

Object Collections

Objects are stored hierarchically in the Active Directory. Container objects contain children. You can enumerate these child objects with the Children property of the class DirectoryEntry. In the other direction, you can get the container of an object with the Parent property.

A user object doesn’t have children, so you use an organizational unit in the following example (see Figure 42-11). Noncontainer objects return an empty collection with the Children property. Get all user objects from the organizational unit Wrox Press in the domain thinktecture.local. The Children property returns a DirectoryEntries collection that collects DirectoryEntry objects. You iterate through all DirectoryEntry objects to display the name of the child objects:

  using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/OU=thinktecture, " +              "DC=explorer, DC=local";    Console.WriteLine("Children of " + de.Name);    foreach (DirectoryEntry obj in de.Children)    {       Console.WriteLine(obj.Name);    } } 

image from book
Figure 42-11

In this example, you see all the objects in the organizational unit: users, contacts, printers, shares, and others. If you want to display only some object types, you can use the SchemaFilter property of the DirectoryEntries class. The SchemaFilter property returns a SchemaNameCollection. With this SchemaNameCollection, you can use the Add() method to define the object types you want to see. Here, you are just interested in seeing the user objects, so user is added to this collection:

 using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/OU=Thinktecture, " +              "DC=explorer, DC=local";    Console.WriteLine("Children of " + de.Name);       de.Children.SchemaFilter.Add("user");    foreach (DirectoryEntry obj in de.Children)    {       Console.WriteLine(obj.Name);    } }

As a result, you see only the user objects in the organizational unit in Figure 42-12.

image from book
Figure 42-12

Cache

To reduce the network transfers, ADSI uses a cache for the object properties. As mentioned earlier, the server isn’t accessed when a DirectoryEntry object is created; instead, with the first reading of a value from the directory store all the properties are written into the cache, so that a round trip to the server isn’t necessary when the next property is accessed.

Writing any changes to objects changes only the cached object; setting properties doesn’t generate network traffic. You must use DirectoryEntry.CommitChanges() to flush the cache and to transfer any changed data to the server. To get the newly written data from the directory store, you can use DirectoryEntry .RefreshCache() to read the properties. Of course, if you change some properties without calling CommitChanges() and do a RefreshCache(), all your changes will be lost, because you read the values from the directory service again using RefreshCache().

It is possible to turn off this property cache by setting the DirectoryEntry.UsePropertyCache property to false. However, unless you are debugging your code, it’s better not to turn off the cache because of the extra round trips to the server that will be generated.

Creating New Objects

When you want to create new Active Directory objects - such as users, computers, printers, contacts, and so on - you can do this programmatically with the DirectoryEntries class.

To add new objects to the directory, first you have to bind to a container object, such as an organizational unit, where new objects can be inserted - you cannot use objects that are not able to contain other objects. The following example uses the container object with the distinguished name CN=Users, DC=thinktecture, DC=local:

  DirectoryEntry de = new DirectoryEntry(); de.Path = "LDAP://treslunas/CN=Users, DC=explorer, DC=local"; 

You can get to the DirectoryEntries object with the Children property of a DirectoryEntry:

  DirectoryEntries users = de.Children; 

The class DirectoryEntries offers methods to add, remove, and find objects in the collection. Here, a new user object is created. With the Add() method, the name of the object and a type name are required. You can get to the type names directly using ADSI Edit.

  DirectoryEntry user = users.Add("CN=John Doe", "user"); 

The object now has the default property values. To assign specific property values, you can add properties with the Add() method of the Properties property. Of course, all of the properties must exist in the schema for the user object. If a specified property doesn’t exist, you’ll get a COMException: “The specified directory service attribute or value doesn’t exist”:

  user.Properties["company"].Add("Some Company"); user.Properties["department"].Add("Sales"); user.Properties["employeeID"].Add("4711"); user.Properties["samAccountName"].Add("JDoe"); user.Properties["userPrincipalName"].Add("JDoe@explorer.local"); user.Properties["givenName"].Add("John"); user.Properties["sn"].Add("Doe"); user.Properties["userPassword"].Add("someSecret"); 

Finally, to write the data to Active Directory, you have to flush the cache:

  user.CommitChanges(); 

Updating Directory Entries

Objects in the Active Directory service can be updated as easily as they can be read. After reading the object, you can change the values. To remove all values of a single property, you can call the method PropertyValueCollection.Clear(). You can add new values to a property with Add(). Remove() and RemoveAt() remove specific values from a property collection.

You can change a value simply by setting it to the specified value. The following example uses an indexer for PropertyValueCollection to set the mobile phone number to a new value. With the indexer a value can only be changed if it exists. Therefore, you should always check with DirectoryEntry .Properties.Contains() to see if the attribute is available:

 using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/CN=Christian Nagel, " +              "OU=thinktecture, DC=explorer, DC=local";    if (de.Properties.Contains("mobile"))    {       de.Properties["mobile"][0] = "+43(664)3434343434";    }    else    {       de.Properties["mobile"].Add("+43(664)3434343434");    }     de.CommitChanges(); } 

The else part in this example uses the method PropertyValueCollection.Add() to add a new property for the mobile phone number, if it doesn’t exist already. If you use the Add() method with already existing properties, the resulting effect would depend on the type of the property (single-value or multivalue property). Using the Add() method with a single-value property that already exists results in a COMException: “A constraint violation occurred.” Using Add() with a multivalue property, however, succeeds, and an additional value is added to the property.

The mobile property for a user object is defined as a single-value property, so additional mobile phone numbers cannot be added. However, a user can have more than one mobile phone number. For multiple mobile phone numbers, the otherMobile property is available. otherMobile is a multivalue property that allows setting multiple phone numbers, and so calling Add() multiple times is allowed. Note that multivalue properties are checked for uniqueness. If the second phone number is added to the same user object again, you get a COMException: “The specified directory service attribute or value already exists.”

Important 

Remember to call DirectoryEntry.CommitChanges() after creating or updating new directory objects. Otherwise, only the cache gets updated, and the changes are not sent to the directory service.

Accessing Native ADSI Objects

Often, it is a lot easier to call methods of predefined ADSI interfaces instead of searching for the names of object properties. Some ADSI objects also support methods that cannot be used directly from the DirectoryEntry class. One example of a practical use is the IADsServiceOperations interface, which has methods to start and stop Windows services. (For more details on Windows services see Chapter 22, “Windows Services.”)

The classes of the System.DirectoryServices namespace use the underlying ADSI COM objects as mentioned earlier. The DirectoryEntry class supports calling methods of the underlying objects directly by using the Invoke() method.

The first parameter of Invoke() requires the method name that should be called in the ADSI object; the params keyword of the second parameter allows a flexible number of additional arguments that can be passed to the ADSI method:

  public object Invoke(string methodName, params object[] args); 

You can find the methods that can be called with the Invoke() method in the ADSI documentation. Every object in the domain supports the methods of the IADs interface. The user object that you created previously also supports the methods of the IADsUser interface.

In the following example, the method IADsUser.SetPassword() changes the password of the previously created user object:

  using (DirectoryEntry de = new DirectoryEntry()) {    de.Path = "LDAP://treslunas/CN=John Doe, " +              "CN=Users, DC=explorer, DC=local";    de.Invoke("SetPassword", "anotherSecret");    de.CommitChanges(); } 

It is also possible to use the underlying ADSI object directly instead of using Invoke(). To use these objects, choose Project image from book Add Reference to add a reference to the Active DS Type Library (see Figure 42-13). This creates a wrapper class where you can access these objects in the namespace ActiveDs.

image from book
Figure 42-13

The native object can be accessed with the NativeObject property of the DirectoryEntry class. In the following example, the object de is a user object, so it can be cast to ActiveDs.IADsUser. SetPassword() is a method documented in the IADsUser interface, so you can call it directly instead of using the Invoke() method. By setting the AccountDisabled property of IADsUser to false, you can enable the account. As in the previous examples, the changes are written to the directory service by calling CommitChanges() with the DirectoryEntry object:

  ActiveDs.IADsUser user = (ActiveDs.IADsUser)de.NativeObject; user.SetPassword("someSecret"); user.AccountDisabled = false; de.CommitChanges(); 

Searching in Active Directory

Because Active Directory is a data store optimized for read-mostly access, you will generally search for values. To search in Active Directory, the .NET Framework provides the DirectorySearcher class.

Tip 

You can only use DirectorySearcher with the LDAP provider; it doesn’t work with the other providers such as NDS or IIS.

In the constructor of the DirectorySearcher class, you can define four important parts for the search. You can also use a default constructor and define the search options with properties.

SearchRoot

The search root specifies where the search should start. The default of SearchRoot is the root of the domain you’re currently using. SearchRoot is specified with the Path of a DirectoryEntry object.

Filter

The filter defines the values where you want to get hits. The filter is a string that must be enclosed in parentheses.

Relational operators such as <=, =, and >= are allowed in expressions. (objectClass=contact) searches all objects of type contact; (lastName>=Nagel) searches all objects alphabetically where the lastName property is equal to or larger than Nagel.

Expressions can be combined with the & and | prefix operators. For example, (&(objectClass=user)(description=Auth*)) searches all objects of type user where the property description starts with the string Auth. Because the & and | operators are at the beginning of the expressions, it’s possible to combine more than two expressions with a single prefix operator.

The default filter is (objectClass=*) so all objects are valid.

Tip 

The filter syntax is defined in RFC 2254, “The String Representation of LDAP Search Filters.” You can find this RFC at www.ietf.org/rfc/rfc2254.txt.

PropertiesToLoad

With PropertiesToLoad, you can define a StringCollection of all the properties that you are interested in. Objects can have a lot of properties, most of which will not be important for your search request. You define the properties that should be loaded into the cache. The default properties that are returned if nothing is specified are the path and the name of the object.

SearchScope

SearchScope is an enumeration that defines how deep the search should extend:

  • SearchScope.Base searches only the attributes in the object where the search started, so at most one object is found.

  • With SearchScope.OneLevel the search continues in the child collection of the base object. The base object itself is not searched for a hit.

  • SearchScope.Subtree defines that the search should go down the complete tree.

The default value of the SearchScope property is SearchScope.Subtree.

Search Limits

A search for specific objects in a directory service can span multiple domains. To limit the search to the number of objects or the time taken, you have some additional properties to define, as shown in the following table.

Open table as spreadsheet

Property

Description

ClientTimeout

The maximum time the client waits for the server to return a result. If the server does not respond, no records are returned.

PageSize

With a paged search the server returns a number of objects defined with the PageSize instead of the complete result. This reduces the time for the client to get a first answer and the memory needed. The server sends a cookie to the client, which is sent back to the server with the next search request so that the search can continue at the point where it finished.

ServerPageTimeLimit

For paged searches, this value defines the time a search should continue to return a number of objects that are defined with the PageSize value. If the time is reached before the PageSize value, the objects that were found up to that point are returned to the client. The default value is –1, which means infinite.

ServerTimeLimit

Defines the maximum time the server will search for objects. When this time is reached, all objects that are found up to this point are returned to the client. The default is 120 seconds, and you cannot set the search to a higher value.

ReferalChasing

A search can cross multiple domains. If the root that’s specified with SearchRoot is a parent domain or no root was specified, the search can continue to child domains. With this property you can specify if the search should continue on different servers.

ReferalChasingOption.None means that the search does not continue on other servers.

The value ReferalChasingOption.Subordinate specifies that the search should go on to child domains. When the search starts at DC=Wrox, DC=com the server can return a result set and the referral to DC=France, DC=Wrox, DC=COM. The client can continue the search in the subdomain.

ReferalChasingOption.External means that the server can refer the client to an independent server that is not in the subdomain. This is the default option.

With ReferalChasingOption.All, both external and subordinate referrals are returned.

Tombstone

If the property Tombstone is set to true, all deleted objects that match the search are returned, too.

VirtualListView

If large results are expected with the search, the property VirtualListView can be used to define a subset that should be returned from the search. The subset is defined with the class DirectoryVirtualListview.

In the search example, all user objects with a property description value of Author are searched in the organizational unit thinktecture.

First, bind to the organizational unit thinktecture. This is where the search should start. Create a DirectorySearcher object where the SearchRoot is set. The filter is defined as (&(objectClass=user)(description=Auth*)), so that the search spans all objects of type user with a description of Auth followed by something else. The scope of the search should be a subtree so that child organizational units within thinktecture are searched, too:

  using (DirectoryEntry de =    new DirectoryEntry("LDAP://OU=thinktecture, DC=explorer, DC=local")) using (DirectorySearcher searcher = new DirectorySearcher()) {    searcher.SearchRoot = de;    searcher.Filter = "(&(objectClass=user)(description=Auth*))";    searcher.SearchScope = SearchScope.Subtree; 

The properties that should be in the result of the search are name, description, givenName, and wWWHomePage:

  searcher.PropertiesToLoad.Add("name"); searcher.PropertiesToLoad.Add("description"); searcher.PropertiesToLoad.Add("givenName"); searcher.PropertiesToLoad.Add("wWWHomePage"); 

You are ready to do the search. However, the result should also be sorted. DirectorySearcher has a Sort property, where you can set a SortOption. The first argument in the constructor of the SortOption class defines the property that will be used for a sort; the second argument defines the direction of the sort. The SortDirection enumeration has Ascending and Descending values.

To start the search, you can use the FindOne() method to find the first object, or FindAll(). FindOne() returns a simple SearchResult, whereas FindAll() returns a SearchResultCollection. Here, all authors should be returned, so FindAll() is used:

  searcher.Sort = new SortOption("givenName", SortDirection.Ascending); SearchResultCollection results = searcher.FindAll(); 

With a foreach loop every SearchResult in the SearchResultCollection is accessed. A SearchResult represents a single object in the search cache. The Properties property returns a ResultPropertyCollection, where you access all properties and values with the property name and the indexer:

    SearchResultCollection results = searcher.FindAll();           foreach (SearchResult result in results)       {          ResultPropertyCollection props = result.Properties;          foreach (string propName in props.PropertyNames)          {             Console.Write(propName + ": ");             Console.WriteLine(props[propName][0]);          }          Console.WriteLine();       }    } 

It is also possible to get the complete object after a search: SearchResult has a GetDirectoryEntry() method that returns the corresponding DirectoryEntry of the found object.

The resulting output shows the beginning of the list of all thinktecture associates with the properties that have been chosen (see Figure 42-14).

image from book
Figure 42-14




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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