Export (0) Print
Expand All
87 out of 108 rated this helpful - Rate this topic

An Introduction to Programming Outlook 2003 Using C#

Visual Studio .NET 2003
 

Andrew W. Troelsen, Microsoft MVP
Intertech Training

June 2004

Summary: This article provides an introductory look at the Microsoft® Outlook® 2003 object model, and explores how to build Outlook-aware applications and Outlook Add-ins using the C# programming language. (35 printed pages)

Click here to download the code sample for this article.

Note   This article assumes you're familiar with the C# programming language and the .NET platform. No knowledge of Outlook 2003 development is required or expected.

Applies to:

   Microsoft® Visual Studio® .NET 2003
   Microsoft® Visual C#® .NET 2003
   Microsoft® Outlook® 2003

Contents

Outlook 2003 as an Object Model
The Outlook Primary Interoperability Assembly (PIA)
The Outlook 2003 Object Model
Building an Outlook Aware Application
Implementing the Main() Method
Contending with the Outlook Security Update
Creating an Outlook Add-In Using Visual Studio .NET 2003
Dissecting the Connect Class Type
The Role of the IDTExtensibility2 Interface
Implementing the Add-in's UI
Implementing the E-mail Statistic Logic
Registering Shared Add-Ins
Conclusion
About the Author

Outlook 2003 as an Object Model

Microsoft has a long history of exposing an application's functionality to external programs. For example, if your project requires spell-checking functionality, you can leverage the object model exposed from Microsoft® Word. In a similar vein, if you are building an application that requires the functionality supplied by Microsoft® Outlook® 2003, you can leverage the associated object model. In a nutshell, the Outlook 2003 object model allows you to interact with:

  • E-mail items.
  • The Outlook Contacts database.
  • The Outlook Calendar.
  • Outlook Notes and Tasks.
  • The UI of Outlook itself (Explorers, Inspectors, CommandBars, etc).

This is obviously a subset of the contained functionality, but I'm sure you get the general idea: The functionality of Outlook 2003 can be accessed via its associated object model.

The Outlook Primary Interoperability Assembly (PIA)

To date, the functionality of Outlook is exposed through a COM-based in-process server (msoutl.olb). When .NET developers wish to interact with these COM types, therefore, you will need to do so through the interoperability layer. Microsoft Corporation has already supplied an 'official' interop assembly (a.k.a., the primary interop assembly) which ships with Outlook 2003.

This assembly has been strongly named, and resides in the Global Assembly Cache under the name Microsoft.Office.Interop.Outlook.dll. To reference this assembly from Microsoft® Visual Studio® .NET 2003, access the COM tab from the Add References dialog and select Microsoft Outlook 11.0 Object Library (Figure 1).

Figure 1. Referencing the Outlook PIA

Note   If you are building custom applications that make use of previous versions (or multiple versions) of the Outlook PIA, be sure to check out http://go.microsoft.com/fwlink/?LinkId=30833, which discusses some possible versioning conflicts.

A Brief Word Regarding Interop Assemblies

The ultimate goal of any interop assembly is to provide .NET types which look and feel like the original COM types. The interoperability layer (in conjunction with a runtime generated proxy termed the Runtime Callable Wrapper, or RCW) takes care of various details regarding marshaling data types. For example, if an interface method was defined to take a COM-based BSTR parameter, .NET developers are free to pass a CLR-based System.String.

Interop assemblies contain specific types that always take a '–Class' suffix, as well as an identically named managed equivalent, for each COM class. For example, the following COM IDL definition:

coclass MyComObject
{
  // Assume this interface defines three methods named
  // One(), Two(), Three().
  [default] interface IMyComObject;
  // Assume this interface defines two methods named 
  // Four(), Five(). 
  interface ISomeOtherInterface;
}

Results in two .NET class types named MyComObject and MyComObjectClass. The MyComObject type only exposes members of the [default] interface. When you wish to access the functionality of additional interfaces, you will need to perform an explicit cast (which triggers a call to IUnknown::QueryInterface() behind the scenes):

// Create a new MyComObject.
MyComObject c = new MyComObject();
c.One();
// To call Four() or Five(), we need to obtain the 
// ISomeOtherInterface explicity. 
ISomeOtherInterface itfOther = (ISomeOtherInterface)c;
itfOther.Five();

On the other hand, if you make use of the MyComObjectClass type, you are able to access each and every member from each and every interface using a single object reference:

// Create a new MyComObjectClass.
MyComObjectClass c = new MyComObjectClass();
c.One();
c.Five();

In effect, the –Class suffixed types are a union of all the interface methods from the original COM type. Given that the Outlook 2003 coclasses were originally designed to only support a single [default] COM interface, you can typically ignore the –Class suffixed types and make use of the identically named .NET proxies.

Note   If you wish to dig deeper into the COM to .NET conversion process, http://blogs.msdn.com/eric_carter/archive/2004/05/06/127698.aspx will prove quite helpful.

The final point to be made regarding interacting with COM types from managed languages has to do with the role of the CLR garbage collector. The .NET memory management model is non-deterministic in nature, in that we as developers don't know exactly when an object will be destroyed, only that it will eventually be destroyed. The COM memory model, on the other hand, was painfully deterministic in nature in that we were forced to manually adjust the object's internal reference count of the COM type using various calls to IUnknown::AddRef() and IUnknown::Release() (although Visual Basic® 6.0 attempted to hide this from view).

When you are programming against types within an interop assembly, the proxy types are garbage collected just as any CLR reference type. Once the proxy has been garbage collected, the RCW forwards all necessary IUnknown::Release() calls to the associated COM object, at which point it is destroyed. Using this technique, you can rest assured that as long as your proxy is alive in memory, so is the associated COM object.

