24.4  Active Directory编程

要开发Active Directory程序,必须导入System.DirectoryServices命名空间。还必须引用System.DirectoryServices程序集。使用这个程序集中的类可以查询对象、查看和更新属性,搜索对象,把对象移动到其他容器对象中等。在下面的代码段中,简单的C#控制台应用程序说明了如何使用System.DirectoryServices命名空间中的类。

本节将介绍:

       System.DirectoryServices命名空间中的类

       连接Active Directory的处理方式:绑定

       获取目录项,创建新对象,并更新已有的项目

       搜索Active Directory

24.4.1  System.DirectoryServices命名空间中的类

24-1列出了System.DirectoryServices命名空间中的主要类。

  24-1

 

DirectoryEntry

这个类是System.DirectoryServices命名空间中的主类。这个类的对象表示Active Directory库中的一个对象。使用这个类可以绑定对象,查看和更新属性。对象的属性都放在PropertyCollection中。PropertyCollection中的每个元素都有一个PropertyValueCollection

DirectoryEntries

DirectoryEntriesDirectoryEntry对象的一个集合。DirectoryEntry对象的Children属性返回DirectoryEntries集合中的一个对象列表

DirectorySearcher

这个类主要用于用指定的属性搜索对象。要定义该搜索,可以使用SortOption类和枚举SearchScopeSortDirection ReferalChasingOption。搜索的结果是一个SearchResultSearchResultCollection。也可以得到ResultPropertyCollection ResultPropertyValueCollection对象

24.4.2  绑定

要获得Active Directory中一个对象的值,必须连接Active Directory服务。这个连接过程称为绑定,绑定路径如下所示。

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

在绑定过程中,可以指定下述内容:

       指定提供程序使用的协议(protocol)

       域控制器的服务器名(server name)

       服务器过程的端口号(port number)

       对象的显名(distingunshed name),以标识要访问的对象。

       如果需要访问Active Directory的用户不是运行当前进程的账户,则提供用户名和密码。

       如果需要加密,应指定authentication类型

下面详细介绍这些内容。

1. 协议

绑定路径的第一部分指定ADSI提供程序。该提供程序是一个COM服务器;prog-id的标识信息在注册表的HKEY_CLASSES_ROOT下。Windows XP附带的提供程序如表24-2所示。

2. 服务器名

在绑定路径中,服务器名在协议的后面。如果用户登录到Active Directory域上,服务器名就是可选的。如果不提供服务器名,就会发生无服务器绑定操作,此时Windows Server 2003会在域中查找与用户绑定过程相关的、“最好的”域控制器。如果站点中没有服务器,就使用查找到的第一个域控制器。

无服务器的绑定如下所示:

LDAP://OU=SalesDC=AthenaProjectDC=Local

  24-2

   

   

LDAP

LDAP服务器,例如Exchange目录和Windows 2000 ServerWindows Server 2003 Active Directory服务器

GC

GC用于访问Active Directory中的全局目录。它也可以用于快速查询

IIS

使用IISADSI提供程序,可以在IIS目录中创建和管理新网站

WinNT

要访问旧Windows NT 4域的用户数据库,可以使用WinNT ADSI提供程序。NT4用户只有几个属性没有改变。也可以使用这个协议绑定Windows 2000域,但这里也限制了可用于NT4的属性

NDS

这个progID用于和Novell Directory Services通信

NWCOMPAT

使用NWCOMPAT可以访问旧的Novell目录,例如Novell Netware 3.x

3. 端口号

在服务器名的后面,可以指定服务器过程的端口号,其语法是:xxxLDAP服务器的默认端口号是端口389: LDAP://dc01.globalknowledge.net:389Exchange服务器使用的端口号与LDAP服务器一样。如果在同一个系统上安装了Exchange服务器,例如用作Active Directory的域控制器,就可以配置另一个端口。

4. 显名

在路径中指定的第四部分是显名(distinguished nameDN)。显名是一个惟一的名称,标识要访问的对象。在Active Directory中,可以使用基于X.500LDAP语法,指定对象的名称。

例如有一个显名:

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

这个显名指定域AthenaProject.local中域组件(Domain ComponentDC) AthenaProject的组织单元(Organizational UnitOU) Consultants的公共名称(Common NameCN) Christian Nagel。最右边的部分是域的根对象。该名称必须符合对象树中的分层方式。

