Enumerating Exchange Contacts in .NET

I gave myself a goal this weekend: to write a simple application that would enumerate all the contacts for a given Exchange user so that I could acquire some experience in Exchange programming. I knew absolutely nothing about the Exchange programming model, or what was actually available to me as a developer. As usual, there was a rather large object model to learn and, of course, tons of documentation with samples that sometimes work well and sometimes don’t. The following article shows what I learned this weekend about the world of Exchange Programming in .NET.

Exchange Programming Basics

Exchange is more than a mail server—it is a tool for storing, securing, exchanging, and collaborating information between groups of people. Think about it: You can deal with such things as Calendar events, Notes, Reminders, and such with Outlook (which talks to Exchange)—all of which have nothing to do, directly, with e-mail. You have folders that act as repositories of information, some of which are public and some of which are private. And, of course, you have mailboxes that are responsible for storing and sorting mail content. Once you take away the view you usually have of Exchange through the eyes of Outlook or some other mail client, it really begins to look like a large directory service or database—full of information to create, update, delete, query, and so forth; this is the inherent reality of the Exchange Server.

Building from the collaborative nature of Exchange, there is constructed a set of COM objects and associated interfaces collectively known as CDO, or the Collaborative Data Objects. This is the technology I chose to write my application in because it was the first one I encountered that allowed me to accomplish my task at hand. However, it is by no means the only available tool Exchange developers have at their disposal. CDO is exclusively for applications that reside on the Exchange Server itself, and is not designed to write large-scale distributed applications. If you want to talk to your Exchange Server from the outside world, you have WebDAV: a protocol that extends the HTTP 1.1 specification to allow for the retrieval of objects and their properties through additional methods. WebDAV can be programmed against using Microsoft technologies by way of the various HTTP WebRequest/Response and XML frameworks (both managed and unmanaged exists) you have at your disposal. A current fallback to CDO is that it does not have a direct port to the .NET runtime, meaning that are no fully managed CDO frameworks and COM interop is necessary. For the most part, this effort went smoothly for me, however, and I didn’t have to concern myself too much with COM; as you’ll see later, though, I purposefully took the long route in a couple of instances for educational purposes.

Necessary Objects and Interfaces

Three technologies were used in my application: .NET, CDO, and ADO. If you’re unfamiliar with CDO, you might find it odd that I’m using ADO in addition to it. The reason is that there exists an ADO provider for the Exchange store (which is, as of Exchange Server 2000, backed by Active Directory), so one can use SQL statements to query, update, and retrieve data. The ADO provider for Exchange is known as ExOLEDB. It may seem a bit foreign, at first, to use SQL statements to bind to and retrieve information from the Exchange Store, but remember your earlier realization that the store is nothing but a large database anyway.

The following table displays the objects and interfaces used in my application:

Interface or Object Technology Description
IPerson CDO Interfaces with an Exchange user
IDataSource CDO Interfaces with the data source for a CDO object
IMailBox CDO Interfaces with the mailbox for an Exchange user
Record ADO An ADO Record object
RecordSet ADO An ADO RecordSet object
Field ADO The field of an ADO RecordSet

The object and interface descriptions above are relatively self-explanatory, and really need no further investigation (for more detailed information, refer to MSDN). Again, remember that all of these interfaces and objects require interop assemblies or runtime marshalling if they are to be subsumed by the .NET runtime.

The CDO COM servers needed to generate the interop assemblies only exist on the Exchange server itself—which happens to not be my development machine (and is probably not your development machine either). Two methods of getting the assemblies, then, were apparent to me (although there are probably better options out there). First, when you download and install the Exchange SDK from Microsoft, you are given a couple type libraries, one of which is cdoex.tlb. This type library describes all the types housed by the CDO COM server, CDOEX.dll. This is not the route I took, however, because it was a little later (after I had already generated my interop assemblies) that I noted the type libraries existed in the SDK install folders Exchange SDKSDKSupportCDO). What I did was manually locate CDOEx.dll on the Exchange server, drag it locally, and reference it with Visual Studio (which generated an interop assembly as well). Both methods are okay, but referencing the type library seems less “hackish” to me.

I encountered one problem, however, that I have been so far unable to neither understand nor resolve: the Person object and IPerson interfaces are totally missing from my interop assembly. Thankfully, the Person object supports IDispatch and so I could talk to the interfaces through automation, but this unfortunately made the code a bit less appealing to look at.

The Collaborate Data Objects Code

Let me describe the code now, pausing every so often to show actual code to keep your attention as I move about. The first task was unfortunately the most frustrating and is due to the problem I mentioned earlier: that my interop assembly did not have the Person object within it. I had to manually load the type information (via the type libraries) using the Person’s CLSID and the runtime’s System.Type.GetTypeFromCLSID:

// Load the type library for Person.
System.Type PersonType =
   System.Type.GetTypeFromCLSID(new System.Guid("CD000107-8B95-
                                11D1-82DB-00C04FB1625D"));
// CoCreateInstance on the type.
System.Object PersonObject =
   System.Activator.CreateInstance(PersonType);

What this code does is first load the type library for the Person object via the object’s CLSID (the process first looks in the Registry for the type registered with this CLSID and then loads its type library). With this information in hand, the .NET runtime is ready to actually work on the type, which includes generating a valid COM instance through CoCreateInstance. The .NET wrapper around this functionality is the System.Activator.CreateInstance() method, which takes as its sole parameter the type object retrieved earlier (which the runtime dutifully cracks and uses to create a real COM object for you).

After calling CreateInstance, one would assume I have a valid Person object, right? Well, not exactly. I only have a Person object, but the object isn’t bound to any real data—so the person is totally faceless; this presents an obvious problem if you are to work with it. The Person CoClass exposes three interfaces to make it functional: IPerson, IDataSource, and IMailBox. To bind the Person object to actual data, you use its IDataSource interface. This means you have to adjust yourselves to look at IDataSource in lieu of IPerson, which, in terms of COM, means a call to QueryInterface and in .NET an explicit cast to your desired interface.

CDO.IDataSource DataSourceInterface =
   PersonObject as CDO.IDataSource;
if (DataSourceInterface != null)
{
   // load myself.
   DataSourceInterface.Open(
      "MAILTO:[email protected]",
      null,
      ADODB.ConnectModeEnum.adModeRead,
      ADODB.RecordCreateOptionsEnum.adFailIfNotExists,
      ADODB.RecordOpenOptionsEnum.adOpenSource,
      System.String.Empty, System.String.Empty);

In C#, the “as” keyword means “attempt to cast to the desired type, and if the cast failed, set the LHS to null.” I check to verify that the casting succeeded and then proceed to attach the Person object to data by specifying an e-mail address; this is just the process that is done in CDO.

More by Author

Must Read