Collaboration Data Objects (CDO) are the modern way for Visual Basic
programmers to create professional, messaging-enabled applications. CDO
provides a rich interface to Microsoft's Messaging API (MAPI) in the form of
an object model, which is a programming concept especially familiar to
Visual Basic programmers. CDO is intuitive, but does have some "gotchas."
Collected here are the top ten tips I recommend you keep in mind as you
write messaging-enabled applications with CDO.
(Note that I use the term "CDO" here to refer specifically to CDO 1.x, the
Visual Basic- and VBScript-friendly "wrapper" for MAPI, and not the broader
collection of messaging technologies referred to as a whole as "CDO.")
- Use the IsSameAs method to compare CDO objects.
CDO
is a wrapper to MAPI, which is itself an object-oriented interface to
message stores, transport mechanisms, and address book databases. This can
occasionally result in unexpected behavior. For example, an application may
wish to determine whether two object references, say CdoMessage1
and CdoMessage2, refer to the same message. The following naive
implementation of such a check won't work reliably:
' This won't work.
If CdoMessage1 Is CdoMessage2 Then
' Do something.
End If
The reason is that the two object references (CdoMessage1 and
CdoMessage2) may refer to different CDO Message objects, yet those
CDO Message objects may both wrap the same MAPI message object. In this case,
the If statement shown will fail (because the two references do
indeed point to different objects), but we would prefer for the statement to
succeed (because the two objects wrap the same MAPI object). To handle
this situation, most CDO objects implement the IsSameAs method.
The syntax of this method is:
bResult = CdoObject1.IsSameAs(CdoObject2)
The IsSameAs method returns True either if CdoObject1 and
CdoObject2 refer to the same CDO object, or if they refer to
different CDO objects that in turn wrap the same MAPI object. The CDO
objects that implement the IsSameAs method are AddressEntry,
AddressEntryFilter, AddressList, AppointmentItem, Attachment, Folder,
InfoStore, MeetingItem, Message, MessageFilter, and Recipient.
- Understand the MAPI architecture.
Because CDO is built on
top of MAPI, it is profoundly influenced by the MAPI architecture. The more
you learn about MAPI, the better CDO programmer you will be. To learn more
about the MAPI architecture, browse the
MAPI Programmer's
Reference in the MSDN Library, and Inside MAPI (Microsoft Press), by Irving De la Cruz and Les
Thaler. Although both of these sources are heavily C++ oriented, they can't
be beat for deep-down, bare-metal MAPI wisdom. Without being too self-serving,
I also suggest my book: CDO
and MAPI Programming with Visual Basic: Developing Mail and Messaging
Applications.
- Understand when to use which messaging technology.
There are
quite a few options available to messaging developers. Here's a rundown on
when to use each one:
- Common Messaging Calls (CMC).
CMC is not intended for use in
Visual Basic applications.
- Simple MAPI.
Simple MAPI is an API that can be accessed through
Visual Basic Declare statements. It is an older technology that
should be avoided in favor of the MAPI ActiveX controls or CDO.
- The MAPI ActiveX Controls.
Use MAPI ActiveX controls when all your
application will ever need is to be able to send or receive plain-text
email. This technology has the lowest learning curve of all of the messaging
technologies available.
- Collaboration Data Objects (CDO).
CDO is the workhorse of messaging
technology for the Visual Basic developer. It is a Visual Basic- and
VBScript-friendly wrapper for MAPI. It supports accessing to all of your
MAPI-based data, including personal and public folders and address books.
Use CDO for most of your messaging development.
- The CDO Rendering Library.
The CDO Rendering Library provides the
ability to display messaging objects (such as folders and email) on Web
pages. It is used on a Web server, and works in conjunction with CDO. Use
this technology when building Web-based solutions.
- The CDO for NTS Library.
The CDO for NTS (Collaboration Data
Objects for NT Server) library is intended for use by Web-page scripts
running on a Web server. CDO for NTS wraps Internet mail protocols, rather
than MAPI. It's useful when Web page scripts don't need the full
functionality of MAPI, for example, when the scripts only need to send a
simple email to an Internet email address. This lets you avoid installing
MAPI on the Web server.
- The Outlook Object Model.
The Outlook Object Model is similar to
CDO in that it provides an Automation wrapper for MAPI. For many client
applications, there may not be a clear choice between CDO and the Outlook
Object Model. For server-based applications, or for applications that won't
have a UI, CDO is required. For applications that manipulate Outlook-
specific message items (such as tasks), Outlook is the better choice.
- Simple Mail Transport Protocol (SMTP).
Visual Basic comes with the
Winsock control, which allows Visual Basic developers to program Internet
protocols directly, including email protocols. However, I can't imagine a
single instance in which this would be preferable to using one of the
technologies that wraps either Internet email protocols or MAPI.
- CDO for Windows 2000.
CDO for Windows 2000, formerly known as CDO
2.0, wraps Internet email protocols, rather than MAPI. It's intended for use
from server-based applications. Use this technology when you're writing ASP
script that must be able to send and receive plain-text email via the
Internet.
- CDO for Exchange 2000.
Sometimes called CDO 3.0, CDO for Exchange
2000 is being billed as the next generation CDO. It has been written from
the ground up to use Internet standards rather than MAPI. CDO for Exchange
2000 is a superset of CDO for Windows 2000, so applications written to use
CDO for Windows 2000 will run with CDO for Exchange 2000, without
modification. In addition, Microsoft claims that CDO 1.21 applications (what
I call "regular" CDO) can be ported easily to the new architecture. CDO for
Exchange 2000 ships with Microsoft Exchange 2000 Server. Use this technology
for hot new development against Exchange 2000 Server.
- Make use of Web-based information sources.
If you get stuck
while developing collaboration applications, don't despair; there is a lot
of help available on the Internet. Here are some sites to get you started:
- MAPI Developers Forum.
The members of this list discuss
MAPI and related topics, including CDO, Outlook, and Exchange Server.
Subscribers include C, C++, and Visual Basic developers of all skill levels.
There are approximately 500 subscribers.
- Visual Basic List.
The members of this list discuss
everything related to Visual Basic, including how to use CDO from Visual Basic.
This is a large, very active list with developers of all skill levels. There are
also plenty of advanced Visual Basic developers who are ready to lend a hand
when they can. There are approximately 4,600 subscribers.
- Visual Basic Beginner's List.
For beginners who don't
feel comfortable posting to the Visual Basic List, there is the Visual Basic
Beginner's List. There are approximately 1,100 subscribers.
- Deja.com's Usenet
Discussion Service.
This Web site allows searching newsgroup
archives for particular keywords or phrases. This is an awesome site if you
don't know precisely which newsgroup you want.
- CDOLive.
This is
a rich site devoted to CDO and related technology. There are articles,
programming samples, links, training and book recommendations, and more.
- Catch errors raised by CDO.
CDO makes good use of Visual
Basic's error handling mechanism to report unusual events. For example, if
the user presses Cancel on a MAPI-provided logon dialog box, a
CdoE_USER_CANCEL error is raised in the Visual Basic code that
attempted the logon. Errors can also be raised when attempting to access
provider-specific MAPI properties through CDO's Fields collection. If the
user has a service provider that doesn't support the requested property,
then an error is raised, even if there were no errors during development.
Good code handles such errors.
For example, below is a function that uses CDO to show the user's address
book. This function assumes a global variable, gCdoSession, which
should contain a logged-in CDO Session object, and a module-level variable,
mCdoMessage, which should contain a CDO Message object. The
function causes the address book to be displayed, and then loads the Message
object's Recipients collection with the recipients that were selected in the
address book. If the user clicks the Cancel button in the address book
window, CDO raises a CdoE_USER_CANCEL error, which is handled by
simply exiting the function. Note that if an error other than
CdoE_USER_CANCEL is raised, it is re-raised to the outside world by
the function's error handler.
Private Sub ShowAddressBook()
On Error GoTo ErrorHandler
mCdoMessage.Recipients = gCdoSession.AddressBook( _
Recipients:=mCdoMessage.Recipien
ts, Title:="Select Names", _
RecipLists:=3)
Exit Sub
ErrorHandler:
If Err.Number <> CdoE_USER_CANCEL Then
Err.Raise Err.Number
End If
End Sub ' ShowAddressBook
- Beware of opening attachments programmatically.
Hackers have
discovered that programmable email is a great way to spread viruses. If the
attacker can get the victim to somehow run an infected program, that program
not only can damage the victim's system, it can use features, such as CDO, to
email itself to additional victims. If you're writing an application that has
the ability to open attached documents, be very careful to check an
attachment's file type before opening it. Alert the user to potentially
dangerous situations and, if possible, provide the ability to turn off
attachment capability entirely in your application.
- Declare typed variables to reference CDO objects, rather than using
dot notation.
For example, instead of this one-liner:
Debug.Print CdoSession.Inbox.Messages.Item(1).Subject
code this longer, but better alternative:
Dim CdoFolder As MAPI.Folder
Dim CdoMessages As MAPI.Messages
Dim CdoMessage As MAPI.Message
Set CdoFolder = CdoSession.Inbox
Set CdoMessages = CdoFolder.Messages
Set CdoMessage = CdoMessages.Item(1)
Debug.Print CdoMessage.Subject
There are several reasons for this:
- IntelliSense works with the second style, but not with the first.
- Repeated access to an object that is deep in the tree is much more
efficient using the second style.
- Certain inconsistencies can occur with the first style that are a side
effect of the fact that CDO is creating a temporary object each time a dot
is evaluated. These inconsistencies don't occur with the second style.
- Use filters to reduce the size of a collection to be searched.
An inefficient way to find message items of interest is to iterate one by one
through a CDO Messages collection, testing each Message object to see if it
meets some criteria. A far better way to achieve the same result is to use
CDO's MessageFilter object and let MAPI do the work for you. By setting
properties on a MessageFilter object, its corresponding Messages collection
shows only the message items that meet your criteria. Don't underestimate
the importance of this mechanism. Consider a client accessing an email
server over a dial-up line. Iterating through an entire Messages collection
requires that all messages be transported over the dial-up line. In contrast,
using a MessageFilter object causes only those messages that pass the filter
to be transported over the dial-up line.
For example, this code snippet shows how to retrieve only those messages
having a message class of "IPM.MyMessageClass":
' CdoSession previously Dim'ed and Set.
Dim CdoFolder As MAPI.Folder
Dim CdoMessages As MAPI.Messages
Dim CdoMessageFilter As MAPI.MessageFilter
Dim CdoMessage As MAPI.Message
Set CdoFolder = CdoSession.Inbox
Set CdoMessages = CdoFolder.Messages
Set CdoMessageFilter = CdoMessages.Filter
' Constrain the Messages collection.
CdoMessageFilter.Type = "IPM.MyMessageClass"
CdoMessageFilter.Unread = True
For Each CdoMessage In CdoMessages
' Do something with the message.
Next CdoMessage
' Clean up.
Set CdoMessage = Nothing
Set CdoMessageFilter = Nothing
Set CdoMessages = Nothing
Set CdoFolder = Nothing
- If you're doing CDO from a Web application, become familiar with IIS
security mechanisms.
Internet Information Server (IIS) has a
flexible security mechanism that can seem difficult to work with until you
become familiar with it. For example, you might have a Web-based CDO
application that accesses users' Exchange mailboxes. This application might
work successfully for months, only to break when you move IIS and Exchange
onto separate machines to get better performance. You discover that the
application works fine when Exchange is on the same machine as IIS, but you
get access restrictions when IIS tries to access Exchange on a remote
machine. Then you discover that if the servers are running Windows 2000,
rather than NT, the problem goes away. What's going on? The answer lies in
the way Windows NT and Windows 2000 handle user authentication, and in the
way IIS piggybacks onto that authentication mechanism. The only way to work
these issues out is to become familiar with the mechanism yourself.
For more information, see
Configuring Security for Internet Information Server, in
the MSDN library.
- If your Web-based CDO application keeps generating an ASP 0115 error,
restart your Web site and examine your CDO logon/logoff code.
CDO
is very picky about security context when logging on to a CDO session and
then off again. Review the documentation for the CDO Rendering Library
Application object's ImpID property and Impersonate method
for details. Once the wrong security context is used for logging off,
further attempts to use CDO result in the ASP 0115 error. At this
point, the only remedy is to shut down and restart the site.