显名的字符串表示的LDAP规范在 RFC 2253( http://www.ietf.org/rfc/rfc2253.txt)上。

(1) 相对显名

相对显名(RDN)用于引用容器对象中的对象。使用RDN时,不需要指定OUDC,有一个公共名称就足够了。CN=Christian Nagel就是组织单元中的一个相对显名。如果已经引用了一个容器对象,要访问其子对象,就可以使用相对显名。

(2) 默认的命名环境

如果在路径中没有指定显名,绑定过程就会使用默认的命名环境(default naming context)。使用rootDSE可以读取默认命名环境。LDAP 3.0rootDSE定义为目录服务器中目录树的根。例如

LDAP://rootDSE

或:

LDAP://servername/rootDSE

通过列举rootDSE的所有属性,将获得没有指定名称时可以使用的defaultNamingContext信息。schemaNamingContext configurationNamingContext指定了用于访问模式所需要的名称和Active Directory库中的配置。

通过下面的代码可获得rootDSE的所有属性:

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP://platinum/rootDSE";

   de.Username = @" platinum"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);

      }

   }

}

这个程序显示了默认的命名环境(defaultNamingContext DC=eichkogelstrasse DC=local),用于访问模式的环境(CN=Schema CN=Configuration DC=eichkogelstrasse DC=local)和配置的命名环境(CN=Configuration DC=eichkogelstrasse DC=local),如图24-9所示。

  24-9

(3) 对象标识符

每个对象都有一个全局惟一的标识符GUIDGUID是一个惟一的128位数字,您可能已经在COM开发中了解了它。可以使用GUID绑定一个对象。这样,即使对象移动到另一个容器中,也可以得到该对象。GUID在创建对象时生成,且总是保持不变。

使用DirectoryEntry.NativeGuid可以得到GUID的字符串表示。这个字符串表示就可以用于绑定对象。

下面的示例显示了一个无服务器绑定的路径名称,它绑定到GUID代表的一个特定对象上:

LDAP://<GUID=14abbd652aae1a47abc60782dcfc78ea>

 (4)  Windows NT域中的对象名

WinNT提供程序不允许在绑定字符串的名称部分使用LDAP语法。在这个提供程序中,对象用ObjectNameClassName指定。Windows NT域的有效绑定字符串如下:

WinNT:

WinNT://DomainName

WinNT://DomainName/UserName, user

WinNT://DomainName/ServerName/MyGroup, group

usergroup后缀指定可以访问类型为usergroup的对象。

5. 用户名

有时,在访问目录时,必须使用一个非当前进程的用户名(也许这个用户没有访问Active Directory所必须的许可),此时必须为绑定过程显式指定用户证书(user credential)Active Directory提供了许多方式来设置用户名。

(1) Downlevel登录

使用downlevel登录,用户名可以用Windows 2000以前的域名来指定:

domain"username

(2) 显名

也可以用user对象的显名来指定用户,例如:

CN=Administrator CN=UsersDC=athenaprojectDC=local

(3)  User Principal Name (UPN)

对象的UPNuserPrincipalName属性来定义。系统管理员可以在Active Directory Users and Computers工具中User属性的Account选项卡上,用登录信息来指定UPN,注意这不是用户的电子邮件地址。

这些信息也惟一地标识了用户,可以用于登录:

Nagel@ athenaproject.local

6. 身份验证

为了给身份验证进行安全的加密,也可以指定身份验证(authentication)类型。身份验证可以用DirectoryEntry类的AuthenticationType属性设置。可以指定其值为AuthenticationTypes枚举中的一个值。因为枚举是用属性[Flags]标识的,所以可以指定多个值。其可能的值有:对发送的数据进行加密的ReadonlyServer,它指定只需要读取访问,Secure表示安全的身份验证。

7. DirectoryEntry类绑定

System.DirectoryServices.DirectoryEntry类可以用于指定所有的绑定信息。可以使用默认的构造函数,用PathUsernamePassword AuthenticationType属性定义绑定信息,或者把这些信息传递给构造函数:

DirectoryEntry de = new DirectoryEntry();

