This a post long in the making. Over a year ago, I started working on a Microsoft Project 2003 to Microsoft Project Server 2007 migration. The learning curve was steep. I had taken a hiatus from developing solutions with Microsoft Project—a long hiatus, really. The last time I seriously did any development with Microsoft Project, aside from the occasional macro or add-in, was 2001. I think I was blessed to come back on the scene when I did because Microsoft Project Server 2007 is truly a giant step forward for developers tasked with building solutions on the Microsoft EPM stack. During the years between 2001 and 2006, I had become a consumer of Microsoft Project and a practitioner of project management, but all of my development efforts were focused on solving other problems in the project management space. The work I did between 2006 and today taught me many things and I have tried very hard to share that learning with the community.
The moniker under which this work has progressed is “mpFx”. The term “Fx” is a short for “functon”, and has been a suffix on a few Microsoft technologies, such as .NET FX, WinFx, and others. I chose the suffix because it rolls off the tongue nicely, and because mpFx started out as a collection of functions that blended data and functionally from several PSI web services.
The work also, by the way, changed my life completely. I was blogging on the topic almost daily as I pushed through the SDK. At a time when I was doing Microsoft Project development during my downtime while working on a another product, I found myself slightly obsessed with the new technology. Many things that had proven difficult or even impossible were now accessible via the PSI. The platform is amazing. It will see improvement but that is a fact that accompanies any major redesign or first offering of an API. Good API design is a wicked problem and the Project PG did a great job.
As my interest grew, I started to reach out to old friends. Guys like Brian Kennemer, for example. Because of the work I was doing and folding into mpFx, I was able to react to Brian’s recommendation to come on board at Microsoft a second time. So, for that I thank Brian, Jeff, and the whole MCS EPM team.
Developing solutions with PSI is about two technologies: .NET 2.0 XML web services and strongly typed datasets. First and foremost it is about XML web services. Once a developer pushes through understanding the basic makeup of PSI, it is relatively easy to develop against the stack, but it is often not very pretty. To create a resource, add the resource to a project team, and wait for the operation to complete, you must instantiate three web services (Resource.asmx, Project.asmx, and QueueSystem.asmx), rig all three web services, and make several web method calls. There is absolutely nothing wrong with this and it is appropriate to the design of the PSI, but it is cumbersome and difficult to understand for the novice.
And that is exactly what I was in the early days, a novice.
So, my intention with mpFx was to create a technology bridge for the developer accustomed to dealing with object models, to the world of XML web services and strongly typed datasets. What I was looking for was semantics such as this:
1: using(ProjectServer projectServer = new ProjectServer("http://projectserver/pwa", "colby.africa", "yoohoo"))
3: projectServer.Projects.Create("New Project");
As you know or will come to find out, this involves many, many lines of code to rig the web services and make the calls, assuming that the .Projects.Create call waits for the operation to complete.
At its heart, mpFx is a set of libraries that coalesces PSI web services and data into an object model, which eases development and provides additional services to the developer. The larger picture is that mPfx is a collection of tools and technologies designed to enable developers and service the broader Microsoft Project community as a whole.
The core of mpFx is two .NET 2.0 class libraries written in C#. The class libraries are Mcs.Epm.MicrosoftProject.mpFx and Mcs.Epm.MicrosoftProject.mpFx.WinForms. Together, the two libraries provide services and user interfaces designed to make developing solutions with Microsoft Project a bit easier. As I was writing the libraries, I started building a test harness application to test the various services and user interface elements. A few weeks ago, I decided that given the volume of services provided that a new approach to testing was needed. The test harness code became so dense and so lacking in basic aesthetics that I chucked it all and start over. About the same time, I release PSI Extension Generator 1.0 to MSDN Code Gallery and created an internal tool for managing Project Server event handlers. It occurred to me that I could build a containment application which would not only serve to test mpFx, but would also be a delivery vehicle for all of my tools and utilities.
Thus mpFxClient was born. MpFxClient is a bare-bones container application built around a plugin architecture. Here are some of the highlights of mpFxClient:
The mpFxClient user interface is quite simple, as you can see in the image to the left. Again, the purpose of the container application is to create a ProjectServer object and host plugins.
A couple of interesting points are that the Microsoft Project Professional profiles are read from the registry, so the login experience is similar and that impersonation is supported so you can specify credentials and log into any Project Server implementation you might have access to on the network.
After successful login, mpFxClient looks to a sub directory beneath the installation directory for plugins. Plugins implement the IMpFxClientPlugin interface. Again, for more information, read this. The plugins are loaded, injected with dependencies such as the ProjectServer object and the toolbar, as well as the parent form so MDI child relationships can be established.
Here is a screenshot of the toolstrip after the plugins have been loaded:
Note that in the screen to the left, the vast majority of the dropdown buttons don’t exist. These were added by the either the plugins themselves or the plugin host (PluginHost), which is a separate object that ties the mpFxClient application and the plugins together.
The PluginHost object also knows about the online gallery, which is hosted on colbyafrica.com. The gallery is built around a file called catalog.xml. Here is a scaled down version of the file:
2: <plugin guid="6187ac29-c0ce-42b9-a5b9-7aab52dadb5e"
3: name="PSI Extension Generator Plugin"
4: description="A plugin for generating PSI Extension Web Services"
5: author="Colby Africa"
8: tag="Development Tools"
9: zip="PSIExtensionGeneratorPlugin.zip" />
Here is a screenshot of the Plugin Gallery Browser, which will give context to the discussion:
The catalog.xmls is a collection of <plugin/> elements, each describing a plugin. The three attributes that may need some explanation are:
One of the first plugins I wrote was a plugin which enabled publishing plugins the gallery. The tool helped write the tool.
By default, the Plugin Publisher Plugin (P3) is not installed, mainly because I need to control what is published to the gallery. If it is installed, a button is added to the Plugin Gallery Browser: . Clicking the button brings a login dialog up:
Once successfully logged in, a new button is added to the Plugin Gallery Browser toolstrip: . Clicking the button displays the Add Plugin dialog:
Clicking the button allows the user to select a plugin assembly. The assembly is loaded, dependencies are detected and added to the Files list at the bottom of the dialog:
Clicking OK does several things. The files listed in the Files list are zipped up, the zipped file is uploaded to the online gallery, and catalog.xml is updated with the new plugin information. Once the upload is complete, the Plugin Gallery Browser is updated to reflect the newly available plugin:
Note the button, which indicates that the plugin exists on the server but doesn’t exist locally. Let’s take a step back and look at the Plugin Manager feature
All plugins installed locally can be managed through a single user interface, called the Plugin Manager. The Plugin Manager allows for the loading, unloading, installation (in the case that a plugin was not distributed via the online gallery), and management of plugin options. Here is a screenshot of the Plugin Manager dialog:
Note that the Event Handler plugin, which we recently added to the online gallery is not present. We will get back to installing a plugin from the gallery in moment. The buttons along the top of the dialog allow for loading, unloading, and installing plugins. Also, note the Options tree node in the tree control on the left. Each plugin exposes an Options user control, which is dynamically loaded into the Plugin Manager dialog’s tab control:
The options control you see above is the options control for the PSI Extension Generator Plugin. Note that a tab exists for each installed plugin.
Returning to the Plugin Gallery Browser and clicking the button will install the Event Handler Plugin to the local gallery. Several things happen when a plugin is published from the online gallery to the local gallery. First, the catalog.xml file is read to determine which zip file should be downloaded. After the download is complete, the files are unzipped into a temporary directory, a new sub directory beneath the plugins sub directory is created, the files are moved over, and the plugin in registered with the PluginHost, which in turn loads the plugin into the application. The following screens are before and after this process occurs:
The green plus is replaced by the lightening image to indicate that the installation is complete.
The mpFxClient menu has been modified to include the Event Handler menu items:
I am not going to spend much time on any particular plugin, but I am going to give you a sneak peek at the Event Handler Plugin. I designed the plugin after doing several engagements which used event handlers and found the SharePoint-based administration tool to be cumbersome. Also, I took Mike Shughrue’s good work on installing event handlers and incorporated it here. Here is a screenshot of the primary user interface:
The tree to the left are all of the available event handler receivers. The Details View to the right shows the event handler specifics. In this case, there are more than one event handler registered for the TimesheetUpdated post-event. Thus, there are two tabs in the details view, one for each.
Clicking the button brings up the Add Event Handler panel:
From here, it is easy to add event handlers, although this is still a work in progress.
The mpFx project is organized by Applications and Libraries, as seen to the right. The main application is mpFxClient, followed by the individual plugins. The two main libraries, and the previously not mentioned Mcs.Epm.MicrosoftProject.mpFx.Client.Shared library live in the Library solution folder.
Mcs.Epm.MicrosoftProject.mpFx.Client.Shared is code shared by the plugins and mpFxClient, including the IMpFxClientPlugin interface, the PluginHost object, and a variety of shared functions.
The rest of the solution elements are dedicated to testing and demonstrations.
I have written quite a bit on mpFx internals, so I am not going to repeat too much of that. Suffice it to say, mpFx is designed for learning and for specific scenarios. When I use mpFx in client engagements, I tend to trim it down just to the pieces I need. In a high-volume, data-intensive, server-side application scenario, care should be taken in considering whether or not to use mpFx as it encapsulates whole swaths of the PSI.
At the time of writing, mpFx is over 150,000 lines of code.
Please read this post for more information.
Let’s review a simple example:
1: using System;
2: using Mcs.Epm.MicrosoftProject.mpFx;
3: using Mcs.Epm.MicrosoftProject.mpFx.ProjectsWebService;
5: namespace ConsoleTest
7: class Program
9: static void Main(string args)
13: using (Log log = new Log(@"C:\temp", LogPeriod.Hourly, "Test Log", true))
14: using (ProjectServer projectServer = new ProjectServer("http://epm2007demo/pwa", DataStoreEnum.WorkingStore, log))
15: using (ProjectDataSet projectDataSet = EntityFactory.NewProject("Demo6"))
17: projectServer.Projects.Create(projectDataSet, false, true);
20: catch (MpFxException exception)
The above example does all of the work to rig the web services, plus it adds a factory object for creating project datasets (among others) and it uses a log object which is injected to capture information about operations.
More throughout the week as I get closer to publishing the mpFx Preview to MSDN Code Gallery.
Have a great week.