If you wish to ensure that COM types are destroyed in a timelier and more predictable fashion, you can leverage the System.Runtime.InteropServices.Marshal type. This class defines a static method named ReleaseComObject(). Simply pass the object reference, and the associated COM type is destroyed on the spot:

using System.Runtime.InteropServices;
class MyClass
{
  public void SomeMethod()
  {
    MyComObjectClass c = new MyComObjectClass();
    ...
    // Explicitly destroy COM type after use.
    Marshal.ReleaseComObject(c);
  }
}

While the idea of destroying COM types at your whim may sound enticing, you must be aware that other CLR objects in your AppDomain will now be unable to use the underlying COM type. Given this (dangerous) possibility, the sample code in this article will avoid the use of Marshal.ReleaseComObject().

Note   This issue has been addressed in the forthcoming version of the .NET platform (Microsoft® Visual Studio.NET® 2005, aka, Whidbey). Check out http://blogs.msdn.com/yvesdolc/archive/2004/04/17/115379.aspx for further details.

The Outlook 2003 Object Model

Once you reference the Outlook PIA, your next task is to investigate the numerous types within the Microsoft.Office.Interop.Outlook namespace (Figure 2).

Figure 2. The Microsoft.Office.Interop.Outlook namespace

Despite the volume of types, the good news is that the object model itself is very well organized and makes use of common design patterns. Thus, once you understand how to iterate over a contact list, it is quite simple to iterate over inbox items.

The other bit of good news is that the entire object model is fully documented in a help file (vbaol11.chm), located by default under <drive>:\Program Files\Microsoft Office\OFFICE11\1033 (Figure 3).

Figure 3. Outlook 2003 Documentation

Now, the bad news (depending on your point of view) is that the help system uses VBScript for the code examples and member prototypes. Given that this article makes no attempt to detail each type in the Outlook 2003 object model, I'll assume you'll consult this help system to complete the picture. This being said, let's examine some of the core class types.

The Application Type

The first type to get to know is aptly named 'Application', which is the root of all other objects in the hierarchy. Once you obtain an object of this type, you are able to programmatically control all aspects of Outlook itself. Table 1 lists some (but by no means all) members of interest.

Table 1. Select members of the Application type

Member of the Application Type Meaning in Life
ActiveExplorer()

ActiveInspector()

These methods retrieve Explorer / Inspector types (respectively) from the current Outlook instance. The explorer/inspector model is described later in this article.
CreateItem() Allows you to create a new Outlook item programmatically.
GetNamespace() Provides access to data store items. "MAPI" is the only available namespace that you can use as of Outlook 2003, which provides access to the set of Outlook folders (Inbox, Notes, etc).
Quit() Terminates the current Outlook session.
COMAddIns This property allows you to discover at runtime the set of Add-ins plugged into the current instance of Outlook.
Explorers

Inspectors

These properties allow you to obtain strongly typed Explorers/Inspectors collections.

Exactly how you obtain an Application type will differ slightly based on the sort of software you are developing. If you are building a custom application that incorporates Outlook 2003, all you need to do is create the type using the C# new keyword:

// Create an Outlook Application object. 
Application outLookApp = new Application();

On the other hand, when you are building Outlook 2003 Add-ins (as we will do later in this article), you are passed in an instance of Application by way of a method named OnConnection():

public void OnConnection(object application, 
  Extensibility.ext_ConnectMode connectMode, 
  object addInInst, ref System.Array custom)
{
  // Assume 'applicationObject' is a member variable of 
  // type Microsoft.Office.Interop.Outlook.Application.
  applicationObject = (Microsoft.Office.Interop.Outlook.Application)
    application;
}

In addition to various properties and methods, the Application type also defines a number of events (StartUp, Quit, ItemSend, NewMailEx) which fire during various circumstances. Ponder the following code snippet:

public class MyApp
{
  public static void Main()
  {
    // Create an Outlook Application object. 
    Application outLookApp = new Application();
    // Rig up the NewMailEx event.
    outLookApp.NewMailEx += new   
      ApplicationEvents_11_NewMailExEventHandler(outLookApp_NewMailEx);
    ...
  }
  private static void outLookApp_NewMailEx(string EntryIDCollection)
  {
    // Do something interesting when a new e-mail arrives. 
  }
}   

Again, given the role of the interoperability layer, the process of handling COM-based events looks identical to the process of handling CLR events. Don't sweat the details just yet, but simply notice that the NewMailEx event works in conjunction with a specific delegate (ApplicationEvents_11_NewMailExEventHandler) which can call any method taking a System.String as its sole parameter and returning nothing.

Outlook 'Item' Class Types

Once you have an Application type, you are then able to create new Outlook 'items'. Possible items are represented by the Microsoft.Office.Interop.Outlook.OlItemType enumeration:

public enum OlItemType
{
  olAppointmentItem = 1;
  olContactItem = 2;
  olDistributionListItem = 7;
  olJournalItem = 4;
  olMailItem = 0;
  olNoteItem = 5;
  olPostItem = 6;
  olTaskItem = 3;
}

Assume you wish to programmatically create a new Outlook Task item. To do so, specify OlItemType.olTaskItem as an argument to Application.CreateItem():

public static void Main()
{
  // Create an Outlook Application object. 
  Application outLookApp = new Application();
  // Create a new TaskItem.
  TaskItem newTask = 
    (TaskItem)outLookApp.CreateItem(OlItemType.olTaskItem);
  // Configure the task at hand and save it.
  newTask.Body = "Don't forget to send DOM the links...";
  newTask.DueDate = DateTime.Now;
  newTask.Importance = OlImportance.olImportanceHigh;
  newTask.Subject = "Get DOM to stop bugging me.";
  newTask.Save();
}

