White Paper: MVP Pattern for Outlook

Visual Studio Tools for Office, commonly known as VSTO, is a set of development tools available in the form of a Visual Studio template-based add-in and a runtime that allows Microsoft Office 2003 and later versions of Office applications to host the .NET Framework Common Language Runtime (CLR) to expose their functionality via the .NET type system. This allows extensions to the Office applications to be written in CLI compliant languages as well as to use functionality and user interface constructs from Office applications in .NET applications.

Like any code developed for a project, VSTO code should be well constructed and be positioned for concepts such as unit testing. The goal of this document is to provide an in-depth examination of how patterns can be applied to build solid VSTO code. The focus will be on the development of an Outlook add-in, though the techniques can be applied to other Office application development efforts as well.

The first hurdle to understand is that the Visual Studio Unit Testing Framework does not support the unit testing of code found in a Visual Studio project containing a VSTO code base. Microsoft recommends that a custom host adapter should be built to get around the problem. That is a non-trivial exercise, and still might not alleviate the problem if Office is not available in the environment running the unit tests (a build server, for example). Because of this lack of support, a design that is both usable at run time and is also unit-test friendly as needed.

About the MVP Pattern

The Model/View/Presenter (MVP) pattern is a typical design pattern used in many software development projects. The pattern, along with its close counterparts such as Model/View/Controller (MVC) and Model/View/ViewModel (MVVM), forms a family of patterns that segregates code into the following categories:

  • The model is the code that manages the application’s data. Typically, the application’s model is the data read from and written to a data store.
  • The view is the code that manages what the application sees. The view contains the application’s user interface as well as events are affected by actions on the user interface, such as button clicks and drag-and-drop actions.
  • The presenter is the code that sits between the model and the view. Typically, applications do not act on raw data coming from the model. Instead, the raw data must be modified, enhanced, or reformatted for use in a view. In the MVP pattern, this work is performed by a presenter that presents data from the model to a view in a format compatible for use in the view.

Model Presenter View Diagram

Applying the MVP pattern to an Outlook 2007 project gives us the following:

  • The model is the data in the MAPI folders and items. When Outlook is connected to an Exchange server, for example, the Exchange data is the model.
  • The view is the Outlook 2007 add-in user interface, which displays the user interface of the add-in. The view code might also contain user interface event notifications, such as notifications sent when the user selects a folder or drags an Outlook item into a folder.
  • The presenter is the code that acts on the data obtained from the model and presents it to the view. Additionally, any generic business rules (such as “when disconnected from Exchange, my add-in should display a message”) are available in the presenter.

Tying It All Together

In the generic case, the model, view and presenter logic would be contained in a single Visual Studio 2010 VSTO project. Let’s call this VSTO project MyCompany.MyAddIn.Outlook2007.

The Outlook 2007 presenter code should be unit tested because it has all of the interesting business logic (such as “when disconnected from Exchange, my add-in should display a message”). However, because VSTO code cannot be unit tested, the Outlook 2007 presenter code cannot be built into the VSTO code; doing so would lock the presenter code out from any unit testing scenarios. Given this need for a test-friendly design, the Outlook 2007 code is spread amongst two projects:

  • MyCompany.MyAddIn.Outlook2007, which is the VSTO code that contains the view
  • MyCompany.MyAddIn.Outlook2007.BusinessLogic, which contains the presenter and support for the model

In a unit testing scenario, the code in the MyCompany.MyAddIn.Outlook2007.BusinessLogic project, which has no reliance on VSTO technology, can be used. The main VSTO code in MyCompany.MyAddIn.Outlook2007 makes use of the code in MyCompany.MyAddIn.Outlook2007.BusinessLogic when necessary. At runtime, the end user loads the code from MyCompany.MyAddIn.Outlook2007 as the “main” Outlook 2007 add-in, which calls into the code in MyCompany.MyAddIn.Outlook2007.BusinessLogic as needed.

Delegating to the Presenter