de.Path = "LDAP://platinum/DC=athenaproject, DC=local";

de.Username = "nagel@ athenaproject.local";

de.Password = "password";

 

// use the current user credentials

DirectoryEntry de2 = new DirectoryEntry(

                            "LDAP://DC= athenaproject, DC=local");

即使成功地构造了DirectoryEntry对象,也并不意味着绑定成功了。在第一次读取属性时进行绑定,可以避免不必要的网络流通量。对象是否存在,或者指定的用户证书是否正确,都可以在第一次访问该对象时确定。

24.4.3  获取目录项

前面介绍了如何指定Active Directory中对象的绑定属性,下面要读取对象的属性。在下面的示例中要读取用户对象的属性。

DirectoryEntry类的一些属性可以提供对象的信息,即NameGuid SchemaClassName属性。第一次访问DirectoryEntry对象的属性时,会执行绑定操作,并填充底层ADSI对象的缓存。后面将详细讨论这些。其他属性可以从缓存中读取,同一对象的数据不需要通过与服务器的通信来获得。

在本例中,用组织单元Wrox Press中的公共名称Christian Nagel访问一个用户对象:

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP://platinum/CN=Christian Nagel, " +

             "OU=Wrox Press, DC=athenaproject, DC=local";

 

   Console.WriteLine("Name: " + de.Name);

   Console.WriteLine("GUID: " + de.Guid);

   Console.WriteLine("Type: " + de.SchemaClassName);

   Console.WriteLine();

    //...

}

Active Directory对象包含许多信息,这些信息取决于对象的类型。属性Properties将返回一个PropertyCollection。每个属性本身就是一个集合,因为一个属性可以有多个值,例如,user对象可以有多个电话号码。在本例中,用一个内部foreach循环查看这些值。从properties[name]返回的集合是一个object数组。属性值可以是字符串、数字或其他类型。使用ToString()方法就可以显示这些值:

   Console.WriteLine("Properties: ");

   PropertyCollection properties = de.Properties;

   foreach (string name in properties.PropertyNames)

   {

      foreach (object o in properties[name])

      {

         Console.WriteLine(name + ": " + o.ToString());

      }

   }

在得到的结果中,包含了user对象Christian Nagel的所有属性,如图24-10所示。OtherTelephone是一个多值属性,它包含许多电话号码。一些属性值只显示System._ComObject对象的类型,例如,lastLogofflastLogonnTSecurityDescriptor。要得到这些属性的值,必须直接使用System.DirectoryServices命名空间的类中的ADSI COM接口。

  24-10

注意:

28章介绍了如何使用COM对象和接口。

2. 直接通过名称访问属性

使用 DirectoryEntry.Properties可以访问所有属性。如果已知属性名,就可以直接访问其值:

foreach (string homePage in de.Properties["wWWHomePage"])

   Console.WriteLine("Home page: " + homePage);

24.4.4  对象集

对象在Active Directory中是分层存储的。容器对象包含子对象。使用DirectoryEntry类的Children属性可以枚举容器中的子对象。另一方面,使用Parent属性可以得到对象的容器。

用户对象没有子对象,所以下面的示例使用一个组织单元,如图24-11所示。非容器对象用Children属性返回一个空集合。下面在域athenaproject.local的组织单元Wrox Press中获得其所有的用户对象。Children属性返回一个DirectoryEntries集合,其中包含DirectoryEntry对象。迭代所有的DirectoryEntry对象,显示子对象的名称。

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP://platinum/OU=Wrox Press, " +

             "DC=athenaproject, DC=local";

 

   Console.WriteLine("Children of " + de.Name);

   foreach (DirectoryEntry obj in de.Children)

   {

      Console.WriteLine(obj.Name);

   }

}

  24-11

本例显示了组织单元中的所有对象:userscontactsprintersshares和其他组织单元。如果只需要查看某些对象类型,可以使用DirectoryEntries类的SchemaFilter性。SchemaFilter属性返回一个SchemaNameCollection。有了这个SchemaNameCollection,就可以使用Add()法定义要查看的对象类型。在本例中,因为只需要查看user对象,所以把user添加到这个集合中:

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP:// platinum /OU=Wrox Press, " +

             "DC= athenaproject, DC=local";

 

   Console.WriteLine("Children of " + de.Name);

   de.Children.SchemaFilter.Add("user");

   foreach (DirectoryEntry obj in de.Children)

   {

      Console.WriteLine(obj.Name);

   }

}

