使用 System.DirectoryServices 搜索 Active Directory
简介
Active Directory 是一种具有特殊用途的数据库,它可以在整个组织范围内复制并轻松扩展,从而更好地存储用户信息、网络配置以及其他在组织范围内进行全局访问所需的数据。它是最强大的操作系统功能之一,如果您的组织拥有 Active Directory,则非常值得在构建自己的应用程序时加以利用。
在本文中,我将详细介绍如何使用 System.DirectoryServices 命名空间来连接 Active Directory、搜索对象并显示搜索结果。当然,System.DirectoryServices 不仅适用于 Active Directory,它还可以用于几种不同的服务,包括普遍使用的 LDAP 协议,但这里主要针对 Active Directory 介绍我的示例。
[@more@]
从 ADSI 转至 System.DirectoryServices
刚开始使用 Active Directory 进行编程的人员可能会对 MSDN 和其他资源上提到的 ADSI 存有疑问。ADSI(即 Active Directory Service Interfaces,Active Directory 服务接口)是一种 COM 库,它使您可以实现非 .NET 语言与 Active Directory 的交互。它是使用 Microsoft® Visual Basic® 6.0、Visual Basic for Applications (VBA) 和脚本语言访问目录信息的最常用方法。因此,许多关于 Active Directory 的示例和新闻组发言都集中在了 ADSI 上。
令人高兴的是,ADSI 中使用的许多概念都可以经过轻松转换用在 System.DirectoryServices 中,例如 ADSI 示例中显示的 ADSI 路径或属性名。此外,还可以在 COM 和 .NET 库之间使用混合代码。System.DirectoryServices 命名空间中有若干方法和属性可以将 ADSI 对象作为参数接收。当然,即使您从未使用过 ADSI,System.DirectoryServices 也是很容易学习和使用的。
建立连接
System.DirectoryServices 命名空间中有两个主要类,即 DirectoryEntry 和 DirectorySearcher。(还有其他几个类,但这两个是首先需要使用的类。)搜索将使用 DirectorySearcher 类(名称与其功能很般配)来执行。使用该类时,可以不设置任何选项,也可以提供一个根 DirectoryEntry 对象。
如果创建了 DirectoryEntry 对象并用作搜索的根,则需要指定路径以说明要连接的服务,还要指定您的安全凭据。创建此单一对象时即建立了指向您的目录的连接。通过提供此 DirectoryEntry 对象作为搜索的根,您便可以使用此连接进行查询。或者,如果不指定根对象,DirectorySearcher 将自动绑定到当前域,并使用您的 Microsoft® Windows® 凭据进行身份验证。
绑定到目录路径
要连接到 Active Directory,您可以使用全局编录 (GC://) 语法来指定路径,也可以使用标准的 LDAP 路径 (LDAP://)。具体使用哪种语法和路径视您的网络环境而定。例如,在我自己的内部网络中,我会使用全局编录语法,因为这使我可以在整个企业网络范围(整个 Active Directory 森林)内进行搜索。我没有使用包含服务器名称的路径,而是仅指定了“GC://dc=home,dc=duncanmackenzie,dc=net”(假设我的域称为 home.duncanmackenzie.net),该路径会告知 ADSI 连接到全局编录服务器的指定域。当然如果您愿意,也可以不指定任何路径来建立连接。如果使用 DirectorySearcher 对象而未提供任何根对象,它将自动使用当前域进行搜索。有关为您的网络确定正确路径的详细信息,
验证目录
指定 LDAP 或 GC 路径后,接下来要处理的是安全凭据。您可以使用 DirectoryEntry 类的构造函数来指定用户 ID 和密码,或者在创建该对象的实例后再设置用户 ID 和密码。请不要在您的代码中存储实际的密码/用户 ID,而应当从用户那里检索此信息,或者最好使用集成身份验证。
Dim rootEntry _
As New DirectoryEntry("GC://dc=home,dc=duncanmackenzie,dc=net", _
userID.Text, _
password.Text)
注意:要使上述代码能够工作,您需要在项目中引用 System.DirectoryServices,并在源文件的顶端添加 Imports System.DirectoryServices (Visual Basic .NET) 或 using System.DirectoryServices; (C#) 代码行。
如果您不指定用户 ID 和密码,System.DirectoryServices 将尝试使用 Windows 集成身份验证进行连接。通常,我倾向于选择集成身份验证,至少在 Microsoft® Windows 窗体应用程序中会使用这种验证,因为这样每个用户可以仅使用各自已有的 Active Directory 权限集。使用硬编码的用户 ID 和密码可能会使我的应用程序赋予用户大于所预期的 Active Directory 访问权限,从而带来安全隐患。
执行搜索
创建初始(根)DirectoryEntry 后,您就可以使用 DirectorySearcher 类来执行查询。执行简单的搜索只需要以下几个步骤:
- 创建 DirectorySearcher 的实例,可以选择使用根 DirectoryEntry 对象。
- 将搜索筛选器设置为描述搜索条件的字符串。
- 使用要为每个搜索结果检索的 Active Directory 属性列表填充 PropertiesToLoad 集合。
- 执行 FindAll 方法并检索结果。
如果需要,您可以跳过几乎所有这些步骤,使用默认设置执行搜索。如果不提供根 DirectoryEntry 对象,DirectorySearcher 将绑定到当前域;如果不提供搜索筛选器,将默认检索所有对象;如果不指定任何 PropertiesToLoad 值,将检索所有属性(您可以读取的属性)。这些默认设置使得 DirectoryServices 类的使用非常简便。不过,我建议您至少使用一个筛选器来限制搜索,并限制加载的属性集,除非您的应用程序确实需要所有属性和所有对象。
Dim searcher As New DirectorySearcher(rootEntry)
searcher.PropertiesToLoad.Add("cn")
searcher.PropertiesToLoad.Add("mail")
' searcher.PropertiesToLoad.AddRange(New String() {"cn", "mail"})
' 将同样有效并节省一些代码
searcher.Filter = "(&(anr=duncan)(objectCategory=person))"
Dim results As SearchResultCollection
results = searcher.FindAll()
附加选项
还有许多附加搜索选项。这些选项不是必需的,但可以显著影响查询的效果。以下是其中一些附加选项及其简要使用说明:
- CacheResults 确定搜索结果是否存储在本地客户端计算机上。使用此设置,可以向前或向后浏览搜索结果,否则只能以“向前”模式浏览结果。
- ClientTimeout、ServerTimeLimit 和 ServerPageTimeLimit 都是用于防止搜索运行时间过长的超时值。第一个值 ClientTimeout 用于控制客户端的等待时间。其他两个时间限制由服务器施加。我建议至少设置 ClientTimeout,以避免您的应用程序出现无限等待的情况。但是,有一点需要注意,即如果到达了某个基于服务器的时间限制,则返回到达该时间点时检索到的条目;如果首先到达了客户端时间限制,则不会返回任何内容。请记住,服务器本身具有可由管理员配置的时间限制,因此在到达您指定的时间限制之前可能会出现超时,从而返回不完整的结果。
- PageSize 确定一次返回的项数。如果不设置 PageSize 属性或将其设置为 0,则一次返回所有结果。不过,使用分页会使您的应用程序看起来更方便一些。请记住,服务器会确定搜索中返回对象的最大数目,以避免用户造成系统负载过重。所以建议您始终使用分页功能,特别是预计会出现很大的结果集时。在本文末尾的演示中,我将向您说明如何与多线程一起使用分页功能,以生成搜索窗体,并且使用户不必等待。
使用结果
执行搜索后,您将得到一个返回的 SearchResultCollection 类的实例,该实例使您可以对结果进行枚举。这时是否使用了分页并不重要,您仍然是以相同的方式在访问搜索结果。如果下一页的结果尚不可用,您的枚举将被锁定,直至结果就绪。
Dim result As SearchResult
For Each result In results
MessageBox.Show(result.Properties("cn")(0))
Next
创建快速搜索演示
经过上述所有步骤,我创建了一个针对 Active Directory 执行搜索的简单应用程序。该示例可以通过本文下载(请单击本文顶部的链接)。如果您希望自己构建,也可以遵照本文进行操作。
为正确配置搜索(包括安全性选项),我必须在所开始的空白 Windows 窗体中设置若干控件,其中包括四个 TextBox 控件(根路径、搜索字符串、用户 ID 和密码)、一个 CheckBox 控件(使我可以在集成身份验证和使用用户 ID 及密码之间进行切换)、一个按钮(用于执行搜索)和一个 ListBox 控件(用于显示结果)。我花了一些时间来安排这些控件,并设置了定位属性以使窗体看起来美观一些,使界面可以调整大小,当然这一步完全是可选的。
图 1:使用最终完成的窗体可以执行简单的搜索
现在,我的按钮的单击事件将接收在四个 TextBox 和 CheckBox 中输入的值,并执行搜索。
Dim rootEntry _
As New DirectoryEntry(rootPath.Text)
If Not integratedAuth.Checked Then
rootEntry.Username = userID.Text
rootEntry.Password = password.Text
End If
Dim searcher As New DirectorySearcher(rootEntry)
searcher.PropertiesToLoad.Add("cn")
searcher.PropertiesToLoad.Add("telephoneNumber")
' searcher.PropertiesToLoad.AddRange(New String() {"cn", "mail"})
' 将同样有效并节省一些代码
searcher.PageSize = 5
searcher.ServerTimeLimit = New TimeSpan(0, 0, 30)
searcher.ClientTimeout = New TimeSpan(0, 10, 0)
searcher.Filter = searchString.Text
Dim queryResults As SearchResultCollection
queryResults = searcher.FindAll()
得到结果后,将它们逐一添加到 ListBox 中。
Dim result As SearchResult
For Each result In queryResults
results.Items.Add(result.Properties("cn")(0))
Next
注意:知道要使用哪些属性以及如何检索这些属性是使用 System.DirectoryServices 时遇到的复杂概念之一。这里有一个很好的 Active Directory 属性名参考资源,至少可供您在处理用户和帐户时使用,即 SDK 参考(英文)。它将属性名映射到 Windows 中用户和组管理单元的信息中。还有一点需要注意,即许多属性可以有多个值,因此属性值可以显示为包含任意多个值的数组。在该示例中,我通过访问属性值数组的第一个元素来检索 cn 属性的值。之所以能够这样做,是因为我碰巧知道 cn 仅包含一个值。当使用多种不同的属性时,您可能需要检查每个属性以确定赋予它的是单个值还是多个值。MSDN 上的 Active Directory Schema 参考为您介绍了这方面内容,它提供了每个属性的详细信息,包括它们的数据类型以及包含的是单个值还是多个值。
完成上述示例后,我们可以从中了解如何针对 Active Directory 执行搜索,但还有两个问题需要解决。第一个问题是,该搜索与我的用户界面(窗体)在同一线程中执行。也就是说,当我的代码等待 DirectoryServices 对象的响应时,我的用户界面将无法响应。第二个问题是,我需要手动向 ListBox 中添加检索结果,而许多编程人员更习惯于使用数据绑定将数据集绑定到用户界面。在本文后面的内容中,我将介绍如何使用后台线程提供具有灵活响应的用户界面,以及如何在运行时创建 DataTable 以便提供数据绑定功能。
将搜索转至后台线程
每当我打算在后台线程中运行任务(所有任务,不只是 DirectoryServices 操作)时,我始终会采用同一个模式。我会先介绍一下这个模式,然后向您展示如何将其应用到目录搜索的特定任务中。
我创建一个类来封装后台任务。这个类包括:
- 用于配置该任务的属性。
- 一个不能接收任何自身参数(这也是我使用属性的原因)的方法,该方法将在新线程中执行。
- 一个或多个触发后用于传递后台任务的进度或结束信息的事件。
注意:如果我在 Windows 窗体中使用此模式,则需要避免因后台任务生成的事件而导致更新窗体本身,并且需要使用窗体的 Invoke 方法在两个线程间正确封送。
将此模式应用于前面创建的搜索示例(在下载代码中创建“DS Background”项目)时,需要添加一个新类 BackgroundSearch。BackgroundSearch 具有若干属性,因此可以适当配置搜索,同时它还有一个 StartSearch 方法。每找到一个结果时,便会生成一个 ResultFound(这一名称同样一目了然)事件。完成整个搜索后,将触发 SearchCompleted 事件。
Imports System
Imports System.DirectoryServices
Public Class BackgroundSearch
Dim m_FilterString As String
Dim m_PageSize As Integer
Dim m_RootPath As String
Dim m_PropertiesToLoad() As String
Dim m_IntegratedAuthentication As Boolean
Dim m_UserID As String
Dim m_Password As String
Public Property IntegratedAuthentication() As Boolean
Get
Return m_IntegratedAuthentication
End Get
Set(ByVal Value As Boolean)
m_IntegratedAuthentication = Value
End Set
End Property
Public Property UserID() As String
Get
Return m_UserID
End Get
Set(ByVal Value As String)
m_UserID = Value
End Set
End Property
Public Property Password() As String
Get
Return m_Password
End Get
Set(ByVal Value As String)
m_Password = Value
End Set
End Property
Public Property PropertiesToLoad() As String()
Get
Return m_PropertiesToLoad
End Get
Set(ByVal Value As String())
m_PropertiesToLoad = Value
End Set
End Property
Public Property FilterString() As String
Get
Return m_FilterString
End Get
Set(ByVal Value As String)
m_FilterString = Value
End Set
End Property
Public Property PageSize() As Integer
Get
Return m_PageSize
End Get
Set(ByVal Value As Integer)
m_PageSize = Value
End Set
End Property
Public Property RootPath() As String
Get
Return m_RootPath
End Get
Set(
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/7186556/viewspace-918146/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/7186556/viewspace-918146/