Because of this design, and because VSTO code cannot be unit tested, it is important that the view code contain as little code as possible. It should, in general, handle Outlook events as needed and then delegate the actual business functionality to the presenter.

For example, suppose that the Outlook 2007 add-in needs to perform some processing on an email item when the item is copied from a MAPI folder and pasted into a folder managed by the add-in. In a simple design in which all code is in the VSTO project, this work would be done entirely within the VSTO code:

public void OutlookEvent_OnItemPastedToFolder(
    object PastedItem,
    object TargetFolder)
{
    // get data from PastedItem
    // get name of destination folder
    // process pasted item

}

Instead, the VSTO code should gather all of the necessary information and delegate it to the presenter:

public void OutlookEvent_OnItemPastedToFolder(
    object PastedItem,
    object TargetFolder)
{
    // get data from PastedItem
    // get name of destination folder

    presenter.ProcessItem(data,       destinationFolderPath);
}

The presenter, in turn, would perform the actual work:

// Presenter code


public void ProcessItem(
    object PastedItemData,
    string DestinationFolderPath)
{
    // process pasted item
}

This segregated design is now testable, and the presenter can be unit tested to ensure that it behaves appropriately through a variety of success and failure conditions.

Interface-Based Abstractions to Outlook Data

The Outlook 2007 VSTO code works with many classes in the Microsoft.Office.Interop.Outlook namespace, such as MAPIFolder, to work with Exchange folder and item data. Since these classes are tightly tied to Outlook, and therefore, are VSTO components, they cannot be removed from the main Outlook 2007 VSTO project. However, the presenter will also need to work with folder and item data. If the presenter is in a separate project, oblivious to VSTO constructs, how can the presenter work with folder and item data? The answer lies in an interface-based design that abstracts MAPI specifics into a generic interface.

Interface-Based Abstraction to Outlook Folders

A MAPI folder can be represented by a generic folder interface, defined within the MyCompany.MyAddIn.Outlook2007.BusinessLogic project. This interface, called IFolder, would provide all of the information needed to access information about a folder:

namespace MyCompany.MyAddIn.Outlook2007.BusinessLogic.Interfaces
{
    public interface IFolder
    {
        string Name { get; set; }
        IFolder Parent { get; }
        bool ContainsMail { get; }
        IFolder GetChildFolder(string folderName);
        bool ChildFolderExists(string folderName);
        IFolder CreateChildFolder(string folderName, FolderContent content);
    }
}

With this interface, many folder details can be obtained without requiring knowledge of MAPI specifics, which are closely tied to both VSTO technology and a running instance of Outlook, neither of which will be available when code is unit tested.

For production code, the IFolder interface will be implemented in the main MyCompany.MyAddIn.Outlook2007 project in a class called OutlookFolder. This class will be given a raw MAPIFolder when it is constructed and will implement the IFolder interface in terms of the encapsulated MAPIFolder:

namespace MyCompany.MyAddIn.Outlook2007
{
    public class OutlookFolder : IFolder
    {
        private MAPIFolder folderInOutlook;

        /// <summary>
        /// The name of the folder.
        /// </summary>

        public string Name
        {
            get
            {
                if (folderInOutlook != null)
                    return folderInOutlook.Name;
                return string.Empty;
            }
            set
            {
                if (folderInOutlook != null)
                    folderInOutlook.Name = value;
            }
        }


        /// <summary>
        /// The parent folder of the current folder. Returns null
        /// if there is no parent.
        /// </summary>

        public IFolder Parent
        {
            get
            {
                if (folderInOutlook.Parent == null)
                    return null;
                if ((folderInOutlook.Parent is MAPIFolder) == false)
                    return null;
                return new OutlookFolder(folderInOutlook.Parent);
            }
        }


        /// <summary>
        /// Determines whether or not the folder contains mail items.
        /// </summary>

        public bool ContainsMail
        {
            get
            {
                if (folderInOutlook.DefaultItemType == OlItemType.olMailItem)
                    return true;
                return false;
            }
        }


