In This Chapter
LINQ to Outlook
Querying Active Directory with Straight C# Code
LINQ to Active Directory
Querying Active Directory with LINQ
“Find a job you like and you add five days to every week.”
—H. Jackson Brown, Jr.
A lot of data comes from traditional relational databases. From these sources, it is plausible to choose SQL or LINQ for similar kinds of tasks. A lot of other data comes from places other than relational databases. Here, LINQ shines as the best alternative.
Anything that serves data as an enumerable collection can be queried with LINQ. For example, Microsoft Outlook mailboxes, contacts, and calendar items can be queried because the Outlook object model exposes these elements as collections. This chapter provides an Outlook example. Active Directory, unfortunately, has its own query language—probably an oddity that shouldn’t be. However, the .NET Framework supports implementing a custom IQueryable
provider. Queryable providers let us conceptually treat Active Directory like a LINQ-queryable repository. This chapter also demonstrates how to create a provider for LINQ. You can use the provider example to create providers for other applications such as SharePoint.
OLE (Object Linking and Embedding) automation is a pretty old technology. It is part of what is collectively called COM (Component Object Model). Many Windows tools have their own exposed object model. For example, Microsoft PowerPoint, Microsoft Excel, Microsoft Access, and Outlook all have an object model that you can code against. Further, because these object models contain data as collections, you can use LINQ to query these resources. (For more on Outlook programming with Visual Basic for Applications [VBA], check out Patricia DiGiacomo’s book Special Edition Using Microsoft Office Outlook 2007 from Que.)
Listing 12.1 uses LINQ to read Outlook’s Inbox and contacts. Any email address that is in the Inbox and not in the contactList
is added as a contact. (As long as you clean junk mail out of your Inbox before you run this code, it’s a great way to update your contact list.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Outlook;
using System.Runtime.InteropServices;
namespace QueryOutlook
{
class Program
{
static void Main(string[] args)
{
_Application outlook = new Application();
if (outlook.ActiveExplorer() == null)
{
Console.WriteLine(“open outlook and try again”);
Console.ReadLine();
return;
}
MAPIFolder inbox =
outlook.ActiveExplorer().Session
.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
MAPIFolder contacts =
outlook.ActiveExplorer().Session
.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
var emails = from email in inbox.Items.OfType<MailItem>()
select email;
var contactList = from contact in contacts.Items.OfType<ContactItem>()
select contact;
var emailsToAdd = (from email in emails
let existingContacts = from contact in contactList
select contact.Email1Address
where !existingContacts.Contains(email.SenderEmailAddress)
orderby email.SenderName
select new { email.SenderName, email.SenderEmailAddress }).Distinct();
Array.ForEach(emailsToAdd.ToArray(), e =>
{
ContactItem contact = ContactItem)outlook.CreateItem(
OlItemType.olContactItem);
contact.Email1Address = e.SenderEmailAddress;
contact.Email1DisplayName = e.SenderName;
contact.FileAs = e.SenderName;
try
{
contact.Save();
}
catch (COMException ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
Console.WriteLine(“Adding: {0}”, e.SenderName);
}
);
Console.ReadLine();
}
}
}
To reproduce the example, add a reference to the Microsoft.Office.Interop.Outlook
assembly (see Figure 12.1). (The example uses Outlook 2007 but Outlook 2003 should work too.) Then, you need to create an instance of Outlook. Listing 12.1 is written in such a way that it anticipates that Outlook is running at present.
The statement
Application outlook = new Application();
creates an instance of the Outlook application. The two statements that begin with MAPIFolder
get the Inbox and Contacts folders, respectively. The MAPIFolder
class’s Items
property realizes the IEnumerable
interface and that’s all you need to be able to use LINQ. The first two LINQ queries use the OfType
generic conversion operator to convert the Items
collections into queryable sequences of MailItem
and ContactItem
objects, respectively.
After the code obtains the email items from the Inbox MAPIFolder
and the contacts from the contacts MAPIFolder
, you can use both sequences to find only email addresses in the Inbox that aren’t in the Contacts folder. The query beginning with the anonymous type emailsToAdd
finds the email addresses.
The anonymous type emailsToAdd
is a collection of projections containing the sender’s name and the sender’s email address using the Distinct
set extension method to exclude duplicates. The range value email is the iterator for each email address. The range value existingContacts
is defined with a let
clause that selects existing contact email addresses. The where
clause predicate excludes email addresses that are already in the Contacts folder and the results are ordered.
Finally, the Array.ForEach
method and compound Lambda Expression representing the Action
creates new contact items for new email addresses filling in the sender’s name, email address, and the FileAs
field and saves the result.
Unfortunately, because of unscrupulous twits and miscreants, Outlook will display the Allow access dialog box (see Figure 12.2). Check the Allow Access For check box (1 minute should be plenty of time) and click Yes to permit the code to update your Contacts folder.
Active Directory is pretty much a de facto standard for authentication and authorization. Some applications might not use it. For instance, some public Internet sites don’t manage subscribers with Active Directory, but for applications and internal web applications, it’s a great tool for storing user information and defining roles.
A couple of minor drawbacks to using Active Directory are that it uses its own cryptic query language and the schema names are a little goofy—dn for distinguished name, ou for organizational unit, and similar abbreviations don’t make querying Active Directory intuitive. You can remedy that, however, which you learn to do in the next section, “LINQ to Active Directory.” First, let’s look at some straight C# code that queries Active Directory (see Listing 12.2).
private static readonly string LDAP =
“LDAP://sci.softconcepts.com:3268/DC=softconcepts,DC=COM”;
private static DirectoryEntry activeDirectory = new DirectoryEntry(LDAP);
private static readonly string user = “xxxxxxxx”;
private static readonly string password = “xxxxxxx”;
using(DirectoryEntry entry = new
DirectoryEntry(LDAP, user, password, AuthenticationTypes.None))
{
string filter = “(&(objectClass=person))”;
DirectorySearcher searcher = new DirectorySearcher(entry, filter);
var results = searcher.FindAll();
foreach (SearchResult result in results)
{
Console.WriteLine(result.Properties[“Name”][0]);
Console.WriteLine(Environment.NewLine);
}
}
Console.ReadLine();
Active Directory is based on the Lightweight Directory Access Protocol called LDAP. LDAP, like many protocols, is based on whitepapers (or RFCs, Request for Comments). There are several RFCs for Active Directory. For example, RFC 3377 is the LDAP version 3 technical specification and RFC 2254 is the specification for LDAP search filters. You can find these specifications by Googling “LDAP RFC” if you are interested.
RFCs for standardized technologies are written by interested parties, generally software and hardware vendors for computer technologies. The idea is that stakeholders who will be using these technologies want a say in the details of the specification. (Unfortunately, sometimes committee designs produce oddities like DN, OU, and DC and foist these decisions on the rest of us who are just trying to remain sane.)
Active Directory is an implementation of the LDAP specification from Microsoft. The basic implementation in conjunction with .NET Framework’s System.DirectoryServices
requires that you create an instance of a DirectoryEntry
using a uniform resource identifier (URI) with the LDAP:// moniker, the path, the port, and some parameters to the Active Directory instance. In the LDAP constant string, the path to an Active Directory server is included along with the arguments for the DC (domain component). (There are other options, but whole books have been written on Active Directory. For more information, see Gil Kirkpatrick’s book Active Directory Programming from Que.)
Having defined the connection URI and created an instance of a DirectoryEntry
, you can now define a DirectorySearcher
with a search filter. In the example, the filter is grabbing everything defined as a person. The results will be people stored in Active Directory.
By passing in a test name and some minor modifications, you can use the code in Listing 12.2 to authenticate application users. The catch is that Active Directory is one of those technologies that require specialized knowledge. If you implement an Active Directory provider for .NET, you can make Active Directory easier to use without requiring that every developer learn how to string together Active Directory filters.
.NET supports implementing an IQueryable
provider. IQueryable
is an interface that you can inject between some repository or data source that you want to query using LINQ. Basically, it works like this: You write a LINQ query, LINQ calls your custom IQueryable
object handing the LINQ query off, and you write code (one time) that converts the LINQ query to something the repository can understand. For instance, you want to write LINQ queries but Active Directory still wants filters in the predetermined format. The custom provider performs the conversion.
This takes some doing because LINQ and Active Directory are substantially different, but the important thing is you only need to do this once and then everyone benefits. Before we begin, go get a cup of coffee. (I will wait.)
Back? Good.
When you use DirectoryServices
, DirectoryEntry
objects, and DirectorySearcher
objects, the data you get back is SearchResult
objects. The challenge is that the SearchResult
objects are key and value pairs, necessitating writing code like result.Properties[“Name”][0]
to get the Name attribute. In addition to being unsightly, this code is very natural to read or write. By creating the provider, you can query Active Directory with clearly defined entities and LINQ, resulting in a much more natural looking query.
LINQ Provider
The amount of code to create an IQueryable
provider is much longer than is generally convenient to include in a book. The total listing is a couple of thousand lines. However, this topic is really the kind of juicy material we want in an Unleashed book. Also, because the provider is intertwined with the framework—calls are made from the framework to the provider—it is a little hard to figure out what is going on just by looking at the code.
To solve the problem of space, enough of the code is listed to make it clear how each piece works, but some of the code is elided. All of the code is available for download from Sams on the book’s product page (www.informit.com/title/9780672329838). In addition, to help provide an overview of the elements, a couple of class diagrams and a sequence diagram with a brief explanation of how to read the diagrams are also provided later in this chapter. Finally, all of the pieces are described in their own section, including the relevant code, models, and other visualizations.
The following is an overview of the steps you’ll need to follow and the code to create to implement the Active Directory provider for LINQ:
1. Create a solution with class library and console projects. The class library will contain the provider, and the console application is for testing the provider.
2. Code a class that implements IQueryable<T>
or IOrderedQueryable<T>
. You’ll implement IOrderedQueryable<T>
so that your provider supports orderby
clauses.
3. Define entities that map to Active Directory schema elements, such as User
.
4. Define the context class, the IQueryProvider
that is used as the entry throughway from LINQ queries to Active Directory.
5. Define the code that translates LINQ expression trees into Active Directory filters and executes those filters against the Active Directory server.
6. Finally, define Active Directory attributes that facilitate mapping programmer-friendly names to elements in the Active Directory schema.
The last step is to write some LINQ queries and test the provider. Let’s begin with the IQueryProvider
interface.
The IQueryProvider
interface defines four methods: two versions of CreateQuery
and two versions of Execute
. In this sample, direct execution isn’t supported and entity types are defined, so it just implements the generic version of CreateQuery
. Listing 12.3 shows the complete listing of the DirectoryQueryProvider
.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace LinqToActiveDirectory
{
public class DirectoryQueryProvider : IQueryProvider
{
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new DirectoryQuery<TElement>(expression);
}
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
throw new NotImplementedException();
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
}
}
When the framework is ready to process the query, it asks the IOrderedQueryable
provider for the IQueryProvider
(which is the class in Listing 12.3) and CreateQuery
is called to begin the transmogrification of the LINQ query to an Active Directory filter and search operation.
The role played by the IQueryProvider
is shown in the redacted part of the sequence diagram shown in Figure 12.3. The sequence diagram is read from top left to bottom right. The classes are the boxes across the top and the lines are method calls. The start of the line is the class containing the call and the arrow points to the class implementing the method. The convention of get_name
is used to indicate a property call, as in get_Provider
. (Property calls are really method calls under the hood.)
Without getting too far ahead of ourselves here, the goal is for the framework to get an instance of the translator (the provider) that converts LINQ query elements into analogous calls to the source of the data; in this case, Active Directory. Because Microsoft has no way of knowing all of the future possible providers people will want, they have to use interfaces to construct a common pattern for implementing providers in general.
The next piece of code defines is the IQueryable
class. Well, we will actually define a class that implements IOrderedQueryable
, which inherits from IQueryable
. IQueryable
is the interface that has all of those neat extension methods like Where<T>
that were introduced as the underpinnings for LINQ. We want IOrderedQueryable
because it inherits from IQueryable
so we can use Select<T>
and Where<T>
, but IOrderedQueryable
is the interface that is the return type for the extension method OrderBy
, which permits us to use orderby
clauses with ascending and descending modifiers. Here is the definition of the OrderBy
method in System.Linq
:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
IComparer<TKey> comparer
)
IOrderedQueryable
requires that you implement a GetEnumerator
method and the properties ElementType
, Expression
, and Provider
. Listing 12.4 contains the complete listing for the IOrderedQueryable
class, including a TextWriter
property that lets you bind the Console.Out
stream (the console window) to the DirectorySource
class. The TextWriter
property makes it easy to send information from the provider to the console (which was a clever trick that Bart De Smet employed in his example).
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.DirectoryServices;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
namespace LinqToActiveDirectory
{
public class DirectorySource<T> : IOrderedQueryable<T>, IDirectorySource
{
private DirectoryEntry searchRoot;
private SearchScope searchScope;
private TextWriter log;
public DirectorySource(DirectoryEntry searchRoot, SearchScope searchScope)
{
this.searchRoot = searchRoot;
this.searchScope = searchScope;
}
public TextWriter Log
{
get { return log; }
set { log = value; }
}
public DirectoryEntry Root
{
get { return searchRoot; }
}
public SearchScope Scope
{
get { return searchScope; }
}
public Type ElementType
{
get { return typeof(T); }
}
public Type OriginalType
{
get { return typeof(T); }
}
public Expression Expression
{
get { return Expression.Constant(this); }
}
public IQueryProvider Provider
{
get { return new DirectoryQueryProvider(); }
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
return new DirectoryQuery<T>(this.Expression).GetEnumerator();
}
}
}
The DirectorySource
in Listing 12.4 keeps track of the DirectoryEntry
, which provides the underlying access to Active Directory through DirectoryServices
. DirectorySource
also implements the IOrderedQueryable
interface and is asked by the .NET Framework to return the Expression
, Provider
, element type, and the enumerator. Provider
is the class that we define that implements IQueryProvider
, GetEnumerator
returns access to the underlying data from Active Directory, and Expression
is the abstract expression class that represents expression tree nodes.
The partial sequence diagram in Figure 12.3 shows the point in time at which the expressions are requested. Later, the expressions are parsed in a loop into its constituent elements by the DirectoryQuery
class (see Figure 12.4).
The abstract Expression
is an instance of DirectorySource<T>
, for example, DirectorySource<User>
. It represents parts of the expression that are composed of extension methods such as Where<T>
and Lambda Expressions such as user.Name == “Paul Kimmel”
. The DirectoryQuery
converts these Lambda Expressions into Active Directory queries.
Remember that LINQ queries are converted to extension method calls containing Lambda Expressions and that Lambda Expressions can be compiled as code or expression tree data (refer to Chapter 5, “Understanding Lambda Expressions and Closures”). Chapter 5 stated that expression trees are used to convert LINQ to other forms like SQL. The convertible expression tree mechanism is what is used for IQueryable providers.
The DirectoryQuery
is the real heart of this example. Although it would be nice to list it all here, its 593 lines and really long code listings don’t make for very good reading. Salient parts are listed, but what the class does at its heart is this: DirectoryQuery
has to convert a LINQ query to an Active Directory query.
DirectoryQuery
is fed all of the expression when DirectoryQuery
is constructed. When DirectoryQuery.GetEnumerator
is called, it recursively parses the expression tree. The parse behavior looks at the expression and asks, “Do I have an extension method call or do I have the Lambda Expression argument?” (For example, is it the Where
or the Lambda Expression predicate that needs parsing right now?) If it is at the point of parsing the Where<T>
extension method, it recourses and parses the argument to Where
. When Parse
returns from the recursive call, it figures out what method call it has—in this example, Where
—and turns the Where
into an Active Directory filter. Thus,
where user.Name == “Paul Kimmel” || user.Name == “Guest”
in the LINQ query becomes
(|(Name=Paul Kimmel)(Name=Guest)
its Active Directory equivalent.
The DirectoryQuery
class is where the code was borrowed heavily from Bart De Smet. However, the code was refactored to fit my writing style and new elements were added and some removed. Bart’s code at http://www.codeplex.com/LinqToAD contains an Active Directory Update capability. That’s removed from the download sample from this book. I added the ability to sort in ascending and descending order. Sorts are not provided directly by Active Directory filters but are provided by DirectoryServices
.
private void BuildMethodCallExpression(MethodCallExpression methodCallExpression)
{
CheckDeclaringType(methodCallExpression);
Parse(methodCallExpression.Arguments[0]);
// always a unary expression
LambdaExpression unary = ((UnaryExpression)
methodCallExpression.Arguments[1]).Operand as LambdaExpression;
switch (methodCallExpression.Method.Name)
{
case “Where”:
BuildPredicate(unary);
break;
case “Select”:
BuildProjection(unary);
break;
case “OrderBy”:
case “OrderByDescending”:
option = new SortOption();
option.PropertyName = GetPropertyNameFromUnary(unary);
option.Direction = GetSortDirection(methodCallExpression.Method.Name);
break;
default:
ThrowUnsupported(methodCallExpression.Method.Name);
break;
}
}
private IEnumerator<T> GetResults()
{
DirectorySchemaAttribute[] attribute = GetAttribute();
DirectoryEntry root = directorySource.Root;
string formattedQuery = GetFormattedQuery(attribute);
DirectorySearcher searcher = new DirectorySearcher(root,
formattedQuery, properties.ToArray(), directorySource.Scope);
if(option != null)
searcher.Sort = option;
WriteLog(formattedQuery);
Type helper = attribute[0].HelperType;
foreach (SearchResult searchResult in searcher.FindAll())
{
DirectoryEntry entry = searchResult.GetDirectoryEntry();
object result = Activator.CreateInstance(project == null ?
typeof(T) : originalType);
if (project == null)
{
foreach (PropertyInfo info in typeof(T).GetProperties())
AssignResultProperty(helper, entry, result, info.Name);
yield return (T)result;
}
else
{
foreach (string prop in properties)
AssignResultProperty(helper, entry, result, prop);
yield return (T)project.DynamicInvoke(result);
}
}
}
Listing 12.7 shows how the Where
predicate is converted to an Active Directory search filter. Listing 12.8 shows how reflection is used in conjunction with GetResults
to assign Active Directory properties to select projection properties, and Figure 12.5 shows a UML class diagram to illustrate how the primary elements are related to each other.
private void BuildPredicate(LambdaExpression lambda)
{
StringBuilder builder = new StringBuilder();
ParsePredicate(lambda.Body, builder);
query = builder.ToString();
}
private void ParsePredicate(Expression expression, StringBuilder builder)
{
builder.Append(“(“);
if (expression as BinaryExpression != null)
{
ParseBinaryPredicate(builder, expression as BinaryExpression);
}
else if (expression as UnaryExpression != null)
{
ParseUnaryExpression(builder, expression as UnaryExpression);
}
else if (expression as MethodCallExpression != null)
{
ParseMethodCallExpression(builder, expression as MethodCallExpression);
}
else
throw new NotSupportedException(
“Unsupported query expression detected. Cannot translate to LDAP equivalent.”);
builder.Append(“)”);
}
private void ParseBinaryPredicate(StringBuilder builder, BinaryExpression binary)
{
switch (binary.NodeType)
{
case ExpressionType.AndAlso:
builder.Append(“&”);
ParsePredicate(binary.Left, builder);
ParsePredicate(binary.Right, builder);
break;
case ExpressionType.OrElse:
builder.Append(“|”);
ParsePredicate(binary.Left, builder);
ParsePredicate(binary.Right, builder);
break;
default: //E.g. Equal, NotEqual, GreaterThan
builder.Append(GetCondition(binary));
break;
}
}
private void ParseUnaryExpression(StringBuilder builder, UnaryExpression unary)
{
if (unary.NodeType == ExpressionType.Not)
{
builder.Append(“!”);
ParsePredicate(unary.Operand, builder);
}
else
throw new NotSupportedException(
“Unsupported query operator detected: “ + unary.NodeType);
}
private void BuildProjection(LambdaExpression lambda)
{
project = lambda.Compile();
originalType = lambda.Parameters[0].Type;
MemberInitExpression memberInitExpression = lambda.Body as MemberInitExpression;
if (memberInitExpression != null)
foreach (MemberAssignment memberAssignment in memberInitExpression.Bindings)
FindProperties(memberAssignment.Expression);
else
foreach (PropertyInfo info in originalType.GetProperties())
properties.Add(info.Name);
}
/// <summary>
/// Recursive helper method to finds all required properties for projection.
/// </summary>
/// <param name=”expression”>Expression to detect property uses for.</param>
private void FindProperties(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
AddMemberExpressionName(expression);
}
else
{
FindExpressionProperties(expression);
}
}
The two attribute classes exist to map Active Directory schema and name elements to our programmer-friendly entities and names. For example, the next section defines an entity user that maps to the IADsUser
interface from Interop.ActiveDS
to an Active Directory user using the DirectorySchemaAttribute
and maps elements like Dn
to the Active Directory distinguishedName
attribute.
Listing 12.9 contains the code for DirectorySchemaAttribute
, and Listing 12.10 contains the code for the DirectoryAttributeAttribute.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqToActiveDirectory
{
public sealed class DirectorySchemaAttribute : Attribute
{
public DirectorySchemaAttribute(string schema)
{
Schema = schema;
}
public DirectorySchemaAttribute(string schema, Type type)
{
Schema = schema;
HelperType = type;
}
public string Schema { get; private set; }
public Type HelperType { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqToActiveDirectory
{
public sealed class DirectoryAttributeAttribute : Attribute
{
public DirectoryAttributeAttribute(string attribute)
{
Attribute = attribute;
QuerySource = DirectoryAttributeType.Ldap;
}
public DirectoryAttributeAttribute(string attribute,
DirectoryAttributeType querySource)
{
Attribute = attribute;
QuerySource = querySource;
}
public string Attribute { get; private set; }
public DirectoryAttributeType QuerySource { get; set; }
}
}
The last element of the sample provider is to define program-specific entities—that is, classes that contain those elements we want to obtain from Active Directory’s objects. Listing 12.11 defines a User
class that is mapped to the IADsUser
definition of a user and Listing 12.12 defines a Group
that is mapped to the LDAP definition of a group. IADsUser
is an interface defined as part of the Active Directory Services Interfaces (ADSI).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToActiveDirectory;
using ActiveDs;
namespace QueryActiveDirectory
{
[DirectorySchema(“user”, typeof(IADsUser))]
class User
{
public string Name { get; set; }
public string Description { get; set; }
public int LogonCount { get; set; }
[DirectoryAttribute(“PasswordLastChanged”, DirectoryAttributeType.ActiveDs)]
public DateTime PasswordLastSet { get; set; }
[DirectoryAttribute(“distinguishedName”)]
public string Dn { get; set; }
[DirectoryAttribute(“memberOf”)]
public string[] Groups { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToActiveDirectory;
namespace QueryActiveDirectory
{
[DirectorySchema(“group”)]
class Group
{
public string Name { get; set; }
[DirectoryAttribute(“member”)]
public string[] Members { get; set; }
}
}
The entity classes are used to construct a DirectorySource
. The Type
of the entity is used to determine what schema and properties should be obtained from the Active Directory query results.
After a considerable amount of plumbing, you can now get data from Active Directory as easily as you might from a custom object or SQL Server (using SQL for LINQ). Remember that the Active Directory LINQ provider is portable across applications so you only have to write this code once. (Well, you can just download the code.)
Listing 12.13 shows two LINQ queries. The first selects from users and returns a projection containing the name only for “Paul Kimmel” or “Guest.” The second query shows a query against groups filtering on the Name
and sorting in descending order.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
using System.Collections;
using LinqToActiveDirectory;
namespace QueryActiveDirectory
{
class Program
{
static void Main(string[] args)
{
GetUsersLinq();
}
private static readonly string LDAP =
“LDAP://sci.softconcepts.com:3268/DC=softconcepts,DC=COM”;
static DirectoryEntry activeDirectory = new DirectoryEntry(LDAP);
private static readonly string user = “xxxxxxxxx”;
private static readonly string password = “xxxxxxxxx”;
private static void GetUsersLinq()
{
var users = new DirectorySource<User>(activeDirectory, SearchScope.Subtree);
users.Log = Console.Out;
var groups = new DirectorySource<Group>(
activeDirectory, SearchScope.Subtree);
groups.Log = Console.Out;
var results = from user in users
where user.Name == “Paul Kimmel” ||
user.Name == “Guest”
select new { user.Name };
Console.WriteLine(“users”);
foreach (var r in results)
Console.WriteLine(r.Name);
Console.ReadLine();
Console.WriteLine(new string(‘-’, 40));
Console.WriteLine(“groups”);
var groupResults = from group1 in groups
where group1.Name == “Users” ||
group1.Name == “IIS_WPG”
orderby group1.Name descending
select group.Name;
Array.ForEach(groupResults.ToArray(), g =>
{
Console.WriteLine(“Group: {0}”, g.Name);
Array.ForEach(g.Members.ToArray(), m => Console.WriteLine(m));
}
);
}
}
}
The prep work is that you construct a DirectoryEntry
from System.DirectoryServices
. The DirectoryEntry
and SearchScope
are used to construct an instance of a DirectorySource
. The parameter (User
or Group
) defines the schema you will be querying from Active Directory and helps map the Active Directory fields to your entity fields. The Console.Out
is assigned to facilitate printing what’s going on in the provider and then you write LINQ queries.
As you can see from this chapter, LINQ isn’t limited to custom objects, XML, or SQL. You can query anything that is represented as a collection, as was demonstrated by the Outlook folder query examples, and you can write an IQueryable
provider to map LINQ to completely new data providers such as Active Directory.
You will need to download the code for this chapter. (Most of the other chapters contain the complete listing.) And, I encourage you to check Bart De Smet’s example at http://www.codeplex.com/LinqToAD. Bart has indicated that he is committed to maintaining and extending that code.
The same capability we used to build an Active Directory provider in this chapter was used to implement support for XML and ADO/SQL. So, now you know how the “magic” works; the next part of the book explores SQL for LINQ.
18.191.150.231