AccountManagement, PrincipalContext, Principals, And Performance

Often times in trivial AD operations using the *.AccountManagement namespace there is a performance hit in comparison to using ASQ approaches. This has been pretty frustrating to me in the past, as detailed here. In my journeys with these objects though I have determined a better approach. Let’s take the examples from the previous post two methods, GetUser and Get Group. To make these more efficient, we are going to use a combination of PrincipalContext, *Principal, and PrincipalSearcher objects along with a materialized Dictionary object in order to speed things up a bit.

Enough with the filler, let’s look at some code. What we are going to do is create a new PrincipalContext (the static method to hydrate this is discussed in the aforementioned post), then create a new *Principal object consuming the hydrated PrincipalContext in the constructor. The *Principal object will subsequently use the SamAccountName property with a wild card as a search parameter. This *Principal object is then passed into the constructor of a new PrincipalSearcher object so the principal search results can be returned using stuff like FindAll and FindOne. Once the PrincipalSearcher is well formed, we execute a FindAll, ToList to a strongly typed collection with IQueryable support, which allows us to execute a Where clause. The clause leveraged a Regular Expression (ala Regex.IsMatch), and the results are collated in a Dictionary(Of TKey, TValue) according to a specified key selector function. Once the Dictionary is built, a simple if clause is inserted which allows us to find, and return, the object we want! Comparative times of query have shown this cuts down on query time BY 5 TIMES!!!!!!! 

Firstly, the modified GetUser method:

[csharp]

public static UserPrincipal GetUser(string userName)
{
UserPrincipal userPrincipal = null;
SPSecurity.RunWithElevatedPrivileges(() =>
{
PrincipalContext principalContext = GetUserPrincipalContext();
var search = new UserPrincipal(principalContext);
search.SamAccountName = userName + ‘*’;
var ps = new PrincipalSearcher(search);
var pr = ps.FindAll().ToList().Where(a =>
Regex.IsMatch(a.SamAccountName, String.Format(@”{0}\D”, userName))).
ToDictionary(a => a.SamAccountName);
pr.Add(userName, Principal.FindByIdentity(principalContext, IdentityType.SamAccountName, userName));
if (pr.ContainsKey(userName))
{
userPrincipal = (UserPrincipal)pr[userName];
}
});
return userPrincipal;
}

[/csharp]

And the GetGroup method:

[csharp]

public static GroupPrincipal GetGroup(string groupName)
{
GroupPrincipal groupPrincipal = null;
SPSecurity.RunWithElevatedPrivileges(() =>
{
PrincipalContext principalContext = GetGroupPrincipalContext();
var search = new GroupPrincipal(principalContext);
search.SamAccountName = groupName + ‘*’;
var ps = new PrincipalSearcher(search);
var pr = ps.FindAll().ToList().Where(a =>
Regex.IsMatch(a.SamAccountName, String.Format(@”{0}\D”, groupName))).
ToDictionary(a => a.SamAccountName);
pr.Add(groupName, Principal.FindByIdentity(principalContext, IdentityType.SamAccountName, groupName));
if (pr.ContainsKey(groupName))
{
groupPrincipal = (GroupPrincipal) pr[groupName];
}
});
return groupPrincipal;
}

[/csharp]

The first thing you should notice is that these could be placed in a static method of the PrincipalContext as an extension, which would be a whole lot more succint, but I was just happy I got it to work. I’ll leave that up to someone else.

Share

PrincipalContext Objects And Performance

Using the new namespaces for AD stuff is really nice, but MSFT really dropped the ball when building PrincipalContext objects. PrincipalContext objects are used to encapsulate the server or domain which are going to be subject to the AD operations, so is hydrated when building UserPrincipal or GroupPrincipal objects. So, for example ,they are generally put into static methods such as:

[csharp]

public static PrincipalContext GetGroupPrincipalContext()
{
PrincipalContext principalContext = null;
SPSecurity.RunWithElevatedPrivileges(() => principalContext = new PrincipalContext(ContextType.Domain, ““, ““));
return principalContext;
}

[/csharp]

so that we can use it later for user and group operations:

[csharp]

public static UserPrincipal GetUser(string userName)
{
UserPrincipal userPrincipal = null;
SPSecurity.RunWithElevatedPrivileges(() =>
{
PrincipalContext principalContext = GetUserPrincipalContext();
userPrincipal = UserPrincipal.FindByIdentity(principalContext, userName);
});
return userPrincipal;
}

public static GroupPrincipal GetGroup(string groupName)
{
GroupPrincipal groupPrincipal = null;
SPSecurity.RunWithElevatedPrivileges(() =>
{
PrincipalContext principalContext = GetGroupPrincipalContext();
groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, groupName);
});
return groupPrincipal;
}

[/csharp]

However, it is important that if you experience performance issues to consider two things. I have noticed that with declarative domain controller specific rather than relying on the round-robin default fashion is pretty effective. Otherwise, you are going to be limited to using Attribute Scope Query (ASQ). This involves using the DirectoryEntry and DirectorySearcher objects.

EDIT:

An update to this post is available here.

Share