        /// <summary>
        /// The constructor for the class.
        /// </summary>
        /// <param name=”folderInOutlook”>
        /// A reference to the MAPIFolder object to be used as
        /// the underlying object for the folder.
        /// </param>
        public OutlookFolder(MAPIFolder folderInOutlook)
        {
            this.folderInOutlook = folderInOutlook;
        }

        /// <summary>
        /// Gets a child folder of the current folder.
        /// </summary>
        /// <param name=”folderName”>
        /// The name of the child folder to find.
        /// </param>
        /// <returns>
        /// A reference to the child folder. A null is returned if
        /// the named child folder is not available.
        /// </returns>

        public IFolder GetChildFolder(string folderName)
        {
            if (ChildFolderExists(folderName) == false)
                return null;
            return new OutlookFolder(folderInOutlook.Folders[folderName]);
        }

        /// <summary>
        /// Determines whether or not a named child folder exists.
        /// </summary>
        /// <param name=”folderName”>
        /// The name of the child folder to find.
        /// </param>
        /// <returns>
        /// True if the child folder exists; false otherwise.
        /// </returns>

        public bool ChildFolderExists(string folderName)
        {
            try
            {
                var childFolder = folderInOutlook.Folders[folderName];
                return true;
            }
            catch (COMException)
            {
                return false;
            }
        }

        /// <summary>
        /// Creates a folder as a child of the current folder.
        /// </summary>
        /// <param name=”folderName”>
        /// The name of the child folder to be created.
        /// </param>
        /// <param name=”content”>
        /// The type of content that the folder will contain.
        /// </param>
        /// <returns>
        /// A reference to the newly created folder.
        /// </returns>

        public IFolder CreateChildFolder(string folderName, FolderContent content)
        {
            var OutlookFolderContentsValue = GetOlDefaultFoldersValue(content);
            var newFolder = folderInOutlook.Folders.Add(folderName, OutlookFolderContentsValue);
            return new OutlookFolder(newFolder);
        }
    }
}

The class may, of course, contain additional methods, but the important requirement is that it implements the IFolder interface.

Once this class is in place, the view can create OutlookFolder objects from the MAPIFolder objects sent into event handlers from Outlook. Once those objects are available, their IFolder implementations can be sent to the presenter:

/// <summary>
/// Handler for the Outlook BeforeItemPaste event, which fires when
/// an Outlook item is pasted into a folder.
/// </summary>
/// <param name=”ClipboardContent”>
/// The content of the clipboard.
/// </param>
/// <param name=”Target”>
/// The Outlook folder to which the move or copy will take place.
/// </param>
/// <param name=”Cancel”>
/// A Boolean specifying whether or not the operation should be
/// canceled.
/// </param>

void explorer_BeforeItemPaste(
    ref object ClipboardContent,
    Outlook.MAPIFolder Target,
    ref bool Cancel)
{
    var IFolderTarget = new OutlookFolder(Target);
    // IFolderTarget can be used by the presenter as an IFolder
    _presenter.DoPaste(IFolderTarget);
}

Interface-Based Abstraction to Outlook Item Store

For some actions, such as getting root folder information, Outlook VSTO code needs to work with Outlook’s data source. Typically, the Outlook data source is accessed from Outlook VSTO code through a COM interface available from within VSTO named Microsoft.Office.Interop.Outlook.NameSpace. An implementation of Microsoft.Office.Interop.Outlook.NameSpace encapsulates the entire Outlook item store.

A data store can be represented by a generic folder interface, also defined within the MyCompany.MyAddIn.Outlook2007.BusinessLogic project. This interface, called IItemStore, would provide all of the information needed to access information from an item store:

namespace MyCompany.MyAddIn.Outlook2007.BusinessLogic.Interfaces
{
    /// <summary>
    /// An interface for defining an item store.
    /// </summary>
    /// <remarks>
    /// The <see cref=”IItemStore”/> interface provides an abstraction
    /// of an item store so that the item store functionality can be
    /// mocked up during unit testing. Unit tests should not work
    /// against an actual Outlook item store instance, for example, but
    /// should be using a mocked-up version. This interface allows for
    /// that mock abstraction to be built.
    /// </remarks>