结果,只显示组织单元中的user对象,如图24-12所示。

24.4.5  缓存

要减少网络流通量,ADSI使用缓存来存储对象属性。如前所述,在创建DirectoryEntry对象时,是不访问服务器的。只要从目录库中读取第一个属性,所有的属性都会写到缓存中,这样,在读取下一个属性时,就不需要往返于服务器了。

写入对象的改变,只会改变已缓存的对象。设置属性不会产生网络流通量。必须使用DirectoryEntry.CommitChanges()才能刷新缓存,把所有已改变的数据传送回服务器。要再次从目录库中获取新写入的数据,可以使用DirectoryEntry.RefreshCache()读取属性。当然,如果没有调用CommitChanges() RefreshCache()就修改了一些属性,所有的改变都会丢失,因为我们将再次使用RefreshCache()读取目录服务中的值。

把属性DirectoryEntry.UsePropertyCache设置为false,就可以关闭这个属性cache。但除非在进行调试,否则最好不要关闭它,因为这会与服务器间产生许多不必要的往返通信。

24.4.6  创建新对象

创建新Active Directory对象时,例如userscomputersprinterscontacts等,可以使用DirectoryEntries类以编程的方式来完成创建工作。

要给目录添加新对象,首先必须绑定一个容器对象,例如组织单元,可以在其中插入一个新对象。不包含其他对象的对象是不能使用的。下面使用一个容器对象,其显名为CN=UsersDC= athenaprojectDC=local

DirectoryEntry de = new DirectoryEntry();

de.Path = "LDAP://platinum/CN=Users, DC= athenaproject, DC=local";

使用DirectoryEntryChildren属性,可以得到一个DirectoryEntries对象:

DirectoryEntries users = de.Children;

使用DirectoryEntries,可以添加、删除和查找集合中的对象。下面创建一个新的user对象。使用Add()方法时,需要一个对象名和一个类型名。使用ADSI Edit很容易获得类型名:

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

对象现在有了默认属性值。要指定特定的属性值,可以使用Properties属性的Add()方法添加属性。当然,所有的属性都必须存在于user对象的模式中。如果指定的属性不存在,就得到一个COMException“指定的目录服务属性或值不存在”:

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@ athenaproject.local");

user.Properties["givenName"].Add("John");

user.Properties["sn"].Add("Doe");

user.Properties["userPassword"].Add("someSecret");

此时,为了把数据写入Active Directory,必须刷新缓存中的数据:

user.CommitChanges();

24.4.7  更新目录项

Active Directory服务中对象的更新和读取一样简单。可以在读取对象后修改它们的值。要删除一个属性的所有值,可以调用PropertyValueCollection.Clear()方法。使用Add(),可以把新值添加到属性中。Remove()RemoveAt()可以从属性集合中删除指定的值。

要修改一个值,可以把这个值设置为指定的值。下面的代码将通过PropertyValueCollection的一个索引符把移动电话号码设置为一个新值。使用该索引符,只能改变已存在的值。因此,应使用DirectoryEntry.Properties.Contains()来确定属性是否可用。

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP://platinum /CN=Christian Nagel, " +

             "OU=Wrox Press, DC= athenaproject, DC=local";

   if (de.Properties.Contains("mobile"))

   {

      de.Properties["mobile"][0] = "+43(664)3434343434";

   }

   else

   {

      de.Properties["mobile"].Add("+43(664)3434343434");

   }

   de.CommitChanges();

}

在本例的else部分,如果移动电话号码不存在新属性,就用方法PropertyValue Collection.Add()添加它。如果使用Add()方法和已有的属性,得到的结果将取决于属性的类型:单值属性或多值属性。使用Add()方法和已有的单值属性,会得到一个COMExceptionA constraint violation occurred。使用Add()方法和已有的多值属性则会成功地把另一个值添加到属性中。