Notice that the return value of CreateItem() is a generic OlItemType; you will therefore need to explicitly cast the result into the correct type (in this case, TaskItem). At this point it is simply a matter of configuring our item using the type's public interface. Once executed, you are able to find the task within the Outlook Task inspector (Figure 4).

Figure 4. A programmatically generated task

Although the names of the OlItemType enumeration are quite straightforward, Table 2 details the relationship between members of the OlItemType enumeration and the resulting return type from Application.CreateItem().

Table 2. The OlItemType enum/Outlook class type relationship

OlItemType Enumeration Value Resulting Type Meaning in Life
olAppointmentItem AppointmentItem Represents a single appointment.
olContactItem ContactItem Represents a single contact.
olDistributionListItem DistributionListItem Represents a distribution list.
olJournalItem JournalItem Represents a single journal entry.
olMailItem MailItem Represents a single e-mail item.
olNoteItem NoteItem Represents a single note.
olPostItem PostItem Represents a post in a public folder that others may browse.
olTaskItem TaskItem Represents a single task.

Obtaining Existing Outlook Items

In addition to creating new items, the Outlook 2003 model also allows you to obtain (and possibly modify) existing items. Regardless of which Outlook item you are interested in enumerating over, the basic process is as follows:

  • Obtain a NameSpace type from Application.GetNamespace().
  • Obtain a MAPIFolder type from NameSpace.GetDefaultFolder().
  • Enumerate over the sub-items using the MAPIFolder.Items indexer.

When you specify the string "MAPI" as an argument to GetNamespace() you receive a NameSpace type, which represents a level of abstraction to specific Outlook data stores (currently, "MAPI" is the only valid namespace). The MAPIFolder type can represent any folder in a given user's mail stores (deleted items, the inbox, journal items, and so forth). The full range of folder options is represented by the OlDefaultFolders enumeration:

public enum OlDefaultFolders
{
  olFolderCalendar = 9;
  olFolderConflicts = 19;
  olFolderContacts = 10;
  olFolderDeletedItems = 3;
  olFolderDrafts = 16;
  olFolderInbox = 6;
  olFolderJournal = 11;
  olFolderJunk = 23;
  olFolderLocalFailures = 21;
  olFolderNotes = 12;
  olFolderOutbox = 4;
  olFolderSentMail = 5;
  olFolderServerFailures = 22;
  olFolderSyncIssues = 20;
  olFolderTasks = 13;
  olPublicFoldersAllPublicFolders = 18;
}

To request a specific folder, specify a value from the OlDefaultFolders enumeration as an argument to NameSpace.GetDefaultFolder(). Ponder the following code, which enumerates over the set of tasks for the current user:

static void Main(string[] args)
{
  // Create an Outlook Application object. 
  Application outLookApp = new Application();
  // Print all tasks. 
  NameSpace outlookNS = outLookApp.GetNamespace("MAPI");
  MAPIFolder theTasks = 
    outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderTasks);
  foreach(TaskItem task in theTasks.Items)
  {
    Console.WriteLine("-> Time Created: {0}", task.CreationTime);
    Console.WriteLine("-> Body: {0}", task.Body);
  }
}

Inspectors and Explorers

Beyond providing access to various items, the Outlook object model also defines types that allow you to manipulate the user interface. The Explorer type represents a window in which folder contents are displayed. Inspectors, on the other hand, represent a single item opened for viewing. The Application class maintains a collection of all Explorers and Inspectors, which can be obtained using the aptly named Explorers / Inspectors properties:

Application app = new Application();
Explorers theExplorers = app.Explorers;
foreach(Explorer e in theExplorers)
{
  // Do something with each Explorer...
}

The GetActiveExplorer() and GetActiveInspector() methods of the Application class can be used to obtain the currently active UI element:

Application app = new Application();
Explorer activeExp = app.ActiveExplorer();
Console.WriteLine("Explorer caption: {0}", activeExp.Caption);

Explorers and Inspectors are quite useful when you are building custom Outlook Add-ins, in that you are able to attach UI widgets to the existing set of CommandBars. You'll do this very thing a bit later in the article.

Building an Outlook Aware Application

To keep focused on manipulating the object model of Outlook (rather than building fancy user interfaces), the first example will make use of a simple command line user interface. If you wish to follow along, create a new C# Console Application named OPine. The Unix hacks out there may know that 'Pine' is the name of a rather popular command line e-mail utility. OPine will mimic a subset of Pine's functionality. Specifically, OPine responds to the following commands:

  • dib: Display inbox items
  • snm: Send new mail item
  • cn: Create a new note
  • dn: Display existing notes
  • q: Quit OPine

OPine will also be able to notify the user when new mail arrives by responding to the NewMailEx event.

Note   OPine will make use of the ApplicationClass type (rather than Application) to circumvent a name-clash introduced later when referencing the System.Windows.Forms.dll assembly. The name clash may also be resolved using a C# alias such as:
using OutLookApp = Microsoft.Office.Interop.Outlook.Application;

In this case, however, the use of the –Class type will do no harm for the OPine example.

Handling the 'dib' Command

Assuming you have already referenced the Outlook 2003 PIA, the next step is to create a helper class (OPineHelper) that defines a set of static methods to do the bulk of the work. First up, we have a method named DisplayInbox(), which takes an ApplicationClass type as its sole argument. The implementation of DisplayInbox() obtains the current MAPI namespace in order to retrieve each MailItem in the Inbox folder. At this point, we will print out to the console the time received, sender name, and subject, using various properties of the MailItem type:

