草庐IT

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

coder 2024-05-29 原文

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

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

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

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

谢谢。

最佳答案

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

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

有三种方法可以确定 Active Directory(或 ADAM)中发生的变化。这些已经在 MSDN 的标题为“Overview of Change Tracking Techniques" ”中记录了一段时间。总而言之:

  • Polling for Changes using uSNChanged 。此技术检查“highestCommittedUSN”值以启动,然后执行搜索“uSNChanged”值随后高于“TheuChange”。 ' 属性不会在域 Controller 之间复制,因此您必须每次都返回到同一个域 Controller 以保持一致性。本质上,您执行搜索以查找最高的 'uSNChanged' 值 + 1,然后读入跟踪它们的结果。
  • 优点
  • 这是最兼容的方式。所有语言和 .NET 的所有版本都支持这种方式,因为它是一个简单的搜索。
  • 开发人员的缺点是这里有很多 x1075。您取回整个对象,并且您必须确定对象上发生了什么变化(以及您是否关心该变化)。
  • 0x1045 67917 处理已删除的对象很痛苦。
  • 这是一种轮询技术,因此它的实时性取决于您查询的频率。这可能是一件好事,具体取决于应用程序。请注意,此处也不跟踪中间值。
  • Polling for Changes Using the DirSync Control .此技术使用 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 模式域一起使用会带来限制,即您必须具有复制获取更改权限(默认只有管理员)才能使用。
  • 这是一种轮询技术。它也不跟踪中间值。因此,如果您要跟踪多次搜索之间的更改的对象,您将只会获得最后一次更改。根据应用,这可能是一个优势。
  • Change Notifications in Active Directory .此技术在单独的线程上注册搜索,当任何与过滤器匹配的对象更改时,该线程将收到通知。每个异步连接最多可以注册 5 个通知。
  • 好处
  • 即时通知。其他技术需要轮询。
  • 因为这是一个通知,所以您将获得所有更改,即使是在其他两种技术中会丢失的中间更改。
  • 缺点
  • 相对资源密集。您不想做大量这些,因为它可能会导致您的 Controller 出现可扩展性问题。
  • 这只会告诉您对象是否已更改,但不会告诉您更改是什么。您需要弄清楚您关心的属性是否已更改。话虽如此,很容易判断对象是否已被删除(至少比 uSNChanged 轮询更容易)。
  • 您只能在非托管代码或 System.DirectoryServices.Protocols 中执行此操作。
  • 在大多数情况下,我发现 DirSync 几乎在所有情况下都适合我。我从来没有费心去尝试任何其他技术。但是,一位读者询问是否有办法在 .NET 中执行更改通知。我认为使用 SDS.P 是可能的,但从未尝试过。事实证明,这是可能的,而且实际上并不难做到。我写这篇文章的第一个想法是使用 MSDN 上的 sample code(并从选项 #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();
    }
    

    关于c# - 使用 C# 向 Active Directory 注册更改通知,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2002606/

    有关c# - 使用 C# 向 Active Directory 注册更改通知的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

      如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

    8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    随机推荐