User对象的属性mobile定义为单值属性,所以不能添加其他移动电话的号码。但是用户可以有多个移动电话的号码。对于多个移动电话的号码,可以使用属性otherMobile,它是一个多值属性,允许设置多个移动电话号码,所以可以调用Add()多次。多值属性有一个重要的检查:即检查其唯一性。如果把第二个电话号码添加到同一个user对象上,也会得到一个COMException:指定的目录服务属性或值已经存在。

提示:

在创建或更新新的目录对象后,应调用DirectoryEntry.CommitChanges()。否则只能更新缓存中的信息,改变的信息不会发送到目录服务上。

24.4.8  访问内部的ADSI对象

调用预定义的ADSI接口方法常常比搜索对象的属性名容易得多。一些ADSI对象还支持不能在DirectoryEntry类中直接使用的方法。例如IADsServiceOperations接口有启动和停止Windows服务的方法。Windows服务将在第32章讨论。

如本章前面所述,System.DirectoryServices命名空间的类使用底层的ADSI COM对象。DirectoryEntry支持直接使用Invoke()方法调用底层对象的方法。

Invoke()的第一个参数是应在ADSI对象中调用的方法名,第二个参数的params关键字允许把数量可变的其他参数传送给ADSI方法:

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

ADSI文档中介绍了可以用Invoke()方法调用的方法。域中的每个对象都支持IADS接口的方法。前面创建的User对象也支持IADsUser接口的方法。

在下面的代码示例中,使用方法IADsUser.SetPassword()改变前面创建的user对象的密码:

using (DirectoryEntry de = new DirectoryEntry())

{

   de.Path = "LDAP://platinum /CN=John Doe, " +

             "CN=Users, DC= athenaproject, DC=local";

 

   de.Invoke("SetPassword", "anotherSecret");

   de.CommitChanges();

}

如果不使用Invoke(),还可以直接使用底层的ADSI对象。要使用这些对象,必须使用Project | Add Reference添加对Active DS Type Library的引用,如图24-13所示。这会创建一个包装器类,在该类中可以访问ActiveDS命名空间中的这些对象。

  24-13

内部对象可以使用DirectoryEntry类的NativeObject属性来访问。在下面的示例中,对象de是一个user对象,所以可以把它强制转换为ActiveDs.IADsUserSetPassword()是在IADsUser接口中说明的方法,所以可以直接调用它,而不是使用Invoke()方法来调用。把IADsUserAccountDisabled属性设置为false,就可以激活账户。与前面的示例一样,调用CommitChanges() DirectoryEntry对象,把改变的内容写到目录服务中:

ActiveDs.IADsUser user = (ActiveDs.IADsUser)de.NativeObject;

user.SetPassword("someSecret");

user.AccountDisabled = false;

de.CommitChanges();

24.4.9  Active Directory中搜索

Active Directory是一个数据库,它为大多数读取访问进行了优化,所以一般应使用搜索一些值。要在Active Directory中搜索,可以使用.NET Framework中的类DirectorySearcher

注意:

DirectorySearcher只能和LDAP提供程序一起使用。它不能和NDSIIS提供程序一起使用。

 

在类DirectorySearcher的构造函数中,可以定义搜索的4个重要部分。也可以使用默认构造函数,用属性定义搜索选项。

1. SearchRoot

SearchRoot指定搜索应从什么地方开始。SearchRoot的默认值是当前使用的域的根。SearchRootDirectoryEntry对象的Path指定。

2. 过滤器

过滤器定义了希望有的值。过滤器是一个必须放在括号中的字符串。

表达式中可以使用关系运算符,如<= = >=(objectClass=contact)会搜索所有类型为contact的对象,(lastName>=Nagel) 会按字母表的顺序搜索lastName属性等于或大于Nagel的所有对象。

表达式可以和前缀运算符 & |组合使用。例如,(&(objectClass=user)(description=Auth*))搜索类型为User,其属性description以字符串Auth开头的所有对象。因为 & | 运算符在表达式的开头,所以可以把多个表达式用一个前缀运算符组合在一起。

默认过滤器是(objectClass=*),所以将选择所有的对象。

注意:

过滤器语法在RFC 2254中定义为“LDAP搜索过滤器的字符串表示”。这个RFChttp://www.ietf.org/rfc/rfc2254.txt中。

3. PropertiesToLoad

