MSDN Magazin > Home > Ausgaben > 2007 > July >  Security Briefs: Active Directory-Cacheabh...
Security Briefs
Active Directory-Cacheabhängigkeiten
Keith Brown

Codedownload verfügbar unter: SecurityBriefs2007_07.exe (182 KB)
Browse the Code Online
Wenn Sie gerade eine Anwendung erstellen, die überall in einem Unternehmen verwendet werden soll, werden Sie wahrscheinlich von einer Integration in Active Directory® profitieren. Sie finden hier zahlreiche nützliche Informationen, wenn Sie sich etwas Zeit nehmen und lernen möchten, wie für eine der vielen APIs, die Active Directory unterstützt, programmiert wird.
Nehmen wir zum Beispiel an, dass Ihre Anwendung bereits die integrierte Windows®-Authentifizierung verwendet, um die Identität der Domäne eines Clients zu überprüfen. Dann haben Sie bereits den ersten und wohl größten Schritt in Richtung Integration in Active Directory getan. Sie nutzen bereits die vorhandenen Active Directory-Anmeldeinformationen des Benutzers, statt eine andere private Benutzerdatenbank zu erstellen. Das ist eine gute Wahl, denn Sie erleichtern es Ihrer Anwendung, im Unternehmen ausgeführt zu werden, ohne dass der Benutzer besondere Anmeldeinformationen für Ihre Anwendung haben muss.
Sie können sogar Verfahren wie z. B. rollenbasierte Sicherheit nutzen, die Gruppen in Active Directory verwendet, um die Berechtigungsebene des Benutzers zu bestimmen. Die Anwesenheit einer bestimmten Gruppe können Sie durch Aufrufen der IsInRole-Methode auf der WindowsPrincipal-Klasse testen. Das ist nicht ungewöhnlich.
Aber gehen Sie doch einen Schritt weiter, denn dann wird es wirklich interessant. Wenn Ihre Anwendung unter einem Domänenkonto (sogar unter einem Konto mit niedrigen Berechtigungen) ausgeführt wird, sollten Sie problemlos Active Directory abfragen können, um mehr über den Benutzer zu erfahren. Sie wissen das vielleicht nicht, aber selbst wenn Ihre Anwendung als Netzwerkdienst auf einem Domänencomputer in einer Testumgebung ausgeführt wird, wird der Prozess dieser Anwendung von den Anmeldeinformationen der Domäne dieses Computers vertreten, wenn Sie eine integrierte Authentifizierung verwenden, um sichere Aufrufe im Netzwerk auszuführen, einschließlich Aufrufen von Active Directory.
Deshalb könnten Sie zusätzlich zu diesem Aufruf von IsInRole Code wie z. B. in Abbildung 1 hinzufügen, um die E-Mail-Adresse oder Telefonnummer ihres Clients in einer Anwendung abzurufen. Jede verwaltete Anwendung, angefangen bei ASP.NET-Anwendungen über Windows Communication Foundation-Dienste bis hin zu Windows Forms-Desktopanwendungen, kann von der Integration in Active Directory profitieren.
string email = LookupEmail((WindowsIdentity)User.Identity)

...