public static void DisplayInbox(ApplicationClass o)
{
  // Get items in my inbox. 
  NameSpace outlookNS = o.GetNamespace("MAPI");
  MAPIFolder inboxFolder 
    = outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
  // Print out some basic info. 
  Console.WriteLine("You have {0} e-mails.", 
    inboxFolder.Items.Count);
  Console.WriteLine();
  foreach(object obj in inboxFolder.Items)
  {
    MailItem item = obj as MailItem;
    if(item != null)
    {
      Console.WriteLine("-> Received: {0}", 
        item.ReceivedTime.ToString());
      Console.WriteLine("-> Sender: {0}", item.SenderName);
      Console.WriteLine("-> Subject: {0}", item.Subject);
      Console.WriteLine();
    }
  }
}

Notice that we are treating the items obtained by the Items property as generic System.Objects, rather than the expected MailItem type. Note furthermore that we are doing a dynamic check to see if the current item can be treated as a MailItem (via the C# 'as' keyword), and if so we interact with various properties of the type. The reason we are doing this dynamic check is the fact that the Outlook Inbox can indeed contain items beyond MailItem types (such as meeting requests). Had we set up our foreach logic as so:

foreach(MailItem item in inboxFolder.Items)
{
  ...
}

We could receive a runtime exception if anything other than a MailItem had been encountered.

In any case, beyond the ReceivedTime, SenderName and Subject properties, the MailItem type provides access to attachments and importance level, as well as the HTML representation of the content (via the HTMLBody property). Again, check out the Outlook 2003 documentation for complete details.

Handling the 'snm' Command

The next static method of OPineHelper, SendNewMail(), is responsible for creating and sending a new e-mail on behalf of the user. As seen previously, we will create a new MailItem type via ApplicationClass.CreateItem(). The following code should pose no raised eyebrows by this point:

public static void SendNewMail(ApplicationClass o)
{
  // Create a new MailItem.
  MailItem myMail = 
    (MailItem)o.CreateItem(OlItemType.olMailItem);
  // Now gather input from user. 
  Console.Write("Receiver Name: ");
  myMail.Recipients.Add(Console.ReadLine());
  Console.Write("Subject: ");
  myMail.Subject = Console.ReadLine();
  Console.Write("Message Body: ");
  myMail.Body = Console.ReadLine();
  // Send it!
  myMail.Send();
}

Creating ('cn') and Displaying ('dn') Notes

The next two static methods are no-brainers at this point, given that all we really need to do is repeat the process used to create new e-mails and iterate over existing e-mail items. In the following code, notice the values specified by the OlItemType and OlDefaultFolders enumerations:

public static void CreateNote(ApplicationClass o)
{
  // Get access to notes. 
  NoteItem myNote = 
    (NoteItem)o.CreateItem(OlItemType.olNoteItem);
  Console.Write("Enter your note: ");
  myNote.Body = Console.ReadLine();
  // Now save the note. 
  myNote.Save();
}
public static void DisplayNotes(ApplicationClass o)
{
  // Get items in my inbox. 
  NameSpace outlookNS = o.GetNamespace("MAPI");
  MAPIFolder notesFolder 
    = outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderNotes);
  // Print out some basic info. 
  Console.WriteLine("You have {0} notes.", 
    notesFolder.Items.Count);
  Console.WriteLine();
  foreach(NoteItem item in notesFolder.Items)
  {
    Console.WriteLine("-> Time Created: {0}", item.CreationTime);
    Console.WriteLine("-> Body: {0}", item.Body);
    Console.WriteLine();
  }
}

The Final Touch

The final static method to worry about here simply displays the set of options to the end user:

public static void DisplayOPineOptions()
{
  Console.WriteLine("***** Welcome To OPine *****");
  Console.WriteLine("dib : Display Inbox");
  Console.WriteLine("snm : Send New Mail");
  Console.WriteLine("cn  : Create Note");
  Console.WriteLine("dn  : Display Notes");
  Console.WriteLine("q   : Quit");
  Console.WriteLine("****************************");
}

That wraps up the creation of the OPine helper class; now let's put it to use.

Implementing the Main() Method

At this point we are ready to implement the Main() method, which is responsible for the following tasks:

  • Create an instance of the ApplicationClass type
  • Obtain the user's command option via Console.ReadLine()
  • Switch on the user supplied string, and execute the appropriate method of OPineHelper

Given these points, here is one possible implementation:

static void Main(string[] args)
{
  // String to hold the user request. 
  string userOption;   
  // Create an Outlook application object. 
  ApplicationClass outLookApp = new ApplicationClass();
  // Display info. 
  OPineHelper.DisplayOPineOptions();
  do
  {
    Console.Write("\nPlease enter your command: ");
    userOption = Console.ReadLine();
    switch(userOption)
    {
      // Display Inbox (dib)
      case "dib":
        OPineHelper.DisplayInbox(outLookApp);
      break;
      // Create Note (cn)
      case "cn":
        OPineHelper.CreateNote(outLookApp);
      break;
      // Send New Mail (snm)
      case "snm":
        OPineHelper.SendNewMail(outLookApp);
      break;
      // Display Notes (dn)
      case "dn":
        OPineHelper.DisplayNotes(outLookApp);
      break;
      // Quit (q)
      case "q":
        userOption = "q";
      break;
      default:  // Anything else? Just display options. 
        OPineHelper.DisplayOPineOptions();
      break;
    }
  }while(userOption != "q");
}

Handling the NewMailEx event

The final bit of functionality we will add to OPine is the ability to handle incoming new e-mails. First, handle the NewMailEx event after allocating the ApplicationClass type:

// Create an Outlook application object. 
ApplicationClass outLookApp = new ApplicationClass();
// Rig up the new message event.
outLookApp.NewMailEx += new 
  ApplicationEvents_11_NewMailExEventHandler(outLookApp_NewMailEx);

The target of the ApplicationEvents_11_NewMailExEventHandler delegate requires a parameter of type System.String. The value of this string represents the ID of the new MailItem itself, which you can obtain via the NameSpace.GetItemFromID() method.

In the event handler which follows, note that we inform the user of the new mail message using a System.Windows.Forms.MessageBox type, so be sure to add a reference to System.Windows.Forms.dll (and update your file's using directive set):

private static void outLookApp_NewMailEx(string EntryIDCollection)
{
  if(DialogResult.Yes == 
    MessageBox.Show("Do you want to see your message?", 
      "You've got mail!", MessageBoxButtons.YesNo))
  {
    // Get the incoming MailItem based on ID.
    ApplicationClass o = new ApplicationClass();
    NameSpace outlookNS = o.GetNamespace("MAPI");
    MAPIFolder mFolder =          
      o.Session.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
    MailItem newMail = (MailItem)
      outlookNS.GetItemFromID(EntryIDCollection, mFolder.StoreID);
    // Now print out. 
    Console.WriteLine("\n\n***** New Message *****");
    Console.WriteLine("-> Sender: {0}", newMail.SenderName);
    Console.WriteLine("-> Subject: {0}", newMail.Subject);
    Console.WriteLine("-> Body: {0}", newMail.Body);            
    Console.WriteLine("***********************\n\n");
  }
}

That's it. We can now compile and take OPine out for a test (Figure 5).

Figure 5. OPine at work

I'm sure you can find numerous ways to extend and improve upon OPine, including moving from a Console-based UI to a Graphical UI (via Windows Forms). Although I obviously know that not too many of you are building command line e-mail programs, I do hope this example has illustrated the process of interacting with Outlook from your custom applications.

Contending with the Outlook Security Update

While running OPine, you were no doubt aware of the following dialog launched by Outlook (Figure 6)

Figure 6. The Outlook 2003 security warning

Although intrusive to the end user, this behavior is by design. Under Outlook 2003, select members (of select objects) are deemed a possible security risk. Thus, the user is prompted for permission to proceed to prevent e-mail worms and viruses from using the object model for evil doings.

Note   If you wish to see documentation of which Outlook types and members result in this security prompt, check out the article What's New in Microsoft Office Outlook 2003 for Developers?

Given that the end user can always deny access to Outlook by responding 'No' to the security prompt, you will do well to make liberal use of try/catch logic when programmatically working with Outlook in your custom applications. For example, to avoid a runtime crash, OPineHelper.DisplayNotes() (as well as the remaining methods) should be retrofitted like so:

public static void DisplayNotes(ApplicationClass o)
{
  // Same as before...
  try
  {
    foreach(NoteItem item in notesFolder.Items)
    { 
      // Same as before...
    }
  }
  catch{Console.WriteLine("Can't access Outlook");}
}

Note   It is worth pointing out that when you are building Outlook Add-ins, the incoming Microsoft.Office.Interop.Outlook.Application parameter of the OnConnection() method is assumed to be trusted, which prevents the appearance of the security alert in most cases.

Creating an Outlook Add-In Using Visual Studio .NET 2003

Our next example examines how to extend Outlook 2003 with custom functionality. The whole idea of application extensibility is another mainstay of Microsoft products. Basically, applications such as Outlook, Word, or Visual Studio .NET were designed in such a way that external vendors or individuals could extend said functionality by plugging in new objects (provided said objects implement the correct interfaces).

While you most certainly build Add-Ins using nothing but the C# command line compiler (csc.exe) and notepad.exe, you will save yourself some typing time if you make use of the Visual Studio .NET Shared Add-in project template. To illustrate, we will be creating an Add-in named EMailStatsAddIn. This Add-in will plug into the existing UI of Outlook 2003 and provide the following functionality:

  • Display how many messages the user has received that day/month.
  • Display how many messages the user has sent that day/month.

The journey begins by selecting this template from the Other Projects | Extensibility Projects folder of the New Project dialog (Figure 7).

Figure 7. The Shared Add-in project template

Once you click the OK button, you will be guided through a five-step Wizard which allows you to configure the initial Shared Add-in project. The first step allows you to select the language (Microsoft® Visual C#® or Microsoft® Visual Basic® .NET C++) and underlying framework (.NET or COM-based ATL) to be used. For our example, we obviously desire Visual C#.

The second step gives you a chance to select the hosts into which your Add-in can plug. Given that we are only interested in Outlook 2003, deselect all other options (Figure 8).

Figure 8. Selecting the desired hosts

Step three allows you to supply a 'friendly' name and description for the current Add-in. These string values control how your Add-in will be registered and displayed by host Add-in dialogs. Feel free to enter values as you see fit.

The two options shown in Step 4 let you to specify if your Add-in should automatically load into the host upon startup, as well as which users on the target machine are allowed to access the Add-in. For EMailStatsAddIn, we will check both options (Figure 9).

Figure 9. Loading options for Shared Add-ins.

Step five is simply a chance to confirm your selections. Once you complete the Shared Add-in Wizard, you find that you end up with a single solution containing two projects:

  • The Add-in itself (EMailStatsAddIn)
  • A Setup project (EMailStatsAddInSetup)

I'll discuss the role of the setup project a bit later in this article.

Referenced Assemblies

In addition to the standard System.dll, System.Data.dll and System.XML.dll assemblies, Shared Add-ins also automatically reference an assembly named Extensibility.dll. This assembly contains a single namespace (Extensibility) which defines exactly three types (see Table 3).

Table 3. Types of the Extensibility namespace

Type of Extensibility Namespace Meaning in Life
IDTExtensibiltity2 The key interface that all Add-ins must implement.
ext_ConnectMode An enumeration that represents the various manners in which a host can connect to a given Add-in.
ext_DisconnectMode An enumeration that represents the various manners in which a host can disconnect from a given Add-in.

The other referenced assembly of interest is Office.dll. While this assembly does define numerous types (which can be confirmed via the Visual Studio.NET 2003 object browser), the most important types have to do with extending the host's GUI with custom widgets to interact with the Add-in under development. I won't be digging too far into the Office object model here, but an online reference can be found on the MSDN Web site.

Note that the Shared Add-in project template does not automatically reference the Microsoft.Office.Interop.Outlook.dll assembly, so be sure to do so now via the Add References dialog. While you are doing so, add a reference to System.Windows.Forms.dll, as we will need access to the MessageBox type.

Dissecting the Connect Class Type

The major file of interest is named Connect.cs. Here you will find a class type named Connect defined as follows (XML code comments stripped away for brevity):

[GuidAttribute("762B03FF-52D0-4735-9D2B-4DE32DB9393E"), 
ProgId("EMailStatsAddIn.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2
{
  public Connect(){}
  public void OnConnection(object application, 
    Extensibility.ext_ConnectMode connectMode, 
    object addInInst, ref System.Array custom)
  {         
    applicationObject = application;
    addInInstance = addInInst;
  }
  public void OnDisconnection(Extensibility.ext_DisconnectMode 
    disconnectMode, ref System.Array custom) {}
  public void OnAddInsUpdate(ref System.Array custom){}
  public void OnStartupComplete(ref System.Array custom){}
  public void OnBeginShutdown(ref System.Array custom){}
  private object applicationObject;
  private object addInInstance;
}

The first point of interest is the fact that the class as been adorned with the [Guid] and [ProgId] attributes, which are well-known values used to identify COM classes. As we are building a managed code library, you may wonder why our assembly needs to be registered for use by COM. Recall that most hosts that have the ability to access Add-in types are programmed to load COM servers (not .NET assemblies). Therefore, to enable interoperability, the Connect type has been supplied with the necessary COM naming conventions (of course, your GUID value will differ).

Next up, notice that the Connect type does not extend an Add-In-centric base class, but simply derives from System.Object. What makes a Shared Add-in unique is the fact that it implements a key interface: Extensibility.IDTExtensibility2. In fact, the members of the Connect class are nothing more than stub implementations of the interface contract.

Finally, note that the Connect class defines two private member variables:

private object applicationObject;
private object addInInstance;

The second member variable (addInInstance) represents an instance of the current Add-in, allocated and plugged in by the host. The applicationObject member variable represents a reference to the hosting application.

Given that Shared Add-ins can be plugged into a number of different applications (Outlook 2003, Visual Studio.NET 2003, various Microsoft Office applications, and so on) applicationObject has been defined as a generic System.Object. Given that our Add-in is intended to be used only by Outlook, however, let's strongly type applicationObject as a Microsoft.Office.Interop.Outlook.Application type:

// Don't forget to reference the Outlook interop assembly! 
using Microsoft.Office.Interop.Outlook;
...
public class Connect : Object, Extensibility.IDTExtensibility2
{
  ...
  // Change to a strongly typed Outlook Application type.
  // private object applicationObject;
  private Microsoft.Office.Interop.Outlook.Application 
    applicationObject;   
  private object addInInstance;             
}

With this initial code update, let's check out the functionality provided by the IDTExtensibility2 interface.

The Role of the IDTExtensibility2 Interface

The IDTExtensibility2 interface type defines five methods that are called by the host application during various stages of an Add-ins lifecycle. Here is the formal definition:

public interface IDTExtensibility2
{
  void OnAddInsUpdate(ref Array custom);
  void OnBeginShutdown(ref Array custom);
  void OnConnection(object Application, ext_ConnectMode ConnectMode, 
    object AddInInst, ref Array custom);
  void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom);
  void OnStartupComplete(ref Array custom);
}  

The OnConnection() Method

The first method of interest, OnConnection(), is called when your Add-in is connected to the host. The first parameter represents the hosting application itself (Visual Studio.NET, Microsoft Outlook, etc.). The second parameter is an enumeration of type ext_ConnectMode, which represents exactly how the host is loading the Add-in type. The ext_ConnectMode enumeration defines the following values (of which ext_cm_CommandLine and ext_cm_Solution are not used by Outlook 2003):

public enum ext_ConnectMode
{
  // Add-in loaded after host.
  ext_cm_AfterStartup = 0;   
  // Add-in loaded from command line.
  ext_cm_CommandLine = 3;   
  // Add-in loaded indirectly from host.
  ext_cm_External = 2;       
  // Add-in loaded by a VS.NET solution which required it. 
  ext_cm_Solution = 4;      
  // Loaded when host starts-up. 
  ext_cm_Startup = 1;
  // Loaded for very first time after installed. 
  ext_cm_UISetup = 5;
}

The third parameter is an object which represents the instance of your Add-in being hosted by the IDE, where the final parameter of type System.Array represents any user-supplied custom data (and can be ignored in our case).

The generated code is quite straightforward. Given that we have strongly typed the applicationObject member variable, however, we will now need to assign this variable using an explicit cast:

public void OnConnection(object application, 
  Extensibility.ext_ConnectMode connectMode, 
  object addInInst, ref System.Array custom)
{
  applicationObject = 
    (Microsoft.Office.Interop.Outlook.Application)application;
  addInInstance = addInInst;
}

We do have one other initial update to make to our OnConnection() method. According to this Knowledge Base article, a prim and proper implementation of OnConnection() should make a runtime test to determine if the connection mode is anything other than ext_ConnectMode.ext_cm_Startup, and if so, forward the incoming System.Array to the Add-ins implementation of OnStartupComplete():

public void OnConnection(object application, 
  Extensibility.ext_ConnectMode connectMode, 
  object addInInst, ref System.Array custom)
{
  // Cast to a strongly typed Application. 
...
  // If we are not loaded upon startup, forward to OnStartupComplete()
  // and pass the incoming System.Array.
  if(connectMode != ext_ConnectMode.ext_cm_Startup)
  {
    OnStartupComplete(ref custom);
  }
}

The OnDisconnection() Method

This method is called when the Add-in is being disconnected from the host (typically via an Add/Remove Add-in dialog box or upon host shutdown). The mode of disconnection is represented by the first argument of type ext_DisconnectMode:

public enum ext_DisconnectMode
{
  ext_dm_HostShutdown = 0;
  ext_dm_SolutionClosed = 3;
  ext_dm_UISetupComplete = 2;
  ext_dm_UserClosed = 1;
}

The Wizard-generated implementation of OnDisconnection() is currently empty. Obviously this is a place for the Add-in to perform any clean up it may need to do in order to shut down successfully. Again, according to this Knowledge Base article, a prim and proper implementation of this method should test the connection mode (this time for anything other than ext_DisconnectMode.ext_dm_HostShutdown) and forward the incoming System.Array to our implementation of OnBeginShutdown():

public void OnDisconnection(Extensibility.ext_DisconnectMode 
  disconnectMode, ref System.Array custom)
{
  if(disconnectMode != 
    ext_DisconnectMode.ext_dm_HostShutdown)
  {
    OnBeginShutdown(ref custom);
  }
  applicationObject = null;
}

The OnAddInsUpdate() Method

The OnAddInsUpdate() method is called if the end user inserts or removed Add-ins to the host (the Application.COMAddins property can be used to obtain the current list at runtime). In the case that you need to perform any special processing when the end user adds or removes a new Add-in, this would be the place to do so. The auto-generated implementation is currently empty, and can remain so.

The OnStartupComplete() Method

This method is called after the host application has completed loading. At this point, all host resources are available for use by the Add-in. This is an ideal place to construct the UI of your Add-in types, as you can safely obtain the set of Explorers and Inspectors.

The OnBeginShutdown() Method

Finally we have OnBeginShutdown(), which indicates that the host is in the process of shutting down (just before the call to the OnDisconnection() method). At this point you still have access to the host application, so this is an ideal place to remove any UI widgets you plugged into the active explorer.

Implementing the Add-in's UI

Our first order of business is to build a user interface for our Add-in's functionality. Given that an Add-in's UI is plugged into the hosting application, we will define a new member variable of type Microsoft.Office.Core.CommandBarButton to the Connect class type:

public class Connect : Object, Extensibility.IDTExtensibility2
{
...
  // Our UI will consist of a single CommandBarButton
  private CommandBarButton btnGetEMailStats;
}

The CommandBarButton widget (which sports the caption 'Statistics') will be plugged into Outlook's Standard command bar (Figure 10):

Figure 10. Our custom Statistics CommandBarButton

As mentioned, the OnStartupComplete() method is an ideal place to build UI elements, given that the host has fully come to life. Here are the steps required to insert new CommandBarButton types into an existing command bar:

  • Obtain the set of command bars from the active explorer.
  • See if your button is currently in the Control's collection of the command bar you wish to modify. If not, create and enable a new instance.
  • Hook up the Click event in the CommandBarButton to respond to your widget's custom functionality.

That being said, here is the updated (and highly annotated) implementation of OnStartupComplete():

public void OnStartupComplete(ref System.Array custom)
{
  // First, get access to the CommandBars on
  // the active explorer. 
  CommandBars commandBars = 
    applicationObject.ActiveExplorer().CommandBars;
  try
  {
    // If our button is already
    // on the Standard CommandBar, use it.
    btnGetEMailStats = (CommandBarButton)
      commandBars["Standard"].Controls["Statistics"];
  }
  catch
  {
    // OOPS!  Our button is not there, so
    // we need to make a new instance.
    // Note that the Add() method was 
    // defined to take optional parameters, 
    // which are not supported in C#.  
    // Thus we must specify Missing.Value.
    btnGetEMailStats = (CommandBarButton)
      commandBars["Standard"].Controls.Add(1, 
      System.Reflection.Missing.Value, 
      System.Reflection.Missing.Value, 
      System.Reflection.Missing.Value, 
      System.Reflection.Missing.Value);
    btnGetEMailStats.Caption = "Statistics";
    btnGetEMailStats.Style = MsoButtonStyle.msoButtonCaption;
  }
  // Setting the Tag property is not required, but can be used
  // to quickly reterive your button.
  btnGetEMailStats.Tag = "Statistics";
  // Setting OnAction is also optional, however if you specify
  // the ProgID of the Add-in, the host will automatically
  // load the Add-in if the user clicks on the CommandBarButton when 
  // the Add-in is not loaded. After this point, the Click
  // event handler is called.
  btnGetEMailStats.OnAction = "!<EMailStatsAddIn.Connect>";
  btnGetEMailStats.Visible = true;
  // Rig-up the Click event for the new CommandBarButton type.
  btnGetEMailStats.Click += new 
    _CommandBarButtonEvents_ClickEventHandler(
    btnGetEMailStats_Click);
}

Notice that our CommandBarButton has been configured to call a method named btnGetEMailStats_Click() when clicked. We will implement the custom logic in just a moment, but the following stub code will do for now:

private void btnGetEMailStats_Click(CommandBarButton Ctrl, 
  ref bool CancelDefault)
{
  // ToDo: Implement custom logic.
}

Tearing Down the UI

Now that we have the logic in place to create our CommandBarButton upon host startup, let's modify OnBeginShutdown() to remove our widget upon shutdown:

public void OnBeginShutdown(ref System.Array custom)
{
  // Get set of command bars on active explorer.
  CommandBars commandBars = 
    applicationObject.ActiveExplorer().CommandBars;
  try
  {
    // Find our button and kill it.
    commandBars["Standard"].Controls["GetEMailStats"].Delete(
      System.Reflection.Missing.Value);
  }
  catch(System.Exception ex)
  {MessageBox.Show(ex.Message);}
}

Note   As you might guess, there are numerous ways to extend the GUI of Outlook 2003. Check out Creating Managed Outlook Buttons with Icons for additional techniques.

Implementing the E-mail Statistic Logic

The last task is to implement the logic within the Click event handler. We will make use of the Outlook object model to determine the number of e-mails received and sent on a daily and monthly basis. Given your time building OPine, the following logic should be rather straightforward:

private void btnGetEMailStats_Click(CommandBarButton Ctrl, 
  ref bool CancelDefault)
{
  string statInfo;
  DateTime today = DateTime.Today;
  // The stats we are tracing. 
  int eMailsToday = 0;
  int eMailsThisMonth = 0;
  int eMailSentToday = 0;
  int eMailSentThisMonth = 0;   
  // Get items in user's inbox. 
  NameSpace outlookNS = applicationObject.GetNamespace("MAPI");
  MAPIFolder inboxFolder 
    = outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
  // Compare time received to current day / month
  // and update our counters. 
  foreach(object item in inboxFolder.Items)
  {
    MailItem mi = item as MailItem;
    if(mi != null)
    {
      if(mi.ReceivedTime.Day == today.Day)
        eMailsToday++;
      if(mi.ReceivedTime.Month == today.Month)
        eMailsThisMonth++;
    }
  }  
  // Build first part of statInfo string. 
  statInfo = string.Format("E-mails received today: {0}\n", 
    eMailsToday);
  statInfo += string.Format("E-mails received this Month: {0}\n", 
    eMailsThisMonth);   
  statInfo += "--------------------------------------\n";
  // Get items in user's sent item folder and
  // test again. 
  MAPIFolder SentFolder =   
    outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderSentMail);
  foreach(object item in SentFolder.Items)
  {
    // See if current item is a MailItem
    MailItem mi = item as MailItem;
    if(mi != null)
    {
      // It is, so get day/month stats.
      if(mi.SentOn.Day == today.Day)
        eMailSentToday++;
      if(mi.SentOn.Month == today.Month)
        eMailSentThisMonth++;
    }
 }  
  // Build last part of statInfo string. 
  statInfo += string.Format("E-mails sent today: {0}\n", 
    eMailSentToday);
  statInfo += string.Format("E-mails sent this Month: {0}\n", 
    eMailSentThisMonth);
  // Show results.
  MessageBox.Show(statInfo, "Current E-mail stats");
}

At this point our Add-in is complete! Assuming you were able to compile the project without errors, we can now register and test the EMailStatsAddIn functionality.

Registering Shared Add-Ins

Recall that when you create a Shared Add-In project with Visual Studio.NET 2003, you receive a setup project. To build the EMailStatsAddIn for use, right click on the EMailStatsAddInSetup project icon from the Solution Explorer and select Rebuild (Figure 11).

Figure 11. Building the installers

After this point, your project directory contains a standard setup executable and an *msi installer file. While you could use these files to install your Add-in, you can do so directly within Visual Studio.NET 2003 (Figure 12).

Figure 12. Installing EMailStatsAddIn via Visual Studio .NET 2003

Now, when you launch Outlook 2003, you should find your Statistics button in the Standard command bar. Sure enough, when clicked, you will be presented with the day's e-mail statistics (Figure 13).

Figure 13. EMailStatsAddIn at work

Note   It is worth pointing out that setup logic generated by Visual Studio.NET 2003 may need to be modified to suit your needs. Your .NET assembly may fail to register itself correctly for use by COM. Given this, your managed Add-ins will fail to appear in the Outlook COM Add-in dialog box, which can be quite problematic if you have not configured your Add-in to launch upon start-up. Omar Shahine from Microsoft has posted a workaround solution at http://go.microsoft.com/fwlink/?LinkId=30833. An alternative viewpoint regarding this issue can be found at http://blogs.msdn.com/robmen/archive/2004/04/28/122491.aspx.

Conclusion

As you have seen, .NET developers interact with Outlook 2003 types using a primary interoperability assembly. The Application type is the root of the model, which exposes numerous collections that represent various Outlook items. During the development of a command line drive mail application (OPine), you have seen how to create and obtain various items programmatically, as well as how to respond to select events. As illustrated, Outlook 2003 itself can be expanded by creating custom Add-ins. Visual Studio.NET 2003 provides a specific project template for this purpose, which yields a class implementing the IDTExtensibility2 interface, as well as a related installer project.

Clearly there is much more to the story of Add-ins and the Outlook 2003 object model than I had time to examine in this introductory article. My goal was to provide an introductory look at the Outlook 2003 object model through the eyes of a C# programmer, which I hope has left you in good position for further exploration. If you are interested in additional information, check out the following links:

About the Author

Andrew Troelsen is a consultant and trainer with Intertech Training. Andrew is the author of numerous books, including the award winning C# and the .NET Platform Second Edition (Apress 2002). He also authors a monthly column for (of all places) MacTech, where he explores .NET development on Unix-based systems using the SSCLI, Portible.NET, and Mono CLI distributions.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.