C# helper function to enumerate and retrieve large LDAP multi-valued attributes.
/// <summary>
/// Listing 6.8 - Range Expansion method used for large attributes.
/// </summary>
/// <param name="entry"></param>
/// <param name="attribute"></param>
/// <returns></returns>
/// <example>
/// <code>
/// ArrayList members = RangeExpansion(group, "member");
/// </code>
/// </example>
/// <remarks>Addison Wesley - The .NET Developer's Guide to Directory Services Programming</remarks>
public static ArrayList RangeExpansion(DirectoryEntry entry, string attribute)
{
ArrayList al = new ArrayList(5000);
int idx = 0;
// zero based index, so less 1
int step = entry.Properties[attribute].Count - 1;
string range = String.Format("{0};range={{0}}-{{1}}", attribute);
string currentRange = String.Format(range, idx, step);
DirectorySearcher ds = new DirectorySearcher(
entry,
String.Format("({0}=*)", attribute),
new string[] { currentRange },
System.DirectoryServices.SearchScope.Base
);
bool lastSearch = false;
SearchResult sr = null;
while (true)
{
if (!lastSearch)
{
ds.PropertiesToLoad.Clear();
ds.PropertiesToLoad.Add(currentRange);
sr = ds.FindOne();
}
if (sr != null)
{
if (sr.Properties.Contains(currentRange))
{
foreach (object dn in sr.Properties[currentRange])
{
al.Add(dn);
idx++;
}
// our exit condition
if (lastSearch)
{
break;
}
currentRange = String.Format(range, idx, (idx + step));
}
else
{
// one more search
lastSearch = true;
currentRange = String.Format(range, idx, "*");
}
}
else
{
break;
}
}
return al;
}
//
// Unit test
// --------------------------------------------------------------------------
/// <summary>
/// Shows how to use Listing 6.8
/// </summary>
[Test]
public void EnumerateLargeAttributes()
{
// point this to a big group in your directory (TestUtils.Settings.DefaultPartition == "dc=domain,dc=com")
DirectoryEntry bigGroup = TestUtils.CreateDirectoryEntry("CN=Ranged Group,CN=Roles," + TestUtils.Settings.DefaultPartition);
using (bigGroup)
{
foreach (string s in RangeExpansion(bigGroup, "member"))
{
Console.WriteLine(s);
}
}
}
//
// TestUtils.cs
// --------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using DotNetDevGuide.DirectoryServices.Configuration;
namespace DotNetDevGuide.DirectoryServices
{
class TestUtils
{
static LdapSettings settings;
static TestUtils()
{
settings = System.Configuration.ConfigurationSettings.GetConfig("LdapSettings") as LdapSettings;
if (settings == null)
throw new InvalidOperationException("Invalid Configuration Specified");
}
public static LdapSettings Settings
{
get { return settings; }
}
/// <summary>
/// Retrieves a DirectoryEntry using configuration data
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static DirectoryEntry CreateDirectoryEntry(string path)
{
//I am intentionally not checking a bunch of stuff for input validation
//because this is just test harness and I don't care if you screw it up too much
AuthenticationTypes bindingAuth = AuthenticationTypes.Secure;
switch (settings.Protection)
{
//typical for non-SSL ADAM - so assuming server specified
case ConnectionProtection.None:
bindingAuth = AuthenticationTypes.None;
break;
//typical for AD
case ConnectionProtection.Secure:
bindingAuth = AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.Signing;
break;
//typical for SSL-ADAM (assuming server specified as well)
case ConnectionProtection.SSL:
bindingAuth = AuthenticationTypes.SecureSocketsLayer;
break;
}
if (!String.IsNullOrEmpty(settings.Server))
bindingAuth = bindingAuth | AuthenticationTypes.ServerBind;
return new DirectoryEntry(
BuildAdsPath(path),
settings.Username,
settings.Password,
bindingAuth
);
}
/// <summary>
/// Creates a DirectoryEntry from the DefaultPartition defined in config
/// </summary>
/// <returns></returns>
public static DirectoryEntry GetDefaultPartition()
{
return CreateDirectoryEntry(settings.DefaultPartition);
}
/// <summary>
/// Simple method to find and return a user using the CN name and searching the
/// defaultNamingContext defined in config.
/// </summary>
/// <param name="userRDN"></param>
/// <returns></returns>
public static DirectoryEntry FindUserByCN(string userRDN)
{
using (DirectoryEntry searchRoot = GetDefaultPartition())
{
DirectorySearcher ds = new DirectorySearcher(
searchRoot,
String.Format("(cn={0})", userRDN),
new string[] { "cn" },
SearchScope.Subtree
);
SearchResult sr = ds.FindOne();
return (sr != null) ? sr.GetDirectoryEntry() : null;
}
}
/// <summary>
/// We use this take into account whether we have a server or not
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string BuildAdsPath(string path)
{
return String.Format("LDAP://{0}{1}", String.IsNullOrEmpty(settings.Server) ? String.Empty : settings.Server + "/", path);
}
}
}