本次框架封装主要是针对微软域控活动目录的基本操作。C#原先以提供活动目录的基本操作,但由域控本身概念较多,开发人员需要对其比较了解,为了降低这方面的学习成本,对其进行二次封装。
框架主要是针对如下两个方面进行封装:
- LDAP查询语句自动的生成
- 活动目录实体对象的操作
在阐述前,先来让我们简单了解关于域控的基本知识。
域控服务器
域控服务器是用语言软件集中管理的器件,能安全集中管理域中账户密码、管理策略等构成数据库,统一安全策略。
域服务器的作用是:
- 安全集中管理,统一安全策略。
- 软件集中管理,按照公司要求限定所有机器只能运行必需的办公软件。
- 环境集中管理,利用AD可以统一客户端桌面,IE,TCP/IP等设置
- 活动目录是企业基础架构的根本,为公司整体统一管理做基础。其它ISA,Exchange,防病毒服务器,补丁分发服务器,文件服务器等服务依赖于域服务器。
原生操作
1 | var path = "LDAP://contoso.com"; |
封装后操作
1 | var localAdOperation = new AdOperationBuilder().BuilderLocalAdOperation(); |
设计思路
从以上的原生操作和封装后操作,很容易看出开发人员只需要生成LDAP语句,无需再去学习比较复杂的LDAP语法(与,或,非等)如:
1 | (&(objectClass=User)) |
所以本次封装的目标是简单,容易上手且无需关心域控活动目录实体对象属性,仅需根据实际开发要求获取对应的用户,组,组织单或其他。另有特殊要求可在项目中简单扩展即可完成所对应的操作。
看一下总体UML图:
框架主要分为多域控服务器配置封装,LDAP语句生成,每个实体对象生成查询,修改和删除。
性能问题
由于本人比较喜欢TDD的开发模式,所以在每个功能点都会有相应的单元测试及功能测试,且域控活动目录原先查询有些慢,再加上有分页的封装,就没有加强测试性能这一块。
但在实际项目中使用发现查询速度差强人意。查询整个活动目录的ObjectClass=User的对象,查询时间结果如下图(既然花了这么久,有点惊讶):
性能排查过程
为了让查询比较方便,采用了数据转换实体的方法即反射技术。
所以想到是否由于元素过多,反射赋值或查询属性导致,决定第一步将反射类型对象进行缓存,利用C#表达式委托方法进行赋值和查询属性,而不使用反射进行赋值及查询属性值,但在做完这一步动作后,发现效果并不是很理想。第二步决定用trace进行跟踪测试程序运行,查看那一步引起性能问题。
跟踪到C#提供的原生获取属性的方法占用时间比较多,但原生方法无法改造,只能减少获取实体对象属性(根数据库查询不使用*一样的原理)。
现在放在面前只有减少属性的获取,但如果这样的话,调用端操作就比较麻烦,查询时需要知道有哪些属性字段(字段名的获取不向数据库来得那么方便),比较麻烦。
难道真的没有办法再优化了么?还是本身封装时就存在方法错误,此时抛弃认为正确的设计,采用原生方法进行测试,并获取相同的属性,发现测试性能并没有那么差。
会奇怪的发现和封装的方法里面调用不太一样,里面并没有把大多时间放在SearchResultCollection.get_Item方法上,而是在MoveNext上。从这个方法可以联想到封装为了能够用到InvokeGet方法,直接使用了SearchResult.GetDirectoryEntry方法。
为什么这个方法会引起性能问题呢,让我们接着往下看。
看到这一行有Bind方法,那Bind方法有什么呢,Bind方法里面调用了一个COM组件原生方法ADsOpenObject,这个原方法虽然看不到更多详细信息,但从中看到PATH,USERNAME,PASSWORD,可以猜测到这是又一次到服务器中进行获取数据。
性能优化
通过上述的分析可以总结到封装在每一次获取数据特殊赋值时,原生方法会再一次去服务器获取数据,所以导致查询性能慢的原因。所以将无需调用InvokeGet方法的属性将不进行使用GetDirectory方法,直接使用SearchResult.Properties属性获取其值,最终测试如下图。
总结
封装一个框架除了要简单,易用,示例,文档,性能也很重要的一部分,如性能不行框架也是无法使用,就像当年LINQ To SQL刚出来还是比较新颖的,但使用后发现数据库中数据比较多时会比较慢,所以放弃使用,再到微软也重新设计数据库ORM框架EF,可以看到性能是必备条件。