Guest Post by Eric Legault
Using RoamingSettings with your add-in to store application data for your web-based Outlook add-in is all well and good. However, it may not take much to exceed the 32k storage limit, plus you are also left to define the data format as you see fit – which of course means extra work. Learn how you can overcome this by creating a hidden folder in the user’s mailbox to store and retrieve your application’s business objects as serialized settings in an email message, by simply passing JSON objects that then get serialized into XML for storage and vice-versa.
As is usual when you’re learning any kind of new technology, finding useful starter articles and/or projects can prove difficult. Hopefully when you’re done reading this you’ll come away with some big and bright lightbulbs going off in your head! I’ll be covering the foundations of using application data storage, managing business objects, performing common messaging operations and building a simple but sharp and responsive UI. The sample project accompanying this article should not only prove to be a very useful learning tool, but can serve as the basis for implementing a custom storage provider which you can easily re-use for your own projects.
The Cool Tech We’ll Use
I’ve got quite the mix in this solution – and I guarantee you’ll come away from this article with some useful APIs, techniques and code that you can steal:
· A custom reading pane Outlook Add-in
· Some jQuery (mainly for selectors, a dialog widget and some Deferreds)
· Exchange Web Services (aka EWS, but using SOAP and not that fancy managed API - so the hard way)
· JSON to XML and XML to JSON (thanks to x2js)
Here’s a peek at what we’re doing:
Figure 1: The activated "Storage Provider Demo" Outlook add-in
Creating a Better Method for Solution Storage
The Mailbox API already provides a native way to read/write custom application for Outlook add-ins via the RoamingSettings object (see “Get and set add-in metadata for an Outlook add-in” for a good overview). However, there are limits of 32KB for each setting and 2MB maximum that a single add-in can store. That may sound like enough, but not in the real world! For one add-in I was writing I was storing mailbox folder paths and PR_ENTRY_ID values for each folder in RoamingSettings, and during testing with my own production mailbox (with 400+ folders) I hit the 32KB limit quickly. I immediately realized this was no good at all and that I needed to build something custom. So I built it!
Using the same approach as RoamingSettings, my Custom Solution Storage Provider simply uses a hidden folder in the user’s mailbox and a Post message (you can use an e-mail message if you want – just alter the CreateItem request) to store required content in the message body. Not only that, it stores it as XML so that you can easily use JSON objects to manage your application settings/data and serialize/de-serialize that data via this hidden storage. All you need to do is use EWS to read and write to the message whenever you want. However, keep in mind that EWS calls in Outlook add-ins limits both request and response calls to 1MB. It may be rare to exceed this limit, but please keep this in mind.
What We’ll Be Doing
· Using various EWS operations:
· Creating a folder in the root of a mailbox with CreateFolder
· Creating a message with CreateItem
· Retrieving the body content of an existing message with GetItem
· Updating the body of an existing message with UpdateItem
· Persisting JSON objects to XML, and create JSON objects from XML
· Keeping the code organized using the jQuery Module Pattern
· Executing multi-step EWS operations using jQuery Deferreds
· Theme the UI using Office Fabric
· Effectively displaying application notifications and dialogs in the context of an Outlook add-in
· Dynamically manipulating HTML elements on the page using jQuery
What You’ll Need
· Quick Start: “Create and debug Office Add-ins in Visual Studio”
· An Office 365 account or Exchange 2013+ Mailbox (need a test account?)
· Source for the Office UI Fabric (optional; included in sample project)
· Source for x2js for the JSON<->XML (optional; included in sample project)
· The source for this sample project if you want to “click and learn”
· Napa version: https://aka.ms/Dtxwlh
· GitHub repo: https://github.com/elegault/OutlookAddinStorageProviderDemo
How This Will Work
We’ll create a simple but effective UI that’ll showcase how to:
· Create a named e-mail folder that’ll contain the hidden solution storage message
· Use input elements to create some sample JSON business objects in memory and output their XML representation to the page
· Save and retrieve add-in settings and business logic/XML to both RoamingSettings (to store the IDs for the folder and solution storage message) and our solution storage message
· Reset add-in settings if needed to start from scratch
If you want to step through the code and see how everything works, download the source code (see links above in “What You’ll Need”) and run the project. If you need help with that, see “Create and debug Office Add-ins in Visual Studio”.
You can also install the add-in and run it without needing the source code at all. I’ve published the add-in to a web site in Azure - simply download the manifest file and install it from the Manage add-ins page under Mailbox options in Office 365. This is one way of releasing an add-in without having to publish it to the Office store (as long as you have an https cert).
Core Project Components
All Office Add-in projects in Visual Studio are comprised of two separate projects within the solution: one for the add-in itself (MailAddinStorageProvider) and one for the web component (MailAddinStorageProviderWeb). You can take a look at “Create and debug Office Add-ins in Visual Studio” for a quick walkthrough if you’re a first-timer. The add-in’s project is simple and just contains an XML manifest that’s used to basically declare the add-in’s description and intent. For this project, the main requirements are to:
· Activate for read messages (as opposed to compose messages)
· Ask for ReadWriteMailbox permissions (necessary for EWS calls)
· Request a display size of 450 px (the maximum; we need a lot of space)
The web project really only contains five files that have all our custom guts:
Note: these folder paths differ from the Visual Studio project template defaults
Files for referenced components are stored in other folders:
· Content: Office and Fabric CSS and scripts
Figure 2: Office Add-in project components in Visual Studio Solution Explorer
Architecting the Solution Storage Provider
The heavy lifting for implementing this custom storage provider is performed by using EWS operations. Note that we’re limited to a sub-set of EWS operations because not every method that is typically available in the EWS Managed API (which can be used for client applications, but NOT web apps) are supported in Outlook add-ins. The first operation that we need to call is CreateFolder, which will create a folder with the name provided in the Folder Name textbox after you click the “Create Folder” button in the NavBar. We then need to store the Id value for that folder in RoamingSettings so that we know where to create our solution storage message. For that, we need to call CreateItem and once again save the Id for that message in RoamingSettings so that we can get and update the message body when required to persist our application data via GetItem and UpdateItem calls.
Callout: EWS Operations in a Nutshell
To call an EWS operation from an Outlook add-in requires calling the Mailbox.makeEwsRequestAsync method. I’ll use the CreateFolder call as an example of how to construct an EWS request. The initial call is made in the createSolutionStorage feature:
The first parameter for makeEwsRequestAsync requires a string in the form of an XML SOAP request with the details of the EWS operation we are asking Exchange to perform; I’m calling the ewsRequests.getCreateSolutionStorageFolderRequest function to build and return that string, helped with the folderName and isHidden parameters for that function:
We have to set the value of the DistinguishedFolderId property accordingly based on whether we want to make the folder hidden or not. If it does not need to be hidden, the folder will be created at the root of the Mailbox and visible within the folder hierarchy in Outlook. However, I recommend for purposes of testing that you do NOT create a hidden folder, as there is no code provided that can delete that folder if you want to reset your solution storage and start from scratch. To reset your storage with a visible folder, you can delete the folder in Outlook (and hence delete the hidden message) and then make sure to click the “Clear Settings” button to remove the Ids for the folder and message from RoamingSettings (the named settings themselves are deleted, not just the values). If you do want to delete the hidden folder, you can use OutlookSpy, MFCMAPI or the EWSEditor to find that folder in the MsgFolderRoot and delete it using those utilities.
The second parameter is the function callback that will read the XML response returned from Exchange; in this case, the createSolutionStorageFolderCallback function:
Note the judicious use of evaluations for various response scenarios. Ideally errors will never be hit, but you’ll find these checks invaluable when you’re first trying out any kind of EWS operation as they almost never work out at first run! However, if everything works as it should then the results of our call will contain the FolderId value for our new folder in the XML response body which we can then persist in RoamingSettings to use later when we need to create the storage message in that folder. A typical response body would look something like this:
Ready to Store Your Data!
Now that we have our solution storage folder created, go ahead and create some business objects! Enter an Artist Name, select a genre and click the “Add Artist” button:
Figure 3: Using the add-in's UI to generate business objects and XML
Behind the scenes, the addArtist() function create a new instance of a Band object, adds it to our solutionStorage.appicationData.FavoriteBands object and then passes solutionStorage.appicationData to the X2JS. json2xml_str function, which returns the data as serialized XML:
Here’s what our FavoriteBands collection of Band objects looks like in XML:
Now that we have the XML, we can push it up to storage – go ahead and click the “Update Storage” button. This will run the updateStorage() function, which is setup to detect whether this is the first time we’re using the solution storage, because if it is we’ll create it first – otherwise we’ll just update it. Since this is our first time using storage, this function will call solutionStorage.createStorageItem() function which in turn issues an EWS request for CreateItem. When we read the response from solutionStorage.createStorageItemCallback we can grab the Id of the new message and persist it to RoamingSettings. Later calls to update storage will use the EWS UpdateItem operation instead.
Both CreateItem and UpdateItem calls are similar in that they are taking our business objects (stored in solutionStorage.applicationData) and passing it to the X2JS.json2xml_str function which conveniently returns to us the XML string that we can store in the message body:
As you can see in the folder that we created, the XML markup is happily living in the message body of the Post message that’s being used for solution storage:
Figure 4: Data for the add-in stored as XML in an Outlook Post item
To get started, simply add some references to the Fabric CSS via CDN (or host them yourself) in the head of the HTML:
Then apply a Fabric class to make otherwise bland HTML controls like buttons and dropdowns jump out, such as in the controls we’re using to add and remove business objects:
Code Review Time
Object-Oriented Programming adherents will notice a similarity in how the solutionStorage feature was designed. I followed the jQuery Module Pattern and broke down the core methods and properties into loosely coupled units of functionality the best I could. The key constructs are:
· ApplicationData, FavoriteBands and Band object functions
· Features for EWS operations (ewsCallbacks and ewsRequests) and the core [module] function used by all Office add-ins
· The solutionStorage feature with core properties and functions for:
· get StorageIds()
Figure 5: The Module Pattern with core features
One thing that’s very handy when dealing with EWS calls is being able to cleanly execute multi-step EWS operations using jQuery Deferreds. I’m not a fan of calling a second EWS call from within an EWS callback function as your code can get very messy very quickly. By using Deferred objects you can have more legible code through chainable constructors to better organize your asynchronous calls. A good example of how this is used in our sample project is in the createFolder function:
After we make the call to create the folder in the “when()” branch, we can then wait until the “then()” branch executes to decide if we need to make a second EWS call to make the folder hidden. This is a better approach than making this evaluation inside the first callback within the “when()” branch. Deferreds can quickly become essential if you need to make three or more EWS calls (it happens!) in a row, and the compactness of the code is far better than navigating amongst multiple methods when coding or debugging.
Feel free to rip out the solutionStorage feature for your own projects and extend or modify it as you see fit. You may require multiple storage items for advanced scenarios or need to use JSON strings instead of XML for application data. Take the UI examples even further with other cool Fabric components like the Panel, DatePicker, PersonaCard or ListItem. Or borrow the code organization patterns and EWS XML requests for your own unique solution. You have your wicked web dev skills already, and now you hopefully have a good foundation for taking what you know to the next level with Office add-ins. Cheers!
Originally posted on http://www.ericlegaultconsulting.com/blog/