Симон Робинсон - C# для профессионалов. Том II
В рассматриваемом примере поиска мы хотим найти все объекты пользователей в организационной единице Wrox Press, где свойство description содержит значение Author.
Сначала мы соединяемся с организационной единицей Wrox Press. Здесь начинается поиск. Создадим объект DirectorySearcher, где задан SearchRoot. Фильтр определяется как (&(objectClass=user)(description=Auth*)) для того, чтобы мы нашли все объекты типа user, где свойство description начинается с последовательности Auth, за которой может следовать что-то еще. Область поиска должна быть поддеревом, чтобы поиск происходил в порождаемых организационных единицах для Wrox Press:
DirectoryEntry de new DirectoryEntry();
de.Path = "LDAP://OU=Wrox Press, " + "DC=eichkogelstrasse, DC=local";
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot = de;
searcher.Filter = "(&(objectClass=user)(description=Auth*))";
searcher.SearchScope = SearchScope.Subtree;
В результате поиска мы хотим получить свойства name, description, givenName, и wWWHomePage.
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("description");
searcher. PropertiesToLoad.Add("givenName");
searcher.PropertiesToLoad.Add("wWWHomePage");
Мы готовы начать поиск. Однако, результат необходимо отсортировать. DirectorySearcher имеет свойство Sort, где можно задать SortOption. Первый аргумент конструктора SortOption определяет свойство, по которому будет проводиться сортировка, второй аргумент определяет направление сортировки. Перечисление SortDirection имеет значения Ascending и Descending.
Чтобы начать поиск, можно использовать метод FindOne() для нахождения первого объекта или FindAll(), чтобы найти все объекты. FindOne() вернет простой SearchResult, FindAll() вернет SearchResultCollection. Мы хотим получить всех авторов, поэтому используем FindAll():
searcher.Sort = new SortOption("givenName", SortDirection.Ascending);
SearchResultCollection Results = searcher.FindAll();
С помощью цикла foreach мы получаем доступ ко всем SearchResult в SearchResultCollection. SearchResult представляет один объект в кэше поиска. Свойство Properties возвращает ResultPropertyCollection, где мы получаем доступ ко всем свойствам и значениям по имени свойства и по индексу.
SearchResultCollection results = Searcher.FindAll();
foreach (SearchResult result in results) {
ResutPropertyCollection props = result.Properties;
foreach (string propName in props.PropertyNames) {
Console.Write(propName + ": ");
Console.WriteLine(props[propName][0]);
}
Console.WriteLine();
}
}
Если необходимо получить весь объект после поиска, то это также возможна. SearchResult имеет метод GetDirectoryEntry(), который возвращает соответствующую запись DirectoryEntry найденного объекта.
Результирующий вывод показывает начале списка всех авторов книги Professional C# с выбранными свойствами
Поиск объектов пользователей
Последнее приложение, которое будет создано в этой главе, это приложение Windows Forms. С его помощью можно найти все объекты пользователей домена с динамически определяемой строкой фильтра. Можно также задать свойства объектов пользователей, которые должны выводиться.
Интерфейс пользователя
Интерфейс пользователя выводит нумерованные шаги, помогая использовать приложение.
1. На первом шаге можно ввести имя пользователя, пароль и контроллер домена. Вся эта информация является необязательной. Если контроллер домена не вводится, то соединение работает со связыванием без сервера. Если отсутствует имя пользователя, то используется контекст безопасности текущего пользователя.
2. С помощью кнопки все имена свойств объекта User могут загружаться динамически в окно списка ListBoxProperties.
3. После загрузки имен свойств, можно выбрать свойства, которые должны выводиться. Режим SelectionMode окна списка задач как MultiSimple.
4. Можно ввести фильтр для ограничения поиска. Значение по умолчанию, которое задается в этом диалоговом окне, ищет все объекты пользователей: (objectClass=user).
5. Теперь можно начать поиск.
Получение именующего контекста схемы
Это приложение имеет только два метода обработки событий: первый метод — обработчик для кнопки загрузки свойств и второй — для запуска поиска в домене. В первой части мы динамически считываем свойства класса User из схемы для вывода его в интерфейсе пользователя.
В методе-обработчике buttonLoadProperties_Click() с помощью метода SetLogonInformation() имя пользователя, пароль и имя хоста считываются во время диалога и сохраняются в членах класса. Затем метод SetNamingContext() задает имя LDAP схемы и имя LDAP используемого по умолчанию контекста. Имя LDAP этой схемы используется в вызове SetUserProperties() для задания свойств в окне списка:
private void buttonLoadProperties_Click(object sender, System.EventArgs e) {
try {
SetLogonInformation();
SetNamingContext();
SetUserProperties(schemaNamingContext);
} catch (Exception ex) {
MessageBox.Show("Cheek your inputs! " + ex.Message);
}
}
protected void SetLogonInformation() {
username =
(textBoxUsername.Text == "" ? null :
textBoxUsername.Text);
password =
(textBoxPassword.Text == "" ? null :
textBoxPassword.Text);
hostname = textBoxHostname.Text;
if (hostname ! = "") hostname += "/";
}
Во вспомогательном методе SetNamingContext() мы используем корень дерева каталога для получения свойств сервера. Мы заинтересованы в значениях двух свойств: SchemaNamingContext.
protected string SetNamingContext() {
using (DirectoryEntry de = new DirectoryEntry()) {
string path = "LDAP://" + hostname + "/rootDSE";
de.Username = username;
de.Password = password;
de.Path = path;
schemaNamingContext =
de.Properties["schemaNamingContext"][0].ToString();
defaultNamingContext =
de.Properties["defaultNamingContext"][0].ToString();
}
}
Получение имен свойств класса пользователя
У нас есть имя LDAP для доступа к схеме. Можно использовать его для доступа к каталогу и для считывания свойств. Мы заинтересованы не только в свойствах класса User, но также в свойствах базовых классов для User: Organizational-Person, Person и Top. В этой программе имена базовых классов жестко закодированы. Можно было бы прочитать базовый класс динамически с помощью атрибута subClassOf. Метод GetSchemaProperties() возвращает строковый массив со всеми именами свойств определенного типа объектов. Все имена свойств собраны в объекте properties типа StringCollection:
protected void SetUserProperties(string schemaNamingContext) {
StringCollection properties = new StringCollection();
string[] data = GetSchemaProperties(schemaNamingContext, "User");
properties.AddRange(GetSchemaProperties(schemaNamingContext, "Organizational-Person"));
properties.AddRange(GetSchemaProperties(schemaNamingContext, "Person"));
properties.AddRange(GetSchemaProperties(schemaNamingContext, "Top"));
listBoxProperties.Items.Clear();
foreach (string s in properties) {
listBoxProperties.Items.Add(s);
}
}
В методе GetSchemaProperties() мы снова обращаемся к активному каталогу. В этот раз вместо rootDSE используется имя LDAP в схеме, которое мы обнаружили ранее. Свойство systemMayContain содержит коллекцию всех атрибутов, которые допустимы в классе objectType:
protected string[] GetSchemaProperties(string schemaNamingContext, string objectType) {
string [] data;
using (DirectoryEntry de = new DirectoryEntry()) {
de.Username = username;
de.Password = password;
de.Path = "LDAP://" + hostname + "/CN=" + objectType + "," + schemaNamingContext;
DS.PropertyCollection properties = de.Properties;
DS.PropertyValueCollection values = properties["systemMayContain"];
data = new String[values.Count];
values.CopyTo(data, 0);
}
return data;
}
Одно интересное замечание к этому коду: в приложении Windows Forms класс PropertyCollection пространства имен System.DirectoryServices имеет конфликт имен с System.Data.PropertyCollection. Поскольку писать такие длинные имена как System.DirectoryServices.PropertyCollection не всегда хочется, то с целью разрешения конфликта имя пространства имен можно сократить с помощью
namespace DS = System.DirectoryServices;
Именно отсюда появляется DS.PropertyCollection.
Шаг 2 приложения завершен. Окно списка (listbox) содержит все имена свойств объектов User.
Поиск объектов User
Обработчик для кнопки поиска вызывает вспомогательный метод FillResult():
private void buttonSearch_Click(object render, System.EventArgs e) {
try {
FillResult();
} catch (Exception ex) {
MessageBox.Show("Check your input: " + ex.Message)
}
}
В методе FillResult() выполняется обычный поиск в полном домене активного каталога, как мы видели раньше. SearchScope задается как Subtree, Filter для строки мы получаем из TextBox, а свойства, которые должны быть загружены в кэш, задаются значениями, которые пользователь выбирает в окне списка