Chapter 12
Querying Outlook and Active Directory

In This Chapter

Image LINQ to Outlook

Image Querying Active Directory with Straight C# Code

Image LINQ to Active Directory

Image 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.

LINQ to Outlook

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.)

Listing 12.1 Scanning the Inbox and Contacts with LINQ and Adding New Email Addresses to the Contacts Folder

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();

Figure 12.1 Add a reference to the Microsoft.Office.Interop.Outlook assembly to program against Outlook’s object model.

Image

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.

Figure 12.2 Sadly, there are smart people in the world who don’t always act so smart, creating problems for the rest of us, making speed bumps like the Allow access dialog box a necessary evil.

Image

Querying Active Directory with Straight C# Code

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).

Listing 12.2 Straight C# Code That Uses System.DirectoryServices and Straight C# Code to Query Active Directory

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.

LINQ to Active Directory

.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.

Creating an IQueryable 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.

Implementing the IQueryProvider

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.

Listing 12.3 The IQueryProvider Class Is the Starting Point for LINQ Query Execution

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.)

Figure 12.3 The System.Linq.Queryable framework class calls DirectorySource, which implements IOrderedQueryable to request the provider—the IQueryProvider—and, in turn, creates the DirectoryQuery class, which implements the IQueryable interface.

Image

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.

Defining Active Directory as the Data Source

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).

Listing 12.4 The IOrderedQueryable Provider Lets Us Use LINQ to Active Directory.

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).

Figure 12.4 The abstract expression class is the DirectorySource<T>—for instance DirectorySource<User>—representing extension methods and Lambda Expressions.

Image

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.

Converting a LINQ Query to an Active Directory Query

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.

Listing 12.5 shows a BuildMethodCallExpression that handles OrderBy and OrderByDescending (orderby ascending or orderby descending). Rather than build a filter (which doesn’t exist for sorting), a SortOption’s properties are set and the SortOption object is assigned to the DirectorySearcher.Sort property in the GetResults method (see Listing 12.6).

Listing 12.5 BuildMethodCallExpression Assigns Fields from Active Directory Values to Project Fields for Select, Builds a Filter for Where, and Creates a SortOption Object for the DirectorySearcher for OrderBy and OrderByDescending

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;
 }
}

Listing 12.6 Ties All of the Elements of Select, Where, and OrderBy Together to Construct a DirectorySearcher from DirectoryServices and Obtain the Data from Active Directory

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.

Figure 12.5 A class diagram depicting significant elements of the solution and how they fit together.

Image

Listing 12.7 Converting Where Lambda Expressions into Active Directory Search Filters

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);
}

Listing 12.8 Storing Fields from the Active Directory Query in a Hashtable to Assign to Anonymous Project Types in the select Clause

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);
 }
}

Implementing Helper Attributes

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.

Listing 12.9 The DirectorySchemaAttribute Is Used to Indicate to Which Active Directory Schema the Entities Map

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; }
 }
}

Listing 12.10 DirectoryAttributeAttribute Is Used to Indicate to What Field an Entity Field Maps and Whether It Maps Using the IADsUser Field Names or LDAP Field Names

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; }
 }
}

Defining Active Directory Schema Entities

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).

Listing 12.11 The User Entity Maps to an Active Directory User

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; }
 }
}

Listing 12.12 The Group Entity Maps to an Active Directory Group

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.

Querying Active Directory with LINQ

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.

Listing 12.13 Using the Active Directory LINQ Provider to Query Users and Groups

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.150.231