使用PropertiesToLoad,可以定义所有属性的StringCollection。对象可以有许多属性,其中的大多数对于搜索请求都不太重要。我们定义了应加载到缓存中的属性。如果没有指定属性,默认属性就应是对象的Path Name属性。

4. SearchScope

SearchScope 是一个枚举,定义了搜索应延伸的深度:

       SearchScope.Base只搜索对象中的属性,至多可以得到一个对象。

       SearchScope.OneLevel表示在基对象的子集合中继续搜索。基对象本身是不搜索的。

       SearchScope.Subtree定义了在整个树中搜索。

SearchScope属性的默认值是SearchScope.Subtree

5. 搜索的限制

在目录服务中搜索特定的对象可以在几个域中进行。对搜索要限制对象的个数或搜索时间,可以定义其他几个属性(如表24-3所示)

   24-3

 

   

ClientTimeout

客户机等待服务器返回结果的最长时间。如果服务器没有响应,就不返回记录

PageSize

使用 paged search,服务器会返回用PageSize 定义的许多对象,而不是所有的对象。这会减少客户获得第一个答案的时间和需要的内存,服务器把一个cookie送给客户机,客户机再把下一个搜索请求发送回服务器,这样搜索就可以在上一次结束的地方继续进行

ServerPageTimeLimit

对于paged search,这个值定义了一个搜索时间,在该时间内返回用PageSize 值定义的许多对象。如果这段时间在PageSize值之前到达,就会把此时查找到的对象返回给客户机。默认值为-1,表示时间为无限

ServerTimeLimit

定义服务器搜索对象的最长时间。过了这段时间后,就会把此时查找到的所有对象返回给客户机。默认值是120秒,不能把搜索时间设置为较高的值

ReferalChasing

搜索可以在几个域中进行。如果用SearchRoot 指定的根是一个父域或没有指定根,就会继续在子域中搜索。使用这个属性可以指定是否应在不同的服务器上继续搜索

ReferalChasingOption.None表示不能在不同的服务器上继续搜索

ReferalChasingOption.Subordinate指定继续在子域中搜索,搜索从DC=WroxDC=COM开始时,服务器可以返回一个结果集,例如DC=FranceDC=WroxDC=COM引用。客户机可以继续在子域中搜索

ReferalChasingOption.External表示服务器可以让客户机搜索不在子域中的另一个服务器,这是默认选项

ReferalChasingOption.All表示返回外部和从属的引用

 

在搜索示例中,要搜索组织单元Wrox Pressdescription属性值为Author的所有user     对象。

首先,绑定组织单元Wrox Press,在该组织单元中开始搜索。创建一个DirectorySearcher对象,在其中设置SearchRoot。过滤器定义为(&(objectClass=user) (description=Auth*)),这样就可以搜索所有类型为Userdescription属性以Auth开头的对象。搜索的范围应在一个子树中,即在Wrox Press的子组织单元中搜索:

using (DirectoryEntry de =

   new DirectoryEntry("LDAP://OU=Wrox Press, DC= athenaproject, DC=local"))

using (DirectorySearcher searcher = new DirectorySearcher())

{     

   searcher.SearchRoot = de;

   searcher.Filter = "(&(objectClass=user)(description=Auth*))";

   searcher.SearchScope = SearchScope.Subtree;

要在搜索结果中包含的属性有namedescriptiongivenNameWWWHomePage

   searcher.PropertiesToLoad.Add("name");

   searcher.PropertiesToLoad.Add("description");

   searcher.PropertiesToLoad.Add("givenName");

   searcher.PropertiesToLoad.Add("wWWHomePage");

下面准备开始搜索。还应对结果进行排序。DirectorySearcher有一个属性Sort,它可以设置SortOptionSortOption构造函数的第一个参数定义了要排序的属性,第二个参数定义了排序的方向。SortDirection枚举的值是Ascending Descending

要开始搜索,可以使用FindOne()方法查找第一个对象,或者使用FindAll()FindOne()返回一个简单的SearchResultFindAll()返回一个SearchResultCollection。要得到所有的作者对象,就应使用FindAll()

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

    SearchResultCollection results = searcher.FindAll();

使用一个foreach循环,可以访问SearchResultCollection中的每个SearchResult。一个SearchResult表示搜索缓存中的一个对象。Properties属性返回一个ResultPropertyCollection,使用属性名和索引符可以访问该集合中所有的属性:

   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();

   }

}

