我不确定它是否满足您的需求,但请查看http://dunnry.com/blog/ImplementingChangeNotificationsInNET.aspx
编辑:从文章中添加了文本和代码:
有三种方法可以找出 Active Directory(或 ADAM)中发生变化的事情。这些已经在 MSDN 上以恰当的标题“变更跟踪技术概述”记录了一段时间。总之:
- 使用 uSNChanged 轮询更改。此技术检查“highestCommittedUSN”值以启动,然后搜索随后更高的“uSNChanged”值。'uSNChanged' 属性不会在域控制器之间复制,因此您必须每次都返回到同一个域控制器以保持一致性。本质上,您执行搜索以查找最高的“uSNChanged”值 + 1,然后以您希望的任何方式读取跟踪它们的结果。
- 好处
- 这是最兼容的方式。所有语言和所有版本的 .NET 都支持这种方式,因为它是一个简单的搜索。
- 缺点
- 这里有很多需要开发人员处理的事情。你得到了整个对象,你必须确定对象发生了什么变化(以及你是否关心这个变化)。
- 处理已删除的对象很痛苦。
- 这是一种轮询技术,因此它的实时性取决于您查询的频率。根据应用程序,这可能是一件好事。请注意,此处也不跟踪中间值。
- 使用 DirSync 控件轮询更改。此技术使用 ADSI 中的 ADS_SEARCHPREF_DIRSYNC 选项和隐藏的 LDAP_SERVER_DIRSYNC_OID 控件。只需进行初始搜索,存储 cookie,然后再次搜索并发送 cookie。它将仅返回已更改的对象。
- 好处
- 这是一个易于遵循的模型。System.DirectoryServices 和 System.DirectoryServices.Protocols 都支持此选项。
- 过滤可以减少您需要烦恼的事情。例如,如果我的初始搜索是针对所有用户“(objectClass=user)”,我随后可以使用“(sn=dunn)”对轮询进行过滤,并且只取回两个过滤器的组合,而不必处理一切都来自初始过滤器。
- Windows 2003+ 选项消除了使用此选项(对象安全)的管理限制。
- Windows 2003+ 选项还将使您能够仅返回在大型多值属性中已更改的增量值。这是一个非常好的功能。
- 很好地处理已删除的对象。
- 缺点
- 这是 .NET 2.0+ 或更高版本的唯一选项。.NET 1.1 的用户将需要使用 uSNChanged Tracking。脚本语言不能使用这种方法。
- 您只能将搜索范围限定为分区。如果您只想跟踪特定的 OU 或对象,您必须稍后自己整理这些结果。
- 将其与非 Windows 2003 模式域一起使用会带来限制,即您必须具有复制获取更改权限(默认只有管理员)才能使用。
- 这是一种轮询技术。它也不跟踪中间值。因此,如果您想跟踪一个对象在搜索之间多次更改,您只会得到最后一次更改。这可能是一个优势,具体取决于应用程序。
- Active Directory 中的更改通知。此技术在单独的线程上注册搜索,当任何匹配过滤器的对象发生更改时,该线程将接收通知。每个异步连接最多可以注册 5 个通知。
- 好处
- 即时通知。其他技术需要轮询。
- 因为这是一个通知,所以您将获得所有更改,甚至是在其他两种技术中会丢失的中间更改。
- 缺点
- 资源相对密集。你不想做一大堆这些,因为它可能会导致你的控制器出现可伸缩性问题。
- 这只会告诉您对象是否已更改,但不会告诉您更改是什么。您需要弄清楚您关心的属性是否发生了变化。话虽如此,很容易判断对象是否已被删除(至少比 uSNChanged 轮询更容易)。
- 您只能在非托管代码或 System.DirectoryServices.Protocols 中执行此操作。
在大多数情况下,我发现 DirSync 在几乎所有情况下都适合我。我从不费心尝试任何其他技术。然而,一位读者询问是否有办法在 .NET 中进行更改通知。我认为使用 SDS.P 是可能的,但从未尝试过。事实证明,这是可能的,而且实际上并不难做到。
我写这篇文章的第一个想法是使用 MSDN 上的示例代码(并从选项 #3 中引用)并将其简单地转换为 System.DirectoryServices.Protocols。结果证明这是一条死胡同。您在 SDS.P 中执行此操作的方式和示例代码的工作方式完全不同,因此无济于事。这是我想出的解决方案:
public class ChangeNotifier : IDisposable
{
LdapConnection _connection;
HashSet<IAsyncResult> _results = new HashSet<IAsyncResult>();
public ChangeNotifier(LdapConnection connection)
{
_connection = connection;
_connection.AutoBind = true;
}
public void Register(string dn, SearchScope scope)
{
SearchRequest request = new SearchRequest(
dn, //root the search here
"(objectClass=*)", //very inclusive
scope, //any scope works
null //we are interested in all attributes
);
//register our search
request.Controls.Add(new DirectoryNotificationControl());
//we will send this async and register our callback
//note how we would like to have partial results
IAsyncResult result = _connection.BeginSendRequest(
request,
TimeSpan.FromDays(1), //set timeout to a day...
PartialResultProcessing.ReturnPartialResultsAndNotifyCallback,
Notify,
request);
//store the hash for disposal later
_results.Add(result);
}
private void Notify(IAsyncResult result)
{
//since our search is long running, we don't want to use EndSendRequest
PartialResultsCollection prc = _connection.GetPartialResults(result);
foreach (SearchResultEntry entry in prc)
{
OnObjectChanged(new ObjectChangedEventArgs(entry));
}
}
private void OnObjectChanged(ObjectChangedEventArgs args)
{
if (ObjectChanged != null)
{
ObjectChanged(this, args);
}
}
public event EventHandler<ObjectChangedEventArgs> ObjectChanged;
#region IDisposable Members
public void Dispose()
{
foreach (var result in _results)
{
//end each async search
_connection.Abort(result);
}
}
#endregion
}
public class ObjectChangedEventArgs : EventArgs
{
public ObjectChangedEventArgs(SearchResultEntry entry)
{
Result = entry;
}
public SearchResultEntry Result { get; set;}
}
这是一个相对简单的类,可用于注册搜索。诀窍是在回调方法中使用 GetPartialResults 方法来仅获取刚刚发生的更改。我还包含了非常简化的 EventArgs 类,用于将结果传回。请注意,我在这里没有对线程做任何事情,也没有任何错误处理(这只是一个示例)。你可以像这样使用这个类:
static void Main(string[] args)
{
using (LdapConnection connect = CreateConnection("localhost"))
{
using (ChangeNotifier notifier = new ChangeNotifier(connect))
{
//register some objects for notifications (limit 5)
notifier.Register("dc=dunnry,dc=net", SearchScope.OneLevel);
notifier.Register("cn=testuser1,ou=users,dc=dunnry,dc=net", SearchScope.Base);
notifier.ObjectChanged += new EventHandler<ObjectChangedEventArgs>(notifier_ObjectChanged);
Console.WriteLine("Waiting for changes...");
Console.WriteLine();
Console.ReadLine();
}
}
}
static void notifier_ObjectChanged(object sender, ObjectChangedEventArgs e)
{
Console.WriteLine(e.Result.DistinguishedName);
foreach (string attrib in e.Result.Attributes.AttributeNames)
{
foreach (var item in e.Result.Attributes[attrib].GetValues(typeof(string)))
{
Console.WriteLine("\t{0}: {1}", attrib, item);
}
}
Console.WriteLine();
Console.WriteLine("====================");
Console.WriteLine();
}