Is it possible to use the System.DirectoryServices.AccountManagement
library and the PrincipalSearcher
class to retrieve a custom attribute for all Principals returned from the call to FindAll()
?
I'm currently using this example: http://msdn.microsoft.com/en-us/library/bb552835%28v=vs.90%29.aspx.
However, when accessing my custom property/attribute, it seems to be making an additional trip to the AD store. I would like it to eagerly load this property at the initial call to FindAll()
.
Yes, it is possible to eagerly load the custom attribute for all principals at the initial
call to FindAll(). You only have to specify your custom attributes as described
in the Microsoft sample using [DirectoryProperty("YOUR_PROP_NAME")] attribute.
By accessing the underlying DirectorySearcher's property PropertiesToLoad using the GetUnderlyingSearcher() method
of the UserPrincipal class you can see that your custom attribute is included in the collection of
properties to load. You can examine the PropertiesToLoad collection in the debugger.
On my machine the collection contains a total of 68 properties.
And here begins the problem and the performance penalty. The more properties
are included in this collection the more round trips to Active Directory are necessary to retrieve them.
I did some performance tests:
Using the Microsoft sample to retrieve 200 InetOrgPerson objects took about 500ms.
Using the DirectorySearcher class directly and only requesting the properties of interest
took only 70ms (see sample below).
using (DirectoryEntry e = new DirectoryEntry("LDAP://server10/CN=users,DC=treyresearch,DC=net",
"treyresearch\\administrator", "P@$$W0rd", AuthenticationTypes.FastBind | AuthenticationTypes.Secure))
{
using (DirectorySearcher ds = new DirectorySearcher(e, "(&(objectCategory=inetorgperson)(logonCount=0))"))
{
ds.SearchScope = SearchScope.OneLevel;
ds.PropertiesToLoad.Clear();
ds.PropertiesToLoad.Add("logonCount");
ds.PropertiesToLoad.Add("sAMAccountName");
Stopwatch sw = new Stopwatch();
sw.Start();
int countPerson = 0;
using (SearchResultCollection searchResultCol = ds.FindAll())
{
foreach (SearchResult sr in searchResultCol)
{
ResultPropertyValueCollection propCol = sr.Properties["logonCount"];
if (propCol.Count > 0)
{
countPerson++;
object cou = propCol[0];
}
}
sw.Stop();
Console.Out.WriteLine(sw.ElapsedMilliseconds);
Console.Out.WriteLine(countPerson);
}
}
}
By the same token I've used objectCategory in the search filter instead
of objectClass because objectCategory is a so called indexed property.
Accessing indexed properties is faster than accessing non indexed properties.
Furthermore I've specified AuthenticationTypes.FastBind to improve
performance.
To further improve performance see this article on MSDN describing how to create efficient search queries.
To summarize, using DirectorySearcher class and specifing only the properties you are
interested in creatly improves the performance of your search (reducing round trips to Active Directory).
Hope, this helps.
Yes, you can; Cast GetUnderlyingObject() for each Principal object to DirectoryEntry, then you may access the custom attribute like oDE.Properties[YOUR_CUSTOM_ATTRIBUTE].Value.