可以在搜索之后获得完整的对象:SearchResult有一个方法GetDirectoryEntry(),它返回查找到的对象的相应DirectoryEntry

得到的结果应显示《Professional C#(即《C#高级编程》)的作者列表,这些作者都有我们指定的属性,如图24-14所示。

24.5  搜索用户对象

本章的最后一节要创建一个Windows Forms应用程序UserSearch。这个应用程序是非常灵活的,因为可以输入特定的域控制器、用户名和密码,以访问Active Directory,或者使用运行进程的用户。在这个应用程序中,我们将访问Active Directory服务的模式,获得user对象的属性。该用户可以输入一个过滤器字符串,搜索域中的所有user对象。还可以设置应显示的user对象的属性。

24.5.1  用户界面

用户界面显示了一些已编好序号的步骤,说明如何使用该应用程序(如图24-15所示)

  24-15

       在第一步中,输入用户名、密码和域控制器。所有这些信息都是可选的。如果没有输入域控制器,就要使用无服务器绑定进行连接。如果没有输入用户名,就使用当前用户的安全环境。

       使用一个按钮,就可以把User对象的所有属性名动态加载到listBoxProperties 列表   框中。

       在加载了属性名后,就可以选择要显示的属性。列表框的SelectionMode设置为MultiSimple

       可以输入限制搜索的过滤器,在该对话框中设置的默认值是搜索所有的user对象:(objectClass=user)

       现在开始搜索。

24.5.2  获取模式命名环境

这个应用程序只有两个处理程序方法:按钮的第一个处理程序方法加载属性,第二个处理程序方法则在域中开始搜索操作。在第一部分,从模式中动态读取User类的属性,在用户界面中显示它。

在处理程序方法buttonLoadProperties_Click()中,使用SetLogonInformation()从对话框中读取用户名、密码和主机名,并存储在类的成员中。接着,SetNamingContext()方法设置模式的LDAP名称和默认环境的LDAP名称。这个模式LDAP名称用于调用中,以设置列表框中的属性SetUserProperties()

private void buttonLoadProperties_Click(object sender, System.EventArgs e)

{

   try

   {

      SetLogonInformation();

      SetNamingContext();

 

      SetUserProperties(schemaNamingContext);

   }

   catch (Exception ex)

   {

      MessageBox.Show("Check your inputs! " + ex.Message);

   }

}

protected void SetLogonInformation()

{

   username = (textBoxUsername.Text == "" ? null : textBoxUsername.Text);

   password = (textBoxPassword.Text == "" ? null : textBoxPassword.Text);

   hostname = textBoxHostname.Text;

   if (hostname != "") hostname += "/";

}

在帮助方法SetNamingContext()中,使用目录树的根来获得服务器的属性。这里只考虑两个属性的值:schemaNamingContext defaultNamingContext

 

protected string SetNamingContext()

{

   using (DirectoryEntry de = new DirectoryEntry())

   {

      string path = "LDAP://" + hostname + "rootDSE";

      de.Username = username;

      de.Password = password;

      de.Path = path;

      schemaNamingContext = de.Properties["schemaNamingContext"][0].ToString();

      defaultNamingContext =

                        de.Properties["defaultNamingContext"][0].ToString();

   }

}

24.5.3  获取User类的属性名

使用LDAP名称可以访问模式。使用它可以访问目录,并读取属性。我们不仅要介绍User类的属性,还将介绍User的基类:Organizational-PersonPersonTop。在这个程序中,基类的名称是硬编码的。还可以使用subClassOf属性动态读取基类。GetSchemaProperties()返回一个字符串数组和指定对象类型的所有属性名。这些属性名都在StringCollection属性中:

protected void SetUserProperties(string schemaNamingContext)

{

   StringCollection properties = new StringCollection();

   string[] data = GetSchemaProperties(schemaNamingContext, "User");

   properties.AddRange(GetSchemaProperties(schemaNamingContext,

                       "Organizational-Person"));

   properties.AddRange(GetSchemaProperties(schemaNamingContext, "Person"));

   properties.AddRange(GetSchemaProperties(schemaNamingContext, "Top"));

   listBoxProperties.Items.Clear();

   foreach (string s in properties)

   {

      listBoxProperties.Items.Add(s);

   }

}

