摘 要 给出了一个利用活动目录节点的属性生成Treeview结构的算法,利用该算法可以在实现活动目录的Treeview遍历。 关键词 C#,活动目录,Treeview ,递归 微软活动目录是管理网络资源的一种有效技术,它使用户、系统管理员和应用程序开发者能以前所未有的方法使用操作系统,进一步模糊各网络之间的界限,提供了一种针对网络资源安全可靠、方便快捷的全新概念的综合管理工具;与此同时,信息化的今天,网络资源的浏览和操作离不开Web技术,它具有信息共享高、易用性、易维护和安全性好的优点,是互联网的主流技术;假如把活动目录和 Web 技术相互结合,必能使网络应用上新台阶。 一、活动目录简介 活动目录(Active Directory)是面向Windows Standard Server、Windows Enterprise Server以及 Windows Datacenter Server的目录服务。 Active Directory存储了有关网络对象的信息,并且让管理员和用户能够轻松地查找和使用这些信息,使用了一种结构化的数据存储方式,并以此作为基础对目录信息进行合乎逻辑的分层组织。 Active Directory支持标准的LDAP(轻型目录访问协议),它是一种工业标准服务协议,在大多数平台和大多数开发语言中都可以进行访问。 活动目录是存储用户信息、打印机、网络服务和定制数据的目录服务,它是一个树型结构,树枝为组织单元(OU),它可以是国家、公司和组织等,而树叶是对象(CN),它可以是组、用户和网络资源等,组织单元可以包括组织单元和树叶;一个对象可有多个属性,属性包括:姓名、电话号码和单位等。组可包括不同组织单元的多个用户,这些都可实现文件和URL授权的判断条件。 二、TreeView控件简介 树形图用于显示按照树形结构进行组织的数据,其用途比较广泛,如计算机中的文件系统(Windows中的资源管理器)、企业或公司的组成结构等。在Windows下VB、PB和Delphi等工具提供了一个功能很强的树型控件TreeView,利用Treeview控件可以方便地开发树形图。然而在网页上实现树形图就不那么容易了,现在在ASP.NET中利用微软提供的Internet Explorer WebControls使得网页上的树形图开发与在Windows下一样的方便,甚至更灵活。 三、访问活动目录 在.NET中,微软为我们提供了System.DirectoryServices命名空间,它使用了活动目录服务接口(ADSI)。 ADSI是通过编程与许多不同的目录服务提供者交互的方式,也就是一种编程接口。 DirectoryEntry 这是 System.DirectoryServices 命名空间中最有用的一个类。它的实例代表活动目录中的对象,DirectorySearcher 主要用于属性的搜索。 我们通过LDAP协议的方式连接到活动目录中: DirectoryEntry entry1 = new DirectoryEntry("LDAP://ptr.petrochina/ OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochin ", mailuser, passwd, AuthenticationTypes.ServerBind); 其中DC是Domain Component(域组件)的缩写,它只用于表示域的根。OU是Organizational Unit(组织单元)的缩写。OU是容器对象,它主要从逻辑的角度来管理和组织活动目录域。AuthenticationTypes是连接到活动目录的认证方式,这里我们使用的是AuthenticationTypes.ServerBind的方式。 下面是一个获取某个OU节点下所有的组织单元和用户的例子: public static void GetAll() { DirectoryEntry entry = new DirectoryEntry("domainADsPath"); DirectorySearcher mySearcher = new DirectorySearcher(entry); // mySearcher.Filter = ("(objectClass=organizationalUnit)"); // 查询条件是所有的组织单元 mySearcher.Filter = "(|(objectClass=user)(objectClass=organizationalUnit))"; mySearcher.PageSize = 20000; foreach(SearchResult resEnt in mySearcher.FindAll()) { Response.Write (" <b>Name:</b> "+resEnt.GetDirectoryEntry().Name.ToString()); Response.Write (" <b>Path: </b> +resEnt.GetDirectoryEntry().Path.ToString()+"<br>"); } } 参数domainADsPath是活动目录的域名,使用类似“LDAP://域名”的形式, mySearcher.Filter是过滤条件,基本上有以下三个选择: 1. objectClass=organizationalUnit 查询条件是所有的组织单元(OU) 2. objectClass=group 查询条件是所有的组(GROUP) 3. objectClass=user 查询条件是所有的用户(USER) 而且mySearcher.Filter支持多种条件的组合,如本文中的例子"(|(objectClass=user)(objectClass=organizationalUnit))"是查询所有的用户和组织单元,注意这个表达式的写法,逻辑运算符是前置的。 mySearcher.PageSize = 20000此参数可以任意设置,但不能不设置,如不设置读取AD数据最多为999条数据,设置后可以读取大于1000条数据。 这样我们读取出活动目录上某一节点的一些属性,如name(姓名), mail(邮箱),samaccountname(帐号)等信息。把这些信息读出后,可以通过遍历的方式捆绑到Treeview的节点上。 四、Treeview的生成 为了生成Treeview的树形结构,必须在从上面节点读取到的属性中,构造一个可以构造Treeview中父子节点关系的字段,经过测试找到了distinguishedname这个属性。Distinguishedname中存放的是从活动目录根节点到当前节点的组织单元,使用逗号进行的分割。可以看出下面例子的节点是一个叫信息中心的组织单元,它的上级节点是第四采油厂。 distinguishedname = OU=信息中心,OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochina 通过构造截取字符串的函数,可以把一个节点的父节点的信息取道,通过一个递归的函数,我们找到这个父节点,这样在遍历活动目录的同时,把当前的节点不断的插入到Treeview中,最后就生成了Treeview的树形结构了。实现的核心代码如下: // 设置Treeview根节点,就是遍历活动目录开始的起点 // path 是根节点的distinguishedname值 path = "OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochina"; string root=path.Split(',')[0].ToString().Substring(3); TreeNode node1 = new TreeNode(); node1.Text = root; node1.NodeData=path; node1.ImageUrl="images/folder.gif"; node1.ExpandedImageUrl="images/folderopen.gif"; ADTree.Nodes.Add(node1); // ADTree 是我们定义的 Treeview ADTree.ExpandLevel = 1; // 展开树 // 把组织单元和用户节点捆绑到Treeview中 public void GetAll() { System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://ptr.petrochina/" + path, mailuser, passwd, AuthenticationTypes.ServerBind); System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry); mySearcher.Filter = "(|(objectClass=user)(objectClass=organizationalUnit))"; mySearcher.PageSize = 20000; foreach(System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll()) { System.DirectoryServices.DirectoryEntry de=resEnt.GetDirectoryEntry(); string test=de.Properties["distinguishedname"].Value.ToString(); string fjdstr=test.Substring(test.Split(',')[0].ToString().Length+1); TreeNode tmpNd = new TreeNode(); tmpNd.Text=de.Properties["name"].Value.ToString(); // 在NodeData 属性中存放 当前节点的distinguishedname的值 tmpNd.NodeData=test; // 通过objectcategory属性判断节点是组织单元还是用户,并设置不同的Treeview节点显示图片 if ( de.Properties["objectcategory"].Value.ToString().Split(',')[0].ToString()=="CN=Organizational-Unit" ) { tmpNd.ImageUrl="images/folder.gif"; tmpNd.ExpandedImageUrl="images/folderopen.gif"; } else { tmpNd.ImageUrl="images/user.gif"; tmpNd.ExpandedImageUrl="images/user.gif"; } // 递归遍历Treeview找当前节点的父节点,把当前节点插入到Treeview中 try { FindNode(ADTree.Nodes[0],fjdstr).Nodes.Add(tmpNd); } catch(Exception e) { } } } 截取当前节点的父节点的方法是,先用Split()函数把distinguishedname属性按逗号分割成几段,test.Split(',')[0].ToString().Length是当前节点第一逗号前的长度,加上后面的逗号,所以开始截取字符串的长度是test.Split(',')[0].ToString().Length+1,使用截取字符串函数Substring(),很容易就找到当前节点的父节点了。 在上面的代码中使用了一个寻找父节点的递归函数FindNode(ADTree.Nodes[0],fjdstr),这个函数使用了深度优先算法,其原理是截取 distinguishedname属性的字符串,获取到上级节点的distinguishedname的值,通过比较存放在TreeNode节点上NodeData中的distinguishedname值,找到当前节点的父节点,递归函数返回当前节点的父节点,通过Nodes.Add()的方法把当前节点插入到父节点下。 // 查找到父节点的递归函数 private TreeNode FindNode( TreeNode tnParent, string strValue) { if( tnParent == null ) return null; if( tnParent.NodeData.ToString() == strValue ) return tnParent; TreeNode tnRet = null; foreach( TreeNode tn in tnParent.Nodes ) { tnRet = FindNode( tn, strValue); if( tnRet != null ) break; } return tnRet; } 应用上面的算法,我们可以得到一个包含组织单元和用户的树型结构,下图是程序运行结果的演示:
五、结语 本分给出了一个利用活动目录节点的属性生成Treeview结构的算法,这个算法利用是截取活动目录节点中的distinguishedname属性,获得上层的父节点的distinguishedname属性,然后通过递归的方法找到Treeview中父节点,然后把当前节点都插入到Treeview中。所给出程序在遍历活动目录节点数不多的时候,速度还可以,但遍历一个大型的活动目录,如果有上千个节点的时候,效率就会很低,因为在Treeview中每查找一个节点就要遍历一次整个Treeview。所以在生成Treeview的算法上还应有更高效的方法,比如可以把活动目录节点的属性读出后,把满足生成Treeview的字段放到数据库中,最后由数据库生成Treeview。 |