11

这个链接http://msdn.microsoft.com/en-us/library/aa772153(VS.85).aspx说:

您最多可以在一个 LDAP 连接上注册五个通知请求。您必须有一个专用线程来等待通知并快速处理它们。当您调用 ldap_search_ext 函数来注册通知请求时,该函数会返回标识该请求的消息标识符。然后使用 ldap_result 函数等待更改通知。发生更改时,服务器会向您发送一条 LDAP 消息,其中包含生成通知的通知请求的消息标识符。这会导致 ldap_result 函数返回标识已更改对象的搜索结果。

我在 .NET 文档中找不到类似的行为。如果有人知道如何在 C# 中执行此操作,我将不胜感激。我正在查看系统中所有用户的属性何时更改,以便我可以根据更改的内容执行自定义操作。

我查看了 stackoverflow 和其他来源,但没有运气。

谢谢。

4

1 回答 1

22

我不确定它是否满足您的需求,但请查看http://dunnry.com/blog/ImplementingChangeNotificationsInNET.aspx

编辑:从文章中添加了文本和代码:



有三种方法可以找出 Active Directory(或 ADAM)中发生变化的事情。这些已经在 MSDN 上以恰当的标题“变更跟踪技术概述”记录了一段时间。总之:

  1. 使用 uSNChanged 轮询更改。此技术检查“highestCommittedUSN”值以启动,然后搜索随后更高的“uSNChanged”值。'uSNChanged' 属性不会在域控制器之间复制,因此您必须每次都返回到同一个域控制器以保持一致性。本质上,您执行搜索以查找最高的“uSNChanged”值 + 1,然后以您希望的任何方式读取跟踪它们的结果。
    • 好处
      • 这是最兼容的方式。所有语言和所有版本的 .NET 都支持这种方式,因为它是一个简单的搜索。
    • 缺点
      • 这里有很多需要开发人员处理的事情。你得到了整个对象,你必须确定对象发生了什么变化(以及你是否关心这个变化)。
      • 处理已删除的对象很痛苦。
      • 这是一种轮询技术,因此它的实时性取决于您查询的频率。根据应用程序,这可能是一件好事。请注意,此处也不跟踪中间值。
  2. 使用 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 模式域一起使用会带来限制,即您必须具有复制获取更改权限(默认只有管理员)才能使用。
      • 这是一种轮询技术。它也不跟踪中间值。因此,如果您想跟踪一个对象在搜索之间多次更改,您只会得到最后一次更改。这可能是一个优势,具体取决于应用程序。
  3. 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();
}
于 2010-01-04T22:33:45.063 回答