使用C#向Active Directory注册更改通知

11

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

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

我查看了.NET文档,并没有找到类似的行为。 如果有人知道如何在C#中执行此操作,我将非常感激。 我正在寻找在系统中所有用户的属性更改时执行自定义操作的方法,具体取决于更改了什么。

我在stackoverflow和其他来源中查找过,但没有找到答案。

谢谢。

1个回答

25

我不确定它是否符合您的需求,但请查看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跟踪。脚本语言无法使用此方法。
      • 您只能将搜索范围限制为分区。如果要跟踪特定的OU或对象,则必须稍后自行解决这些结果。
      • 与非Windows 2003模式域一起使用时,带有复制获取更改权限(默认仅管理员)的限制。
      • 这是一种轮询技术。它也不跟踪中间值。因此,如果您要跟踪的对象在多次搜索之间发生更改,则只会获得最后一次更改。这可以是应用程序的优点。
  3. Active Directory中的更改通知。该技术在单独的线程上注册搜索,将在任何与过滤器匹配的对象更改时接收通知。您可以每个异步连接注册最多5个通知。
    • 好处
      • 即时通知。其他技术需要轮询。
      • 因为这是通知,所以您将获得所有更改,即使是在其他两种技术
        在大多数情况下,我发现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();
        }
        

@stuartd,我已经把它搞定了,可以接收到更改通知。但是我会收到大约30个属性,其中没有任何一个与当前配置不同。我错过了我们只能接收更改项的部分吗? - Wjdavis5
1
@Wjdavis5,你应该提出一个新问题。 - stuartd
2
@Marco,我发表这篇文章已经八年了?我不知道。你为什么不试一试呢? - stuartd
@Macindows 原文链接 仍然可用,如果我漏掉了什么东西,那里面一定有。 - stuartd
@Macindows 使用 LdapConnection connect = new LdapConnection(serverAddress) - Reza Bayat
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接