Second Life of a Hungarian SharePoint Geek

November 12, 2014

Managing Project Server Event Handlers via the Managed Client Object Model

Project Server events provide a great way to extend the functionality of the product by custom logic. For example, you can write code that will be executed if the web site of the project was created and customize the site with features that can not be integrated into the project site template itself, or respond to the deletion of the project and perform some kind of housecleaning, that is not part of the default deletion, like removing custom created project-specific SharePoint groups.

There is a page in the Central Administration that supports the event handler maintenance (see Central Administration / General Application Settings / PWA Settings (Manage) / Server Side Event Handlers, illustrated below).

image

There is unfortunately a major Application Lifecycle Management (ALM) related issue with this approach: It is a manual process that cannot be easily automated as part of the solution deployment.

Alternatively, one can deploy event handlers as part of a SharePoint package, as stated in the Project Server entity provisioning schema section of the Project Server programmability article. This method is described and demonstrated in the presentation Developer to developer – learn the Microsoft Project Online and Server extensibility (authors: Bill Avery and Chris Givens), see further details in section “Project Server App Packages” beginning at slide 75 of the presentation.

A sample of the Project Server entity provisioning XML including an event handler registration:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ProjectServerEntities xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <EventHandlers>
  4.     <EventHandler Id="6B92EF26-25CA-4716-9352-67FC2EF57BE3" Name="PWSCreated">
  5.       <EventName>WssInteropWssWorkspaceCreated</EventName>
  6.       <Description>Called when a new PWS site was created</Description>
  7.       <EndpointUrl>http://dummysite.org</EndpointUrl>
  8.       <AssemblyName>$SharePoint.Project.AssemblyFullName$</AssemblyName>
  9.       <ClassName>Customer.EPM.PSEventHandler</ClassName>
  10.       <CancelOnError>false</CancelOnError>
  11.     </EventHandler>
  12.   </EventHandlers>
  13. </ProjectServerEntities>

Note the EndpointUrl element, that contains only a dummy value in this case. In this case we use a local event handler implemented in a custom assembly, however we could use remote event handlers as well. This element is optional by the XSD of the Project Server entity provisioning schema, however, if we did not include this in the XML, we would receive a deployment error on the feature activation:

Value cannot be null. Parameter name: uriString

An empty value causes a similar error in the feature receiver:

Invalid URI: The URI is empty.

If the URL is not well formatted:

Invalid URI: The format of the URI could not be determined.

Formerly I applied this approach to deploy my event handlers, lookup tables and enterprise custom fields, but it turned out quickly, that whenever I deploy a new version of the package, the former entities are retracted and deployed again, resulting in a loss of information, for example, on tasks, projects and resources that already had a re-deployed enterprise custom fields assigned. In a developer environment it was only inconvenient, but in test and production system I found that simply unacceptable.

So I decided to write my own tools to list, register and remove Project Server event handlers using the managed client object model.

First, let’s see how to enumerate the event handlers and dump the most important properties:

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     projectContext.Load(projectContext.EventHandlers, ehs => ehs.Include(
  4.         eh => eh.Id,
  5.         eh => eh.Event,
  6.         eh => eh.AssemblyName,
  7.         eh => eh.ClassName,
  8.         eh => eh.Order));
  9.     projectContext.ExecuteQuery();
  10.  
  11.     projectContext.EventHandlers.ToList().ForEach(eh =>
  12.         Console.WriteLine("Event \r\nEvent Handler [Id: {3} AssemblyName: {4} ClassName: {5} Order: {6}]\r\n",
  13.         eh.Event.SourceName, eh.Event.EventName, eh.Event.Id, eh.Id, eh.AssemblyName, eh.ClassName, eh.Order));
  14. }

The output in our case:

image

Using the code below, we can register our custom event handler. To be able to use the predefined values in the PSEventID enumeration, you should include a reference to the Microsoft.Office.Project.Server.Library.dll assembly.

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     var ehci = new EventHandlerCreationInformation
  4.     {
  5.         Id = projDeletedEventHandlerId,
  6.         Name = "ProjDeleting",
  7.         AssemblyName = "Customer.EPM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b28db3dcb95229e1",
  8.         ClassName = "Customer.EPM.ProjectEvents",
  9.         Description = "Called when a project is being deleted",
  10.         EventId = (int)PSEventID.ProjectDeleting, // value is 49, handling ProjectEventReceiver.OnDeleting
  11.         Order = 100,
  12.         CancelOnError = false
  13.     };
  14.  
  15.     projectContext.EventHandlers.Add(ehci);
  16.     projectContext.EventHandlers.Update();
  17.     projectContext.ExecuteQuery();
  18. }

Note: this code only submits the requests for the event handler registration- The registration itself is performed asynchronously in a background process, so there is a delay after running the code until the event handler appears in the list.

Finally, if you don’t need the event handler any more, you can remove it using the following code (assuming you already know the ID of the event handler, for example, from the output of the event handler enumeration example):

  1. var projDeletedEventHandlerId = new Guid("0FB1389D-E620-4062-A99B-9E5716CC958E");
  2.  
  3. using (var projectContext = new ProjectContext(pwaUrl))
  4. {
  5.     var eh = projectContext.EventHandlers.GetByGuid(projDeletedEventHandlerId);
  6.     projectContext.EventHandlers.Remove(eh);
  7.     projectContext.EventHandlers.Update();
  8.     projectContext.ExecuteQuery();
  9. }

A similar code for unregistering the event can be found here, but the sample above is a bit simpler, using only a single ExecuteQuery call. You might need the double call if you don’t know the ID of the event handler yet. In this case you should look up the event handler by name, or by the type of the event it handles first, and it requires a former ExecuteQuery call to get the complete list of the event handlers. Then in the next step, you can already remove the event handler as illustrated above.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: