在.NET环境下用Treeview遍历活动目录

时间:2022-06-24 12:39:24

摘 要 给出了一个利用活动目录节点的属性生成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;
}
    应用上面的算法,我们可以得到一个包含组织单元和用户的树型结构,下图是程序运行结果的演示:

在.NET环境下用Treeview遍历活动目录


五、结语
    本分给出了一个利用活动目录节点的属性生成Treeview结构的算法,这个算法利用是截取活动目录节点中的distinguishedname属性,获得上层的父节点的distinguishedname属性,然后通过递归的方法找到Treeview中父节点,然后把当前节点都插入到Treeview中。所给出程序在遍历活动目录节点数不多的时候,速度还可以,但遍历一个大型的活动目录,如果有上千个节点的时候,效率就会很低,因为在Treeview中每查找一个节点就要遍历一次整个Treeview。所以在生成Treeview的算法上还应有更高效的方法,比如可以把活动目录节点的属性读出后,把满足生成Treeview的字段放到数据库中,最后由数据库生成Treeview。