public string LookupEmail(WindowsIdentity id) 
{
    string path = string.Format(“LDAP://<SID={0}>”, id.User);
    using (DirectoryEntry user = new DirectoryEntry(
        path, null, null, SECURE_BINDING)) 
    {
        return (string)user.Properties[“mail”].Value;
    }
}

const AuthenticationTypes SECURE_BINDING =
    AuthenticationTypes.Secure | AuthenticationTypes.Signing |
    AuthenticationTypes.Sealing;

Es gibt viele nützliche Attribute, wie z. B. mail, damit Ihre Anwendung noch nahtloser innerhalb eines Unternehmens funktioniert. Möchten Sie jemandem eine freundliche E-Mail senden und den Benutzer darin mit dem Vornamen ansprechen? Das Attribut, das Sie dafür brauchen, heißt „givenName“. Sie suchen die E-Mail-Adresse des persönlichen Assistenten eines Benutzers? Fragen Sie einfach das Assistentenobjekt Ihres Benutzers ab, und übernehmen Sie dessen mail-Attribut.

LDAP- und SQL-Abfragen
Mein Codebeispiel in Abbildung 1 verwendet den Namespace „System.DirectoryServices“, mit dem sich die Integration in Active Directory besonders einfach beginnen lässt. Im Hintergrund verwendet diese verwaltete Bibliothek eine COM-Bibliothek mit dem Namen „Active Directory Services Interface“ (ADSI). Darunter befindet sich eine systemeigene C-Bibliothek mit dem Namen „LDAP API“, wobei LDAP für Lightweight Directory Access-Protokoll steht. Einer der Gründe, warum sie „leicht“ genannt wird, ist, weil sie über TCP/IP ausgeführt wird. Aber das erkläre ich ein anderes Mal. Dieser Stapel ist in Abbildung 2 dargestellt. (Auf diese Abbildung komme ich später zurück.)
Abbildung 2 Directory Services-Stapel 
Wenn Sie mit Active Directory kommunizieren, verwenden Sie letzten Endes LDAP, das sowohl ein Netzwerkprotokoll wie auch ein Programmiermodell mit seiner eigenen Abfragesprache ist. Einfache Abfragen wie die in Abbildung 1 gezeigte sind ziemlich einfach zu implementieren.
Das muss aber nicht unbedingt der Fall sein, wenn Sie z. B. so etwas wie ein Organigramm erstellen möchten, was uneingeschränkt möglich ist, da mit Active Directory Organisationen Beziehungen unter Benutzern nachverfolgen können. Nehmen Sie zum Beispiel die Attribute „manager“, „directReports“, „department“ und „title“ eines Benutzerobjekts in Active Directory. Wenn diese Art von Daten in SQL Server™ gespeichert wurde, wäre es möglich, ein Organigramm mit sehr wenigen Roundtrips in der Datenbank zu generieren. Mit dem SQL-Operator JOIN können Sie die Datenbank anweisen, einen Bericht zu erstellen, der dann an Sie zurückgeschickt wird.
LDAP hat keinen Operator JOIN. Active Directory bietet nicht so etwas wie gespeicherte Verfahren, mit denen Sie benutzerdefinierte Berichte wie diesen serverseitig zusammenstellen könnten. Wenn also Ihre LDAP-Abfragen komplexer werden, müssen Sie wahrscheinlich auf dem Client manuelle Verknüpfungen durchführen, um die benötigten Daten zusammenzustellen. Das kann zu vielen Roundtrips zum Domänencontroller führen. Wenn Sie nicht vorsichtig sind, wird Ihre Anwendung Netzwerkbandbreite verschwenden, während der Benutzer ungeduldig an der Konsole auf eine Antwort wartet.

Cacheeigenschaften und Objekte
Wie lässt sich dieses Problem also lösen? Da Verzeichnisse für häufiges Lesen und seltenes Schreiben konzipiert sind, wäre ein temporärer clientseitiger Cache eine logische Möglichkeit, den Aufwand durch komplexe LDAP-Abfragen zu verringern. ADSI hat aus diesem Grund bereits einen Eigenschaftscache implementiert. Wenn der Code in Abbildung 1 das LDAP-Attribut mit dem Namen „mail“ anfordert, lädt die DirectoryEntry-Klasse in Wirklichkeit alle Attribute des Benutzerobjekts herunter und macht sie lokal verfügbar. (Falls es Sie interessiert: Sie können das mit der Methode „RefreshCache“ auf DirectoryEntry steuern und genau die gewünschten Attribute auswählen.)
Der Eigenschaftscache ist zum Zwischenspeichern der Attribute eines einzelnen Objekts nützlich. Wenn Sie aber mit vielen Objekten arbeiten, wie eben beispielsweise bei einem Organigramm, müssen Sie nicht nur die Eigenschaften, sondern die kompletten Objekte zwischenspeichern. Dann stellt sich die Frage, wie lange sich dieser Cache mit Objekten halten lässt. Es scheint verschwenderisch, diese Daten wegzuwerfen, so lange die Möglichkeit besteht, dass sie bald wieder benötigt werden könnten.
ASP.NET besitzt daher eine Cacheinfrastruktur, mit der sich ein lokaler Cache mit Verzeichnisobjekten verwalten lässt. Es ist ziemlich leicht, etwas im Cache abzulegen und dabei einen Zeitpunkt in der Zukunft anzugeben, ab dem der Cache ungültig wird. Wenn Sie das Gesuchte im Cache nicht finden können, wissen Sie, dass es abgelaufen ist, und Sie können dann wieder zum Verzeichnis wechseln und den Cache aktualisieren. Hier das grundlegende Muster:
List<DirectoryEntry> users = Cache[“users”];
if (null == users) 
{
    // refresh cache from Active Directory
    users = loadUserListFromAD();
    Cache.Add(“users”, users, null, DateTime.Now.AddMinutes(5),
        Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
displayOrgChart(users);
Wenn Sie viele Anforderungen für dieses Organigramm haben, kann ein Cache wie dieser Ihr Rettungsanker sein.

SqlDependency und intelligentes Zwischenspeichern
Mit den Versionen ADO.NET 2.0 und SQL Server 2005 wurde eine neue Klasse zum Verwalten lokaler Datencaches eingeführt. SqlDependency im Namespace „System.Data.SqlClient“ verwendet ein Feature von SQL Server 2005, die so genannten Abfragebenachrichtigung. In verwaltetem Code können Sie eine Instanz von SqlDependency erstellen und ihr einen SqlCommand mit einer Abfrage übergeben. Sie werden dann benachrichtigt, wenn sich die von dieser Abfrage vertretenen Daten geändert haben. Um die Datenbanklast zu beschränken, besitzt die beobachtete Abfrage einige Einschränkungen. Beachten Sie aber, dass die Abfrage, die Sie beobachten, nicht unbedingt genau mit der Abfrage übereinstimmen muss, mit der Sie die Daten abrufen. Die als Abhängigkeit verwendete Abfrage kann eine Obermenge der tatsächlich ausgeführten Abfrage sein, und Sie können dennoch von den Vorteilen dieser Funktion profitieren.
Das brachte mich zum Nachdenken: Was, wenn es eine zugehörige Klasse „ADDependency“ gäbe? In der Vergangenheit habe ich schon mehrmals versucht herauszubekommen, wie Active Directory Benachrichtigungen ausgeben kann, wenn sich seine Daten ändern. Denn LDAP-Daten werden häufig lokal zwischengespeichert. Leider konnte ich nie eine Lösung für dieses Problem finden. Aber als ich mich kürzlich mit Joe Kaplan (Microsoft MVP und Experte für Verzeichnisdienste) unterhalten habe, hat er das Steuerelement „DirSync“ erwähnt. Am nächsten Tag konnte ich die ADDependency-Klasse erstellen und testen, die ich immer gesucht hatte.

Das DirSync-Steuerelement
LDAP Version 3.0 verfügt über Steuerelemente, mit denen ein Verzeichnisdienst erweiterte Features bereitstellen kann. Die meisten Programmierer, die mit Microsoft® .NET Framework arbeiten, verbinden mit dem Begriff LDAP-Steuerelement wahrscheinlich Windows Forms-Steuerelemente oder serverseitige ASP.NET-Steuerelemente. Aber das Einzige, aus dem ein LDAP-Steuerelement wirklich besteht, ist ein Verfahren, zusätzliche Metadaten zusammen mit einem LDAP-Befehl zu senden, um zu steuern, wie dieser dann funktioniert.
Active Directory stellt mehrere eigene LDAP-Steuerelemente zur Verfügung. Das Steuerelement, das ich hier verwenden werde, heißt DirSync, und wie sein Name schon andeutet, geht es dabei um die Synchronisation anderer Datenspeicher mit Daten aus Active Directory.
Wenn Sie bedenken, wie Active Directory funktioniert, dann wird klar, dass es natürlich zum Synchronisieren von Daten entworfen wurde. Eines der wichtigsten Features des Verzeichnisses ist es, Daten zwischen Domänencontrollern (DCs) sehr effizient zu replizieren. Objekte und Eigenschaften werden in Active Directory mit 64-Bit-USNs (Update Sequence Numbers) gestempelt. Ich vereinfache das Ganze gewaltig, aber stellen Sie sich vor, ein DC hält eine aktuelle USN, inkrementiert sie jedes Mal, wenn sich etwas ändert, und stempelt das geänderte Objekt und die geänderten Attribute mit der aktuellen USN. Immer, wenn ein verwandter DC eine Replikation von einem anderen DC anfordert, muss der DC nur die letzte USN senden, die er von diesem Partner erhalten hat. Der Empfänger ist dann in der Lage, die Objekte und Attribute zu finden, die sich seit der letzten Replikation zu diesem speziellen Partner geändert haben.
Eines der Hauptziele bei der Implementierung von Active Directory ist zu vermeiden, dass das Netzwerk mit Replikationsdaten überschwemmt wird. Deshalb kommt in Active Directory der Nachverfolgung von Datenversionsnummern so viel Bedeutung zu. Die Funktionsweise ist sehr einleuchtend, wenn Sie sich ein wenig mit Replikationen in Active Directory befassen. Aber es gibt auch Dokumentationsmaterial unter msdn2.microsoft.com/ms677626.aspx, in dem das Abrufen von Änderungen mit DirSync beschrieben ist.
Das DirSync-Steuerelement wird zusammen mit einer Suchanforderung verwendet. Zusammen mit der Anforderung werden diese zusätzlichen Metadaten gesendet (die Daten des Steuerelements „DirSync“), zu denen auch ein Synchronisierungscookie gehört. Bei der allerersten Anforderung an Active Directory mit dem DirSync-Steuerelement senden Sie ein leeres Cookie, und Active Directory antwortet mit allen Suchergebnissen und einem Cookiewert, bei dem es sich um ein Bytearray handelt. Wenn Sie herausfinden wollen, ob sich seit Ihrer letzten Abfrage etwas geändert hat, senden Sie einfach in Ihrer nächsten Suchanforderung das gleiche Cookie über die Metadaten des DirSync-Steuerelements zurück.
Obwohl das Cookie undurchsichtig ist, ist doch ziemlich offensichtlich, was es enthält. Unter anderem würden Sie dort eine Art Versionsnummer, vielleicht sogar eine USN wie in der Replikation vermuten. Was also erhalten Sie bei Ihrer zweiten Anforderung? Nur die Objekte und Attribute, die sich seit der ersten Anforderung geändert haben! Mithilfe des DirSync-Steuerelements können Sie die Zwischenspeicherung tatsächlich effizienter gestalten als mit SqlDependency, denn das Verzeichnis sendet die Deltas, mit denen Sie Ihre zwischengespeicherte Version mit geringstmöglichen Auswirkungen auf das Netzwerk auf dem aktuellen Stand halten können. Das ist sehr vorteilhaft, wenn Sie viele Daten synchronisieren oder, wie in meinem Fall, zwischenspeichern.

System.DirectoryServices.Protocols
Es gibt also in Wirklichkeit zwei Hauptverfahren, mit denen Sie Active Directory mit .NET Framework 2.0 oder höher programmieren können. Einerseits können Sie ähnlichen Code wie in Abbildung 1 verwenden, wobei Sie dann System.DirectoryServices und den in Abbildung 2 gezeigten ADSI-Stapel verwenden. Aber bei .NET Framework 2.0 wurde eine neue API für die Kommunikation mit LDAP-Verzeichnissen eingeführt. Diese neue Schnittstelle ist vollständig verwaltet und von ADSI komplett getrennt und bringt in vieler Hinsicht frischen Wind mit sich. ADSI fügt seinen eigenen Satz an Abstraktionen über dem LDAP hinzu, wie z. B. den Eigenschaftscache und eine implizite Verbindungsverwaltung, die zwar praktisch ist, aber auch Verwirrung stiften kann, weil nicht immer ganz klar ist, wann tatsächlich eine LDAP-Netzwerkanforderung stattfindet.
Der neuere Namespace „System.DirectoryServices.Protocols“ bringt diese ADSI-Last nicht mit sich. Es handelt sich dabei um eine untergeordnete Schnittstelle, die sich direkt über der systemeigenen LDAP-API in der WLDAP32.DLL befindet. Was mir an dieser Schnittstelle besonders gefällt, ist ihre Explizitheit. Man weiß, wann eine TCP/IP-Verbindung für LDAP offen ist, denn man hat sie selbst durch Aufruf von LdapConnection.Bind geöffnet. Man weiß genau, wann Netzwerkverkehr stattfindet, denn man ruft für alle Anforderungen LdapConnection.SendRequest auf und sendet einfach je nach Zielsetzung unterschiedliche Nachrichtenklassen. Wenn man asynchron arbeiten will, gibt es das erwartete BeginSendRequest und EndSendRequest.
Da dieser neue Namespace nur in sehr wenigen Beispielen gezeigt wird, habe ich mich zu einem Sprung ins kalte Wasser entschlossen und entschieden, mit diesem Modell ADDependency zu erstellen. Ich war überrascht, wie einfach und natürlich es zu verwenden ist. Trotzdem muss ich Sie warnen: Die Dokumentation für diese neue API ist zum Zeitpunkt dieses Artikels noch ziemlich dürftig. Wenn Sie Active Directory noch nicht kennen, wird es für Sie wahrscheinlich leichter sein, mit dem traditionellen ADSI-Modell und System.DirectoryServices zu beginnen und diese neue API erst später auszuprobieren, wenn Sie die Grundlagen beherrschen.

Verwendungsmodell für ADDependency
Beim Entwurf von ADDependency habe ich mich an SqlDependency orientiert. Wenn Sie das eine erst einmal kennen, ist es recht einfach, mit dem anderen zu arbeiten. Abbildung 3 zeigt die öffentliche Schnittstelle zu ADDependency. Zunächst erstellen Sie eine Instanz und geben mindestens einen Suchstamm und einen LDAP-Filter an. Beispiel:
public ADDependency(string dnSearchRoot, string ldapFilter,
    params string[] attrsToWatch) : 
        this(“localhost”, dnSearchRoot, ldapFilter,
            DirectorySynchronizationOptions.ObjectSecurity, attrsToWatch)
{
}

public ADDependency(string adServer, string dnSearchRoot, 
    string ldapFilter, DirectorySynchronizationOptions dirSyncOptions,
    params string[] attrsToWatch) 
{
    this.adServer = adServer;
    this.dnSearchRoot = dnSearchRoot;
    this.ldapFilter = ldapFilter;
    this.attrsToWatch = attrsToWatch;
    this.dirSyncOptions = dirSyncOptions;
}

public void Start() 
{
    if (null != conn) throw new InvalidOperationException(
        “You may only call Start one time.”);
    conn = new LdapConnection(new LdapDirectoryIdentifier(adServer),
        null, AuthType.Negotiate);
    conn.Bind();

    timer = new Timer(timerCallback, null, 
        TimeSpan.FromSeconds(0), pollingInterval);
}

public void Stop() 
{
    timer.Dispose();
    conn.Dispose();
}

public event ChangedEventHander Changed;

public delegate void ChangedEventHander(object sender,
    ChangedEventArgs args);

public class ChangedEventArgs : EventArgs 
{
    public ChangedEventArgs(SearchResponse response) 
    {
        this.Response = response;
    }
    public readonly SearchResponse Response;
}

ADDependency dep = new ADDependency(
    “dc=contoso,dc=com”, “(objectclass=user)”);
Sobald eine Instanz der Abhängigkeit existiert, müssen Sie diese für die Dauer halten, während der Daten synchronisiert oder zwischengespeichert werden. ADDependency verwaltet das Cookie und die zugrunde liegende LDAP-Verbindung zum Server. Nun müssen Sie das Changed-Ereignis einbinden, um über Änderungen benachrichtigt zu werden, beispielsweise so:
dep.Changed += OnChange;
...
void OnChange(object sender, ChangedEventArgs args) 
{
    foreach (SearchResultEntry e in args.Response.Entries) 
    {
        Console.WriteLine(e.DistinguishedName);
    }
}
Der hier dargestellte einfache Handler gibt einfach die Namen aller Objekte aus, die kürzlich geändert wurden. Bei einer echten Implementierung würde man die Daten im Cache mit den Attributen der einzelnen Objekte in der Sammlung aktualisieren oder den Cache verwerfen und dann durch erneutes Lesen der Daten aus dem Verzeichnis wieder auffüllen.
Wenn die Verbindung geöffnet und die Abfrage gestartet werden soll, rufen Sie einfach Start auf:
dep.Start();
Dabei wird die System.Threading.Timer-Instanz gestartet, die standardmäßig alle N Sekunden Active Directory abruft um festzustellen, ob sich etwas geändert hat. Die Konstante N sollte so gewählt werden, dass Abrufhäufigkeit und Cachezuverlässigkeit in einem ausgewogenen Verhältnis zueinander stehen. Wenn sie läuft und sich nichts geändert hat, ist diese Anforderung nicht sehr teuer. Sie ist nur ein Roundtrip zum Server, bei dem festgestellt wird, dass sich nichts geändert hat. Beachten Sie aber, dass bei der allerersten Anforderung eine Liste mit allen Objekten zurückgeben wird, damit der Cache initialisiert werden kann.
Wenn Sie fertig sind und die Verbindung beenden wollen, rufen Sie die Methode „Stop“ auf. Bei der aktuellen Konfiguration kann die Instanz nicht wieder gestartet werden, wenn sie einmal beendet wurde (andernfalls wird eine Ausnahme ausgegeben).
Wenn Sie ADDependency und SqlDependency miteinander vergleichen, haben Sie vielleicht bemerkt, dass sie unterschiedlich funktionieren. ADDependency fragt das Verzeichnis alle N Sekunden ab und erhält mit jeder Anforderung Deltas, damit der Benutzer inkrementell den lokalen Cache aktualisieren kann. SqlDependency hingegen führt keine Abrufe aus. Es erhält Benachrichtigungen von SQL Server 2005, und der Aufrufer muss dann wieder aus dem Verzeichnis die eigentlichen Daten abrufen. Das bedeutet, dass keine Deltas abgefragt werden.
Sind mit dem Abrufen Nachteile verbunden? Nur, wenn Sie es übertreiben. Aber da sich Verzeichnisdaten in der Regel nicht sehr häufig ändern, sollte das Abrufintervall entsprechend lang sein. Es ist wie so oft: Am besten sollte man zuerst messen und dann erst entscheiden, ob diese Strategie in Anbetracht des Datenzugriffmusters Ihrer Anwendung sinnvoll ist.

Tiefere Einblicke
Wenn Sie sich Abbildung 3 etwas genauer anschauen, werden Sie feststellen, dass der Konstruktor auf ADDependency einiges mehr enthält, als ich bis jetzt erwähnt habe. Zum Beispiel können (und sollten) Sie eine Liste mit relevanten LDAP-Attributen übergeben, wie z. B. mail, title, manager usw. Dadurch wird die Größe der anfänglich empfangenen Daten und der nachfolgenden Deltas beschränkt. Zusätzlich wird die Auslastung des DCs begrenzt.
Beim zweiten, interessanteren Konstruktor müssen Sie einen speziellen DC angeben. (Der Konstruktor in Abbildung 1 nimmt an, dass Ihre Anwendung auf einem DC ausgeführt wird, und stellt eine Verbindung zu localhost her. Das ist zwar in einer Testumgebung sinnvoll, aber sehr wahrscheinlich nicht in einer Unternehmensumgebung.) Einen speziellen DC anzugeben und an ihm festzuhalten ist wichtig, denn es kann jederzeit vorkommen, dass sich zwei DCs nicht darüber einig sind, was für ein bestimmtes Objekt gilt. Bedenken Sie, dass Aktualisierungen in Active Directory verglichen mit einer SQL-Datenbank verhältnismäßig selten sind, aber dennoch kommen sie gelegentlich vor, und die Replikation nimmt eine gewisse Zeit in Anspruch. Außerdem ist nicht dokumentiert, ob ein DirSync-Cookie, das von einem DC ausgegeben wird, überhaupt für einen anderen DC Sinn ergibt! Dennoch sollten Sie den DC konfigurierbar machen und seinen Namen nicht in Ihrer Anwendung hartcodieren. Dies können Sie mit der Gruppenrichtlinie, einer Anwendungskonfigurationsdatei oder durch eine Methode wie z. B. System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain machen, mit der aktuelle Domänendaten für den Benutzer abgerufen werden.
So wie bei den Abfragen bei SqlDependency gibt es auch bei den Abfragen bei ADDependency einige Einschränkungen. Der Suchstamm muss der Stamm einer Partition sein. Beachten Sie in meinem Beispiel, wie ich den Suchstamm auf dc=contoso,dc=com festlege. Dies ist der Stamm der Anwendungspartition für das Verzeichnis unter contoso.com. Wenn ich stattdessen versucht hätte, mit cn=users,dc=contoso,dc=com meine Suche einzuschränken, hätte die Methode „Start“ eine Ausnahme ausgegeben. Das ist einfach eine Einschränkung des DirSync-Steuerelements. Stattdessen können Sie die beobachteten Daten mit den Argumenten „ldapFilter“ und „attrsToWatch“ einschränken.
Der Grund, warum das alles funktioniert, sind die Klassen „DirSyncRequestControl“ und „DirSyncResponseControl“ (siehe Abbildung 4). Damit wird das Synchronisierungscookie (sowie ein Satz Synchronisierungsoptionen) zum Server und zurück übergeben. Die wichtigste Option hier ist ObjectSecurity, die nur von Windows Server® 2003 unterstützt wird. Ohne diese müssen Sie über hohe Berechtigungen verfügen, um das DirSync-Steuerelement verwenden zu können. Mit dieser Option können Sie ein Konto mit niedrigen Berechtigungen verwenden und können dann nur Änderungen von Objekten sehen, die Sie normalerweise lesen dürfen, was wahrscheinlich genau das ist, was Sie wollen.
private void timerCallback(object o) 
{
    beginPollDirectory();
}

private void beginPollDirectory() 
{
    SearchRequest request = new SearchRequest(dnSearchRoot,
        ldapFilter, SearchScope.Subtree, attrsToWatch);
    request.Controls.Add(
        new DirSyncRequestControl(cookie, dirSyncOptions));
    conn.BeginSendRequest(request, requestTimeout,
        PartialResultProcessing.NoPartialResultSupport,
        endPollDirectory, null);
}

private void endPollDirectory(IAsyncResult asyncResult) 
{
    // harvest results
    SearchResponse response = 
        SearchResponse)conn.EndSendRequest(asyncResult);

    // grab the current search cookie
    foreach (DirectoryControl control in response.Controls) 
    {
        DirSyncResponseControl dsrc = control as DirSyncResponseControl;
        if (null != dsrc) cookie = dsrc.Cookie;
    }

    // if anything changed, notify any listeners
    if (response.Entries.Count > 0 && null != Changed)
    {
        Changed(this, new ChangedEventArgs(response));
    }
}

Beachten Sie die Reihenfolge der Ereignisse. Zuerst löst der Zeitgeber einen Aufruf an timerCallback aus. Diese Methode bewirkt, dass über LdapConnection.BeginSendRequest eine asynchrone Anforderung an den DC gesendet wird. Das Ergebnis wird in der Methode „endPollDirectory“ von ADDependency gesammelt, die dann ihr Changed-Ereignis aktiviert, das die Suchantwort in seine Ereignisargumente packt.
Die vollständige Implementierung von ADDependency finden Sie im Beispielcode zu diesem Artikel, der zum Download bereitsteht. Dort finden Sie einige Beispiele: eine Konsolenanwendung, die Benutzerobjekte im Verzeichnis überwacht und Änderungen daran ausgibt, eine ASP.NET-Anwendung, die eine benutzerdefinierte CacheDependency mit dem Namen „ADCacheDependency“ implementiert, und eine Konsolenanwendung, die als Agent für zufällige Änderungen dient und bei der alle N Sekunden zufällig die Jobtitel der Benutzer getauscht werden. (Haben Sie schon einmal in so einer Firma gearbeitet?) ADCacheDependency macht ein ASP.NET-Cacheelement ungültig, wenn eine Verzeichnisänderung erkannt wird, ähnlich, wie das bei SqlCacheDependency der Fall ist.
Bevor ich meinen Artikel abschließe, möchte ich noch auf das LDAP-Steuerelement „DirectoryNotification“ zu sprechen kommen, mit dem man sich in Zukunft einmal näher befassen sollte. Ich habe bis jetzt noch kein einziges Beispiel gesehen, in dem es verwendet wird. Aber scheinbar kann man damit eine asynchrone Suche konfigurieren, um Ergebnisse zu erhalten, wenn die Suche etwas Interessantes (Geändertes) zurückgibt. Es könnte sich als noch einfacheres Verfahren zum Erfassen von Änderungen erweisen, wenn alle zwischengespeicherten Objekte aus dem Verzeichnis neu gelesen werden sollen. Genau so funktioniert auch SqlCacheDependency in ASP.NET.

Weitere Informationen
Wenn dieser Artikel Ihr Interesse für die Programmierung von Verzeichnisdiensten geweckt hat, darf ich Ihnen einen Tipp geben: Besorgen Sie sich das Buch „The .NET Developer’s Guide to Directory Service Programming“ von Joe Kaplan und Ryan Dunn. Es gibt nicht viele Bücher auf dem Markt, die sich mit dem Programmieren in Active Directory aus verwaltetem Code befassen, aber dieses Buch habe ich immer griffbereit, wenn ich in dieser Richtung arbeite.
Also worauf warten Sie noch? Schreiben Sie selbst ein paar Codezeilen, und finden Sie heraus, welche Schätze in Ihrem lokalen Verzeichnis verborgen sind!

Senden Sie Fragen und Kommentare für Keith Brown in englischer Sprache an  briefs@microsoft.com.


Keith Brownist Mitgründer von Pluralsight, einem führenden Anbieter von Microsoft .NET-Schulungen. Er ist Autor des Pluralsight-Kurses „Applied .NET Security“ sowie mehrerer Bücher, einschließlich The .NET Developer's Guide to Windows Security, das als Druckversion und im Web erhältlich ist. Weitere Informationen finden Sie unter www.pluralsight.com/keith.

Page view tracker