    public interface IItemStore
    {
        IFolder GetTopLevelFolder(string folderName);
        IFolder CreateTopLevelFolder(string folderName);
    }
}

With this interface, many item store details can be obtained without requiring knowledge of Outlook namespace specifics, which are closely tied to both VSTO technology and a running instance of Outlook, neither of which will be available when code is unit tested.

For production code, the IItemStore interface will be implemented in the main MyCompany.MyAddIn.Outlook2007 project in a class called OutlookItemStore:

namespace MyCompany.MyAddIn.Outlook2007
{
    /// <summary>
    /// A representation of an Outlook item store. The item store
    /// stores items in folders.
    /// </summary>
    /// <remarks>
    /// The <see cref=”OutlookItemStore”/> class implements
    /// <see cref=”IItemStore”/>. 
    /// </remarks>

    public class OutlookItemStore : IItemStore
    {
        private ThisAddIn addIn;
        private Application app;
        private Explorer explorer;
        private NameSpace session;
        private MAPIFolder inbox;
        private MAPIFolder itemStoreRoot;


        public OutlookItemStore()
        {
            addIn = Globals.ThisAddIn;
            app = addIn.Application;
            explorer = app.ActiveExplorer();
            session = explorer.Session;
            inbox = session.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
            itemStoreRoot = inbox.Parent;
        }


        public IFolder GetTopLevelFolder(string folderName)
        {
            foreach(var obj in itemStoreRoot.Folders)
            {
                Folder folder = obj as Folder;
                if(folder!=null && folder.Name == folderName)
                    return new OutlookFolder(folder);
            }
            return null;
        }


        public bool TopLevelFolderExists(string folderName)
        {
            var folderObject = GetTopLevelFolder(folderName);
            if (folderObject == null)
                return false;
            return true;
        }


        public IFolder CreateTopLevelFolder(string folderName)
        {
            var OutlookFolder = itemStoreRoot.Folders.Add(folderName);
            return new OutlookFolder(OutlookFolder);
        }
    }
}

In keeping with the generic, interface-based design, folder references returned from an IItemStore implementation are returned as IFolder references.

Unit Testing Readiness

With all of this in place, we have code structured as shown in the following diagram:

MVP Pattern for Outlook Diagrams

As it turns out, this design is appropriately structured for unit testing. Since the presenter contains the Outlook add-in’s core business functionality and works with interfaces abstracted away from concrete Outlook and MAPI instances, the presenter can be unit tested in a straightforward manner. The unit tests can be stored in a separate unit test project, such as MyCompany.MyAddIn.UnitTests, referencing the MyCompany.MyAddIn.Outlook2007.BusinessLogic project.

Tools such as Typemock or frameworks such as Moq, can be used to automatically implement fake (stubbed) instances of the IItemStore and IFolder interfaces. Those stubs, which can be arranged to return information based on certain test parameters, can be passed into the presenter for testing. Since the presenter works only with interface implementations, it is unaware that it is working with a stub rather than with true MAPI data, and performs its work unaware of the actual interface implementation.

Conclusion

Although VSTO projects cannot be unit tested from Visual Studio, a careful application of the standard Model-View-Presenter pattern can bring unit testability back to the most important part of the code: the business logic in the presenter. Additionally, separating the presenter from the view allows for a nice separation of concerns usually enjoyed only by Web-based or XAML-based applications. With a bit of up-front design, VSTO projects can retain all of the benefits of unit testing while still providing everything necessary to respond to all of the Office events required by a well-integrated add-in. Patterns in the MVP family are not often considered in the development of VSTO projects, but doing so can bring many benefits to the design, and even maintainability, of the code.

Download PDF

Click to download the MVP Pattern for Outlook white paper as a PDF.

Copyright 2013 Magenic, All rights Reserved