Because I needed a way to get users from Active Directory to sync them with another application (CRM). I started out writing Directory Services (LDAP) Queries. I started thinking: "Can't this be done with LINQ?", after all in C# and the .NET Framework 3.0, 3.5 we now have the Language Integrated Query possibilities in our programming toolkit. With LINQ you should be able to query all collections, as long as they impement the IQueryable interface. So why not Active Directory?
When you search: "LINQ to AD" with Google. You''ll find the following link: http://www.codeplex.com/LINQtoAD.
Bart de Smet is a belgian developer, who works for Microsoft. He is the one that made LINQ to AD. Here is a serie blogposts he wrote about this topic.
LINQ to AD is a query provider for LINQ that's capable of talking to Active Directory (and other LDAP data sources potentially) over LDAP.
Ofcourse LINQ To AD is a wrapper around System.DirectoryServices.
He wrote the BdsSoft.DirectoryServices.Linq, the LINQ to AD implementation, if you want to use LINQ To AD you need to add a reference to this assembly and you need to reference the Active DS Type Library (ActiveDs.dll). The 'activeds.dll' is a module that contains functions and object methods, or COM components, for the Active Directory Services Interfaces (ADSI) API.
One of the reasons System.DirectoryServices is so powerfull, is because you can still access native ADSI interfaces by using the NativeObject method. NativeObject will return the IADs interface of the specific type of object.
To use the NativeObject method, you'll need to add a reference to the ActiveDs.dll library. I need to get all users belonging to a particular AD Group and sync these to my application.
All I need to do is: Add a reference the BdsSoft.DirectoryServices.Linq assembly and the Active DS Type Library (ActiveDs.dll).
Next I created a method that must Retrieve all users member of a group in the Active Directory.
The method will have two parameters:
-
domain, a string that will hold the Active Directory domain name
-
groupName, a string that will contain the name of the group in AD the users belong to
The method will return a List of type User, User is a custom object that represents a user and is called: "RetrieveADUsersInGroup";
Here a listing of the signature:
1: private List<User> RetrieveADUsersInGroup(string domain, string groupName)
2: {
3: // Put code here
4: }
Listing 1
In the method, an instance of the System.DirectoryServices.DirectoryEntry object needs to be instantiated.
I pass a string to the constructor ('LDAP://'), so the DirectoryEntry instance is bound to the node in Active Directory Domain Services that is located at this specific path.
1: DirectoryEntry rootOfDirectory = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Listing 2
Now we take this DirectoryEntry object (rootOfDirectory) and pass it to the constructor of the object DirectorySource<T> (which implements the IQueryable<T> interface), together with the enum System.DirectoryServices.SearchScope (defines the scope for a Directory search).
We take SearchScope.Subtree, which means the whole SubTree, including Base object and all child objects.
Here the signature of the method that is called and lives inside the BdsSoft.DirectoryServices.Linq assembly.
1: public class DirectorySource<T> : IQueryable<T>, IDirectorySource
2: {
3: // Code here
4: }
Listing 3
Now we use the keyword: 'var', var is a way to declare variables in C# 3.0 and up, that uses implicit typing. Keep in mind that var is not the same as 'object', or the JavaScript 'var' datatype as it’s actually strongly typed, but inferred from whatever the value is being assigned.
The proces of creating datatypes on the fly is called: 'projection'. Var is projecting the datatype from the Query.
1: var groups = new DirectorySource<Group>(rootOfDirectory, SearchScope.Subtree);
2: var listOfAllGroups = from grp in groups
3: where grp.Name == groupName
4: select new { grp.Name, MemberCount = grp.Members.Length, grp.Members };
Listing 4
On line 1 'var groups' the complete root directory is inferred as BdsSoft.DirectoryServices.Linq.DirectorySource<T>, where 'T' is of type 'ActiveDirectory.Group', my own class that represents an ActiveDirectory group.
From line 2 on the LINQ syntax is used to get all groups from the Collection where the name is equal to the value in methods string parameter 'groupName'. From these groups or group the Name, MemberCount and Members are taken and returned by the query and assigned to the listOfAllGroups variable.
I wanted to map the result from this LINQ Query directly to my User type, instead of needing to loop through every result and map each property one by one. What provides an opportunity to show that you can map your own types to the Query's result, after all you can predict the projection result.
First the code that retrieves the users:
1: var UserFromAD = new DirectorySource<Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User>(rootOfMemberDirectory, SearchScope.Subtree);
2:
3: IEnumerable<User> usersFromQuery = from adUser in UserFromAD
4: select new User
5: {
6: Firstname = adUser.FirstName,
7: Lastname = adUser.LastName,
8: AccountName = adUser.AccountName,
9: Email = adUser.Email,
10: PhoneNumber = adUser.TelephoneNumber,
11: Fax = adUser.Fax,
12: AddressStreet = adUser.AddressStreet,
13: City = adUser.City,
14: State = adUser.State,
15: Country = adUser.Country
16: };
Listing 5
On line 3 the variable 'usersFromQuery' is of type 'IEnumerable of type User' (IEnumerable<User>). IEnumerable needs to be implemented if we do not want to use 'var'. On line 4 the part 'select new' is extended to 'select new User' in this way we project our own User type. In the body of the Query you see every field is mapped to a property of the User type through field name/value pairs. Under the covers var infers the field names, field values, and field data types and creates an anonymous type with the fields we project.
Summary:
- IEnumerable of type ...
- select new ...
- map through field name/value pairs
Here a listing with the complete code from the Method:
1: private static List<User> RetrieveADUsersInGroup(string domain, string groupName)
2: {
3: DirectoryEntry rootOfDirectory = new DirectoryEntry(string.Format("LDAP://{0}", domain));
4:
5: var groups = new DirectorySource<Group>(rootOfDirectory, SearchScope.Subtree);
6: var listOfAllGroups = from grp in groups
7: where grp.Name == groupName
8: select new { grp.Name, MemberCount = grp.Members.Length, grp.Members };
9:
10: List<User> userList = new List<User>();
11:
12: foreach (var currentGroup in listOfAllGroups)
13: {
14: foreach (var member in currentGroup.Members)
15: {
16: DirectoryEntry rootOfMemberDirectory = new DirectoryEntry(GetLDAPPath(domain) + "/" + member);
17: var UserFromAD = new DirectorySource<Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User>(rootOfMemberDirectory, SearchScope.Subtree);
18:
19: IEnumerable<User> usersFromQuery = from adUser in UserFromAD
20: select new User
21: {
22: Firstname = adUser.FirstName,
23: Lastname = adUser.LastName,
24: AccountName = adUser.AccountName,
25: Email = adUser.Email,
26: PhoneNumber = adUser.TelephoneNumber,
27: Fax = adUser.Fax,
28: AddressStreet = adUser.AddressStreet,
29: City = adUser.City,
30: State = adUser.State,
31: Country = adUser.Country
32: };
33:
34: userList.AddRange(usersFromQuery);
35: }
36: }
37:
38: return userList;
39: }
Listing 6
LINQ is a layer of abstraction over the way we query data. It is in the case of LINQ to AD a nice beginning to get rid of the LDAP Querying. LINQ overall provides a way to query Databases, Collections, Active Directory, XML and every Datasource for that matter in the same way from now on. In the LINQ to AD case the fact that had to query all groups and then loop through these groups to get the members of the groups is a big shortcoming. I had hoped to have to write one query that did all the work and returned a list of users. Still because the datasource is DirectoryServices and Active DS I can understand that their limits are in the way of making this possible. Still I hope in the future this will be possible.
I do like to work with LINQ and will try to learn more about it's inner workings. I definitely see a shift in paradigm, no more static language only in C# and I think change like this is exiting!
Henry Cordes
My thoughts exactly...