GetSchemaProperties()中,再次访问Active Directory服务。这次不使用rootDSE,而使用前面介绍的模式的LDAP名称。属性systemMayContain包含objectType类中的所有属性的一个集合:

protected string[] GetSchemaProperties(string schemaNamingContext,

                                       string objectType)

{

   string[] data;

   using (DirectoryEntry de = new DirectoryEntry())

   {

      de.Username = username;

      de.Password = password;

 

      de.Path = "LDAP://" + hostname + "CN=" + objectType + "," +

                schemaNamingContext;

 

      DS.PropertyCollection properties = de.Properties;

      DS.PropertyValueCollection values = properties["systemMayContain"];

 

      data = new String[values.Count];

      values.CopyTo(data, 0);

   }

   return data;

}

注意上述代码中的DS.PropertyCollection这是因为在Windows Forms应用程序中,System.DirectoryServices命名空间中的PropertyCollection类与System.Data.PropertyCollection的名称相冲突。为了避免使用像System.DirectoryServices.PropertyCollection这么长的名称,可以使用下面的代码来缩短命名空间的名称:

using DS = System.DirectoryServices;

这样就完成了应用程序的第二步。Listbox控件包含User对象的所有属性名。

24.5.4  搜索用户对象

搜索按钮的处理程序只调用帮助方法FillResult()

private void buttonSearch_Click(object sender, System.EventArgs e)

{

   try

   {

      FillResult();

   }

   catch (Exception ex)

   {

      MessageBox.Show("Check your input: " + ex.Message);

   }

}

FillResult()中,在整个Active Directory 域中进行与前面一样的正常搜索。SearchScope设置为Subtree Filter设置为TextBox中的字符串,应加载到缓存中的属性由用户在列表框中选择的值来设置。方法GetProperties()用于把属性数组传送给方法searcherPropertiesTo Load.AddRange()是一个帮助方法,它从列表框中把选中的属性度到一个数组中。在设置了DirectorySearcher对象的属性后,就调用SearchAll()方法搜索属性。SearchResultCollection中的搜索结果用于生成写到文本框textBoxResults中的汇总信息。

protected void FillResult()

{

   using (DirectoryEntry root = new DirectoryEntry())

   {

      root.Username = username;

      root.Password = password;

      root.Path = "LDAP://" + hostname + defaultNamingContext;

 

      using (DirectorySearcher searcher = new DirectorySearcher())

      {

         searcher.SearchRoot = root;

         searcher.SearchScope = SearchScope.Subtree;

         searcher.Filter = textBoxFilter.Text;

         searcher.PropertiesToLoad.AddRange(GetProperties());

 

         SearchResultCollection results = searcher.FindAll();

         StringBuilder summary = new StringBuilder();

         foreach (SearchResult result in results)

         {

            foreach (string propName in

            result.Properties.PropertyNames)

            {

               foreach (string s in result.Properties[propName])

               {

                  summary.Append(" " + propName + ": " + s + ""r"n");

               }

            }

            summary.Append(""r"n");

         }

         textBoxResults.Text = summary.ToString();

      }

   }

}

启动该应用程序,将列出所有由过滤器选择的有效对象,如图24-16所示。

24.6  小结

本章介绍了Active Directory的体系结构,有关域、树和森林的重要概念。利用它,我们可以访问整个企业的信息。在编写访问Active Directory服务的应用程序时,必须注意读取的数据可能不是最新的,因为进行复制操作时有一定的等待时间。

使用System.DirectoryServices命名空间中的类,可以很容易地访问封装到ADSI提供程序中的Active Directory服务。DirectoryEntry类可以直接读写数据库中的对象。

使用DirectorySearcher类可以进行复杂的搜索,定义过滤器、超时、加载的属性和范围等。使用全局目录,可以加快对整个企业中的对象的搜索,因为它在森林中存储了所有对象的只读版本。

 原文地址 http://book.csdn.net/bookfiles/16/100165679.shtml
posted on 2008-10-13 13:29  18楼的大象  阅读(4196)  评论(0编辑  收藏  举报