How to: Create a Project Server Event Handler and Log an Event

Applies to: Office 2010 | Project 2010 | Project Server 2010 | SharePoint Server 2010

In this article
Developing an Event Handler
Deploying the Event Handler
Registering the Event Handler Association
Testing the Event Handler: Using the Event Viewer and the ULS Log
Debugging the Event Handler

The Events service provides a way to extend Microsoft Project Server 2010 by adding new business logic. Project Server raises pre-events and post-events when Project Server Interface (PSI) methods change business objects such as Project, Task, Resource, or Timesheet. When you associate an event handler with an event, Project Server runs the event handler when that particular event occurs. This article shows how to create, deploy, test, and debug an event handler, and how to write an entry to the Windows event log and to the Unified Logging Service (ULS) trace log for SharePoint applications.

Important

You can develop a Project Server event handler on the local computer that is running Project Server or on a remote computer. In either case, you should use only a test Project Server installation, not a production server, to develop, test, and debug event handlers.

This article includes the following sections:

  • Developing an Event Handler

  • Deploying the Event Handler

  • Registering the Event Handler Association

  • Testing the Event Handler: Using the Event Viewer and the ULS Log

  • Debugging the Event Handler

Note

Because Microsoft Project Professional 2010 and Project Web App sometimes call private PSI methods in the PWA service and the WinProj service, they do not always trigger the same events that the public PSI methods do. When you develop event handlers, test whether actions in Project Professional 2010, Project Web App, project detail pages (PDPs) for workflows, and in third-party applications that use the PSI, trigger event handlers as you expect.

For example, creating a project by using Project Professional 2010 does not trigger the OnCreating event handler. However, creating a project in Project Web App by selecting the Sample Proposal workflow or the Basic Project Plan on the New drop-down menu on the Project Center page does trigger the OnCreating event handler.

Although Project Server 2010 includes more events than Microsoft Office Project Server 2007, event handlers work the same way. For more information, see Project Server Events. For an example that includes event handlers for the user delegation OnActivated and OnDeactivated post-events, see Christophe Fiessinger's blog post, Project Server 2010 Delegation Audit Event Handler. For an example event handler for the OnSending pre-event that changes email notifications, see How to: Customize E-Mail for Project Server Notifications.

Developing an Event Handler

The OnCreating event handler in this article is for the ProjectCreating pre-event, which occurs just before the QueueCreateProject method creates a project. In a pre-event, you can add business rules and cancel the action if the rules are not satisfied. The TestProjectCreating example shows how to use a DataSet in the event arguments and how to use another PSI service in the event handler. The example checks whether the project for a specified department has more than three tasks. If so, the event is canceled. In either case, the event handler writes to both the application event log and to the ULS log.

For a list of all events in Project Server 2010, see the PSEventID enumeration. For a list of event handlers that are available for the Project object, see the methods in the ProjectEventReceiver class. Pre-events can be canceled in an event handler. Post-events, such as the ProjectCreated event, cannot be canceled; post-events are used to collect and exchange information that results from actions that have already occurred. Each event provides event arguments in the event handler parameters. Parameters for the OnCreating event handler include context information such as the user name, Project Web App site GUID, and preferred culture. The event arguments in the ProjectPreEventArgs parameter include the ProjectDataSet of the project to be created, the project GUID, and the project name.

Security noteSecurity Note

Project Server event handlers run on a Project Server computer with the SharePoint farm administrator credentials, or with the specified logon account for the Microsoft Project Server Events Service 2010 service. Because event handlers already run on the Project Server back end, not the Project Web App front end, you would not normally use impersonation in an event handler.

Procedure 1. To develop an event handler

  1. If you are developing on a computer that is running Project Server, you can directly set references to the following assemblies by using the Add Reference dialog box to browse to the assembly. If you are developing on a remote computer, copy the following assemblies from the Project Server computer to a convenient directory on the development computer:

    • Microsoft.Office.Project.Schema.dll:  The Microsoft.Office.Project.Server.Schema namespace includes the necessary class definitions, such as ProjectDataSet, for the event arguments. Copy from, or browse to, the [Windows]\assembly\GAC_MSIL\Microsoft.Office.Project.Schema\14.0.0.0__71e9bce111e9429c directory.

      If the event arguments do not include one of the PSI datasets, the Microsoft.Office.Project.Server.Schema namespace is not needed.

    • Microsoft.Office.Project.Server.Events.Receivers.dll:  Copy from, or browse to, the [Program Files]\Microsoft Office Servers\14.0\Bin directory on the Project Server computer.

    • Microsoft.Office.Project.Server.Library.dll:  Copy from, or browse to, the [Program Files]\Microsoft Office Servers\14.0\Bin directory.

    • Microsoft.SharePoint.dll:  The Microsoft.SharePoint namespace is needed if the event handler uses another PSI service or if it uses project sites. Copy from, or browse to, the [Program Files]\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI directory.

    To copy the assemblies, on the Project Server computer, run a Visual Studio Command Prompt (2010) window as an administrator. The CopyRefs.bat file (in the Project 2010 SDK download) copies the necessary assemblies from the Project Server computer to a development computer. Change the development computer name and the share name in the following script.

    echo off
    
    set SHARE=\\DEV_PC\ShareName
    
    set SCHEMA=C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Project.Schema\14.0.0.0__71e9bce111e9429c
    set PROJ_SERVER_LIBS="c:\Program Files\Microsoft Office Servers\14.0\Bin"
    set SHAREPOINT_LIBS="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI"
    
    xcopy /y %SCHEMA%\*.dll %SHARE%
    xcopy /y %PROJ_SERVER_LIBS%\Microsoft.Office.Project.Server.Events.Receivers.dll %SHARE%
    xcopy /y %PROJ_SERVER_LIBS%\Microsoft.Office.Project.Server.Library.dll %SHARE%
    xcopy /y %SHAREPOINT_LIBS%\Microsoft.SharePoint.dll %SHARE%
    
  2. In Microsoft Visual Studio 2010, create a project of type Class Library; for example, name the project TestCreatingProject. For a Visual C# project, you can rename the Class1.cs, for example, you might rename it to be TestCreatingProject.cs. If you change the namespace name in the source code, ensure that you also change the default namespace on the Application tab of the project Properties pane.

  3. Add the references that are specified in step 1, and then add the following reference assemblies:

    • System.Runtime.Serialization.dll

    • System.Security.dll

    • System.ServiceModel.dll

  4. Add the following lines to the global references in the TestCreatingProject.cs file:

    using System.Diagnostics;
    using System.ServiceModel;
    using System.Security.Principal;
    using Microsoft.SharePoint;
    using Microsoft.Office.Project.Server.Events;
    using PSLib = Microsoft.Office.Project.Server.Library;
    using PSSchema = Microsoft.Office.Project.Server.Schema;
    
  5. Delete the Class1 definition in the TestCreatingProject.cs file, and then create an event handler class that derives from the base abstract event receiver class that you need. For example, name the class CheckProjectDepartment. In the following example, the default namespace is changed, and the class derives from the ProjectEventReceiver base abstract class.

    namespace Microsoft.SDK.Project.Samples.TestCreatingProject
    {
        public class CheckProjectDepartment : ProjectEventReceiver
        {
        }
    }
    
  6. Create a method to override the base method for the specific event. Each event has a corresponding base method that you must override with your own implementation. For example, to write an event handler for the Creating event of the Project business object, you override the OnCreating method. In Visual Studio, IntelliSense lets you select the available base methods and completes the method framework as follows:

    public override void OnCreating(PSLib.PSContextInfo contextInfo, ProjectPreEventArgs e)
    {
        base.OnCreating(contextInfo, e);
    }
    

    You can comment-out or delete the base.OnCreating method, because the base method does no processing in an abstract class.

  7. The TestCreatingProject solution writes to the event log and to the ULS log. To create a logging service for the ULS trace log, add a class named LoggingService to the Visual Studio project, and then change the namespace to match that of the CheckProjectDepartment class. The following code is the complete class implementation in the LoggingService.cs file, which creates an information category with a Verbose level and a warning category with an Unexpected level. For more information about using the ULS trace log, see Writing to the Trace Log from Custom Code and Debugging and Logging Capabilities in SharePoint 2010.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.SharePoint.Administration;
    
    namespace Microsoft.SDK.Project.Samples.TestCreatingProject
    {
        // Define a custom logging service for the ULS log.
        public class LoggingService : SPDiagnosticsServiceBase
        {
            private const string LOG_SERVICE_NAME = "Project Test Logging Service";
            private const string PRODUCT_DIAGNOSTIC_NAME = "Project Server Event Handler";
            private const uint EVENT_ID = 5050;
    
            // ULS categories:
            public const string PROJECT_INFO = "Event Handler Information";
            public const string PROJECT_WARNING = "Event Handler Warning";
    
            private static LoggingService activeLoggingService;
    
            private LoggingService() : base(LOG_SERVICE_NAME, SPFarm.Local)
            {
            }
    
            // Register the product name and set the ULS log categories that are available.
            protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
            {
                List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>
                {
                    new SPDiagnosticsArea(
                        PRODUCT_DIAGNOSTIC_NAME,
                        new List<SPDiagnosticsCategory>
                        {
                            new SPDiagnosticsCategory(PROJECT_INFO,
                                                      TraceSeverity.Verbose, 
                                                      EventSeverity.Information),
                            new SPDiagnosticsCategory(PROJECT_WARNING,
                                                      TraceSeverity.Unexpected,
                                                      EventSeverity.Warning),
                        })
                };
                return areas;
            }
    
            // Create a LoggingService instance.
            public static LoggingService Active
            {
                get
                {
                    if (activeLoggingService == null)
                        activeLoggingService = new LoggingService();
                    return activeLoggingService;
                }
            }
    
            // Write an information message to the ULS log.
            public static void LogMessage(string categoryName, string message)
            {
                SPDiagnosticsCategory category =
                    LoggingService.Active.Areas[PRODUCT_DIAGNOSTIC_NAME].Categories[categoryName];
                LoggingService.Active.WriteTrace(EVENT_ID, category, TraceSeverity.Verbose, message);
            }
    
            // Write an error message to the ULS log.
            public static void LogError(string categoryName, string message)
            {
                SPDiagnosticsCategory category =
                    LoggingService.Active.Areas[PRODUCT_DIAGNOSTIC_NAME].Categories[categoryName];
                LoggingService.Active.WriteTrace(EVENT_ID, category, TraceSeverity.Unexpected, message);
            }
        }
    }
    
  8. Add the wcfLookupTable.cs proxy file for the LookupTable service to the Visual Studio project. The PSI proxy files are in the Documentation\Intellisense\WCF\Source subdirectory of the Project 2010 SDK download.

  9. Write the OnCreating event handler implementation, by using properties of the PSContextInfo and ProjectPreEventArgs event arguments. In the following code, the eventLog object requires the following statement to get a reference to the system EventLog: using System.Diagnostics; .

    Note

    Output of the ProjectDataSet object in the example is for debugging purposes only; there should be no file output on a production server.

    The OnCreating event handler does the following:

    1. Creates an EventLog instance and assigns its source.The event log entry uses an optional application-specific event identification number (the EVENT_ID constant) that enables you to filter for events.

    2. Gets information from the event arguments, such as the user name, Project Web App GUID, and ProjectDataSet that another application used to call QueueCreateProject. The ProjectDataSet in the event arguments is read-only; it can be assigned to a variable that is of type Microsoft.Office.Project.Server.Schema.ProjectDataSet.

    3. Gets the GUID of the value in the built-in Department lookup table. The GUID is the CODE_VALUE property of the Project Departments custom field. The GUID of the Project Departments custom field itself is the PROJECT_DEPARTMENT_MD_PROP_UID value.

    4. Finds the department name by using the GetDepartmentValue method (see step 10 and step 11).

    5. Sets the Cancel property to the default value false to let the event action continue.

    6. Implements the business logic and determines whether the Cancel property should be set to true. In the OnCreating event handler example, if there are more than three tasks in the project for a specified department, the event action is canceled and the project is not created.

    7. Writes the event log and ULS log entries by using the WriteLogEntries method (see step 12).

    public class CheckProjectDepartment : ProjectEventReceiver
    {
        // Change the department name to match a department in your installation.
        private const string PROJECT_DEPARTMENT2CHECK = "Test Dept 2";
    
        private const string EVENT_SOURCE = "Project Event Handler";
        private const int EVENT_ID = 5050;
        private static SvcLookupTable.LookupTableClient lookupTableClient;
        private EventLog eventLog;
    
        // Change the output directory for your computer.
        private const string OUTPUT_FILES = @"C:\Project\Samples\Output\";
        private static string outFilePath;
    
        public override void OnCreating(PSLib.PSContextInfo contextInfo, ProjectPreEventArgs e)
        {
            // The base method does no processing in an abstract class.
            //base.OnCreating(contextInfo, e);
    
            // Create an EventLog instance and assign its source.
            eventLog = new EventLog();
            eventLog.Source = EVENT_SOURCE;
            string logEntry = string.Empty;
    
            // Get information from the event arguments.
            string userName = contextInfo.UserName;
            Guid pwaUid = contextInfo.SiteGuid;
    
            string projectName = e.ProjectName;
            Guid projectUid = e.ProjectGuid;
            PSSchema.ProjectDataSet projDs = e.ProjectDataSet;
    
            // Write the ProjectDataSet to a file, for debugging purposes.
            outFilePath = OUTPUT_FILES + "ProjectDataSet4CreatingEventHandler.xml";
            projDs.WriteXml(outFilePath);
    
            e.Cancel = false;
    
            // Get the GUID of the default Project Departments custom field.
            Guid projDeptCFMdPropUid = PSLib.CustomField.PROJECT_DEPARTMENT_MD_PROP_UID;
            Guid projDeptUid = Guid.Empty;
    
            for (int i = 0; i < projDs.ProjectCustomFields.Rows.Count; i++)
            {
                if (projDs.ProjectCustomFields[i].MD_PROP_UID == projDeptCFMdPropUid)
                {
                    // Get the custom field value, which is the GUID of the value in 
                    // the Department lookup table.
                    projDeptUid = projDs.ProjectCustomFields[i].CODE_VALUE;
                    break;
                }
            }
    
            int numTasks = projDs.Task.Rows.Count;
            string departmentName = GetDepartmentValue(pwaUid, projDeptUid);
    
            if (projDeptUid != Guid.Empty)
            {
                // Sample 'business rules.'
                if (departmentName == PROJECT_DEPARTMENT2CHECK && numTasks > 3)
                {
                    e.Cancel = true;
                }
            }
            // For this example, write the log entries in all cases.
            WriteLogEntries(e.Cancel, numTasks, departmentName, userName, projectName);
        }
    
        // Add the GetDepartmentValue method and the WriteLogEntries method.
    }
    
  10. Write the GetDepartmentValue method. Because the GUID and the text value of the project department are contained in the LookupTableTrees table of the Department lookup table, and the GUID of the built-in Department lookup table is specified by DEPARTMENTS_LT_UID, you can use the ReadLookupTablesByUids method to get the lookup table data. Using a lookup table method requires initializing a LookupTableClient object, which then requires the SetClientEndpoint method (step 11).

    The GetDepartmentValue method iterates through the LookupTableTrees table to find the leaf node where the LT_STRUCT_UID (the GUID of the lookup table value) matches the department GUID that was obtained from the ProjectDataSet. If there is a match, the department name is the LT_VALUE_TEXT property.

    // Get the name of the department.
    private string GetDepartmentValue(Guid pwaUid, Guid departmentUid)
    {
        // Set the language code for the lookup table; U.S. English in this case.
        const int LCID = 1033;  
        string result = string.Empty;
    
        SetClientEndpoint(pwaUid);
    
        // Read the Departments lookup table data.
        Guid[] lutUids = { PSLib.LookupTables.DEPARTMENTS_LT_UID };
        SvcLookupTable.LookupTableDataSet lutDs = 
            lookupTableClient.ReadLookupTablesByUids(lutUids, false, LCID);
    
        // Find the text value in the lookup table tree item that matches the department GUID.
        for (int i = 0; i < lutDs.LookupTableTrees.Count; i++)
        {
            if (lutDs.LookupTableTrees[i].LT_STRUCT_UID == departmentUid)
            {
                result = lutDs.LookupTableTrees[i].LT_VALUE_TEXT;
                break;
            }
        }
        lookupTableClient.Close();
        return result;
    }
    
  11. Because you cannot use an app.config file for an event handler, write the SetClientEndpoint method to programmatically set the WCF endpoint for the LookupTableClient object. Code in the SetClientEndpoint method is very similar to code in the Configuring the Services Programmatically section of the Walkthrough: Developing PSI Applications Using WCF article. The main difference is that the Project Web App GUID is known from the event arguments. When you initialize an SPSite object with the site GUID, you can get the URL of the site.

    // Programmatically set the WCF endpoint for the LookupTable client.
    private void SetClientEndpoint(Guid pwaUid)
    {
        const int MAXSIZE = 500000000;
        const string svcRouter = "/_vti_bin/PSI/ProjectServer.svc";
    
        BasicHttpBinding binding = null;
    
        SPSite pwaSite = new SPSite(pwaUid);
        string pwaUrl = pwaSite.Url;            
    
        if (pwaSite.Protocol.ToLower() == "https:")
        {
            // Create a binding for HTTPS.
            binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
        }
        else
        {
            // Create a binding for HTTP.
            binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
        }
    
        binding.Name = "basicHttpConf";
        binding.SendTimeout = TimeSpan.MaxValue;
        binding.MaxReceivedMessageSize = MAXSIZE;
        binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE;
        binding.MessageEncoding = WSMessageEncoding.Text;
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
    
        // The endpoint address is the ProjectServer.svc router for all public PSI calls.
        EndpointAddress address = new EndpointAddress(pwaUrl + svcRouter);
    
        lookupTableClient = new SvcLookupTable.LookupTableClient(binding, address);
        lookupTableClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel
            = TokenImpersonationLevel.Impersonation;
        lookupTableClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;
    }
    
  12. Create the WriteLogEntries method. The following code creates an information type and a warning type for the event log entry, builds the logEntry string that contains information about the event, and then writes the event log entry. The event log entry can be multiple lines of text.

    The code then creates a ULS log message, where information must be in a single line of text. If the event is canceled, the LoggingService.LogError method writes the warning message to the ULS log; otherwise, the LoggingService.LogMessage writes the information message.

    // Write entries to the event log and to the ULS log.
    private void WriteLogEntries(bool canceled, int numTasks, string projectDept,
        string userName, string projectName)
    {
        EventLogEntryType entryType = EventLogEntryType.Information;
        string taskInfo = "\nThe number of tasks is valid.";
    
        // Create an event log entry.
        if (canceled)
        {
            entryType = EventLogEntryType.Warning;
            taskInfo = 
                "\nFor that department, the project cannot be created with more than 3 tasks.";
        }
    
        string logEntry = "User: " + userName;
        logEntry += "\nProject: " + projectName;
    
        projectDept = ((projectDept == string.Empty) ? "[none]" : projectDept);
        logEntry += "\nDepartment: " + projectDept;
        logEntry += "\nTasks: " + numTasks.ToString() + taskInfo;
    
        // Create an event log entry.
        eventLog. WriteEntry(logEntry, entryType, EVENT_ID);
    
        // Create a ULS log entry.
        logEntry = string.Format(
            "User: {0}; project: {1}; department: {2}; tasks: {3}. ",
            userName, projectName, projectDept, numTasks);
        logEntry += taskInfo;
    
        if (canceled)
        {
            LoggingService.LogError(LoggingService.PROJECT_WARNING, logEntry);
        }
        else
        {
            LoggingService.LogMessage(LoggingService.PROJECT_INFO, logEntry);
        }
    }
    
  13. Finally, create a strong name key file for the TestCreatingProject.dll assembly. For example, open the TestCreatingProject properties window in Visual Studio, click the Signing tab, and then select the Sign the assembly checkbox. Create a strong name key file, such as CreatingProjectKey.snk.

After the TestCreatingProject solution compiles with no errors, you can deploy the solution to the Project Web App computer.

Deploying the Event Handler

There are two ways to deploy the event handler assembly. Procedure 2a shows how to register the assembly in the global assembly cache of the Project Web App computer. You must have administrator logon access to the Project Server computer. When you register the assembly in the global assembly cache, you can get the public key token that is necessary for testing the event handler and for later registering the event handler on a production server.

Procedure 2b shows how to register the assembly in the [Program Files]\Microsoft Office Servers\14.0\Bin\ProjectServerEventHandlers directory. It is not necessary for you to directly access the global assembly cache on a production Project Server computer.

Procedure 2a. To register an event handler in the global assembly cache

  1. Run a Visual Studio Command Prompt window as an administrator, change to the solution directory, and then type the following command:

    gacutil /i bin\debug\TestCreatingProject.dll
    
  2. Verify that your assembly is registered. Open the [Windows]\assembly directory in Windows Explorer (or type assembly in the Run dialog box from the Start menu), and scroll down to see that the TestCreatingProject assembly is registered, as in Figure 1.

    Figure 1. The TestCreatingProject assembly is registered in the global assembly cache

    TestCreatingProject in the global assembly cache

  3. Copy the full name of the registered assembly to a text file for use when you register the event handler in Project Server. The full name contains the text name, culture, version, and public key token:

    1. Right-click the TestCreatingProject assembly in the global assembly cache, and then click Properties.

    2. In the TestCreatingProject Properties dialog box, select the name, copy it, and then paste the name to a text file such as in Notepad.

    3. Copy the culture, version, and public key token, and paste each to the text file. Following is the full name of the assembly:

      TestCreatingProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=92978aaaab03ff98
      
  4. Copy the fully-qualified class name to the text file for use in registering the event handler with Project Web App. The fully-qualified class name of the TestCreatingProject example is Microsoft.SDK.Project.Samples.TestCreatingProject.CheckProjectDepartment.

Procedure 2b. To register an event handler by using XCopy deployment

  1. The system administrator must create a share that gives you full access to the [Program Files]\Microsoft Office Servers\14.0\bin\ProjectServerEventHandlers directory on the Project Server computer. For example, create a share named EventHandlers for the directory.

    Project Server automatically registers event handler assemblies that are in the ProjectServerEventHandlers subdirectory.

  2. In a Visual Studio Command Prompt window on the development computer, change to the directory that contains the event handler assembly, and then type the following command (use double-quotes around the destination directory if the path contains spaces):

    xcopy /y TestCreatingProject.dll \\ServerName\EventHandlers
    

    The event handler assembly is now copied to the Project Server computer in the [Program Files]\Microsoft Office Servers\14.0\bin\ProjectServerEventHandlers directory.

During testing, you will probably need to unregister and reregister the assembly several times. The TestCreatingProject solution contains the following script in the InstallEventHandler.bat file, in the Utilities subdirectory. Run the batch file as an administrator.

echo off

@SET EVENT_HANDLERS="C:\Program Files\Microsoft Office Servers\14.0\Bin\ProjectServerEventHandlers"

REM To deploy to a production server, copy the event handler to the ProjectServerEventHandlers subdirectory.
REM xcopy /y ..\bin\debug\TestCreatingProject.dll %EVENT_HANDLERS%
REM xcopy /y ..\bin\debug\TestCreatingProject.pdb %EVENT_HANDLERS%

REM To debug the event handler, register it in the global assembly cache.
gacutil /u TestCreatingProject
gacutil /i ..\bin\debug\TestCreatingProject.dll

To use the InstallEventHandler.bat file for Procedure 2b, comment-out the gacutil.exe statements, and uncomment the xcopy statements.

Registering the Event Handler Association

After the event handler assembly is deployed on the Project Server computer, you must associate it with an event in Project Server before the event handler can be used. Procedure 3 shows how to register the TestCreatingProject event handler by using Project Web App. You could also create an application to register event handler associations, by using the Events service in the PSI.

Tip

For a sample application that can add and remove event handler associations, see Project Server 2007 Event Handler Admin Tool on CodePlex. Although the tool was designed for Office Project Server 2007, it also works with Project Server 2010.

Procedure 3. To register an event handler association in Project Web App

  1. In Project Web App, in the Quick Launch, click Server Settings.

  2. On the Server Settings page, in the Operational Policies section, click Server Side Event Handlers.

  3. On the Server Side Event Handlers page, scroll down the Events list and click the Project link for the Creating event, and then click New Event Handler (Figure 2).

    Figure 2. Adding a Project Creating event handler

    Registering the Project Creating event handler

  4. On the New Event Handler page (Figure 3), type the following values for the fields:

    1. Name:  The friendly name for the event handler.

    2. Description:  An optional description of the event handler.

    3. Assembly Name:  The full name of the assembly from the text file that you prepared in Procedure 2a, for example:

      TestCreatingProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=92978aaaab03ff98
      
    4. Class Name:  The fully qualified name of the class in the assembly that implements the event handler, for example, Microsoft.SDK.Project.Samples.TestCreatingProject.CheckProjectDepartment.

    5. Order:  If there are multiple event handlers associated with one event, the order number determines the sequence in which the event handlers are invoked. The value can be from 1 to 999.

    Figure 3. Configuring an event handler

    Configuring a new event handler in Project Web App

  5. Click Save.

    Note

    Event registration is asynchronous and uses the Project Server service application and the Microsoft SharePoint Foundation timer processes. If the timer periodicity is one minute, it can take up to two minutes to register the event handler with Project Server.

  6. Check that the event is added. In the Events list, click the Project link for the Creating event again. If registration is completed, you should see the event handler in the Event Handlers list.

The Utilities subdirectory of the TestCreatingProject solution contains the following information in the EventHandlerRegistration.txt file for use in Procedure 3:

Add the following to the Edit Event Handler page in Project Web App,
for the Project Creating event.

Name: 
Project Creating Event Handler

Description: 
Test the OnCreating event handler for department requirements

Assembly Name (check the correct attributes after registering the event handler in the GAC): 
TestCreatingProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=92978aaaab03ff98

Class Name:
Microsoft.SDK.Project.Samples.TestCreatingProject.CheckProjectDepartment

Order:
1

Note

Each time that you change and recompile the event handler code, you must reregister the event handler (by using Procedure 2a or Procedure 2b), delete the event handler in the Project Web App list, and then add the updated event handler by using Procedure 3 again.

Testing the Event Handler: Using the Event Viewer and the ULS Log

The TestCreatingProject event handler writes a message to the event log and to the ULS trace log every time an application calls the QueueCreateProject method. For test example purposes, if the project department is named Test Dept 2 and contains more than three tasks, the event is canceled and the project is not created.

Note

To create a project, Project Professional 2010 calls methods in the private WinProj service of the PSI. Because it does not call QueueCreateProject, Project Professional 2010 does not trigger the Project Creating event. To test the event handler, you can use a custom application such as ProjTool.exe or the CreateProject4Department sample in QueueCreateProject. For information about ProjTool, see Using the ProjTool Test Application in Project Server 2010.

Procedure 4. To test the event handler

  1. Enable SharePoint applications to record ULS trace log entries, as follows:

    1. Open the SharePoint 2010 Central Administration application, click Monitoring, and then click Review job definitions in the Timer Jobs section of the Monitoring page.

    2. Click Diagnostic Data Provider: Trace Log, and then on the Edit Timer Job page, set the timer job to run every 10 minutes. Click OK. Figure 4 shows that the trace log schedule type is Minutes.

      Figure 4. Setting the ULS trace log

      Setting the ULS trace log

  2. To easily read and monitor the ULS log, and to filter for events and save ULS log examples, you can download and install the ULS Viewer from MSDN Code Gallery. Otherwise, you can view the current ULS log in the default [Program Files]\Common Files\Microsoft Shared\Web Server Extensions\14\LOGS\ directory by using Notepad, or copy the current log to another directory and view it with Microsoft Excel.

    Note

    The ULS Viewer is not a supported application.

    1. In the ULS Viewer application window, to monitor the ULS log in real time, press Ctrl+U.

    2. To see only events from the TestCreatingProject event handler and the Project Server Queue service, add a filter where the EventID equals 5050 or the Category equals Queue. You can save the filter, and then load the filter in future sessions.

  3. Because ProjTool does not directly add a department value when it creates a project, you can use the CreateProject4Department sample in the Project 2010 SDK download. Otherwise, you can modify ProjTool or use your own application to create projects that have a specified department. The remainder of this procedure uses CreateProject4Department.exe.

    1. On the Enterprise Custom Fields and Lookup Tables page in Project Web App, edit the Department lookup table to include a department named Test Dept 2 (or whatever text value you have for the PROJECT_DEPARTMENT2CHECK constant in the TestCreatingProject code).

    2. In Microsoft SQL Server Management Studio, use the following query to find the LT_STRUCT_UID value of the lookup table entry where the LT_VALUE_TEXT field is Test Dept 2.

      SELECT TOP 1000 [LT_UID]
            ,[LT_STRUCT_UID]
            ,[LT_VALUE_TEXT]
        FROM [ProjectServer_Published].[dbo].[MSP_LOOKUP_TABLE_VALUES]
      
    3. Copy the LT_STRUCT_UID value into the DEPARTMENT_TEST constant value of the CreateProject4Department code, and then compile the CreateProject4Department application.

  4. If you are using the ULS Viewer, ensure that it is running and collecting current trace entries. In a Command Prompt window, run CreateProject4Department.exe at least twice: once to create a project with three or fewer tasks, and once to create a project with more than three tasks. The name parameter values in the following commands correspond to the project name values in Figure 5 and Figure 6.

    CreateProject4Department -name "Test Proj4Dept 10" -tasks 3 
    CreateProject4Department -name "Test Proj4Dept 11" -tasks 4
    
  5. Open the Event Viewer window, expand the Windows Logs node, and then click the Application log. The Project Event Handler source (Figure 5) shows the Information level event for the project with three tasks. The project is created.

    Figure 5. Using the Event Viewer

    Using the Event Viewer

    The Warning level event above the selected event shows the Project Event Handler entry for the case with four tasks. The Error level event shows that the Project Server Queue was blocked when the QueueCreateProject method could not create the project.

  6. In the ULS Viewer window (Figure 6), set a filter as described in step 2, and then click the Event Handler Warning item. The message shows that the project with four tasks was not created, and a short time later there was a critical queue event.

    Figure 6. Using the ULS Viewer with a filter

    Using the ULS Viewer

In addition to the event log and the ULS log, you can find information about Project Server Queue jobs in Project Web App. For example, click Personal Settings in the Quick Launch, and then click My Queued Jobs. Figure 7 shows the error details for the queue job that failed when the OnCreating event handler canceled the event. The critical queue event message in the ULS Viewer includes the JobUID value in the Queue Job Error Details dialog box.

Figure 7. Project Web App showing error details in a queue job

Error details in a queue job

Note

When actions are canceled, there are some differences in behavior for the 88 pre-event handlers. You should test the behavior when an action is canceled, and provide instructions to users for the specific situation. In general, Project Professional 2010 shows an error message in the Status Bar that something is wrong in the queue, or that the project is not published, but does not display the CancelReason property to the end user. In Project Web App, with the June 2011 cumulative update for Project Server 2010, project detail pages (PDPs) do display the cancel reason. Some canceled actions also show in the Queue Job Error Details dialog box in Project Web App.

Some canceled actions also show in the Queue Job Error Details dialog box in Project Web App. With ProjectEventReceiver methods for example, if code in the OnCreating or OnUpdating event handlers sets the CancelReason property, that text would also be displayed in the queue information (Figure 7). However, the OnDeleting and OnSummaryPublished event handlers do not show the CancelReason value in the queue information.

Debugging the Event Handler

Registered event handlers run on the Project Server computer and are called by the Project Server Eventing service. To debug an event handler, you can use Visual Studio 2010 and attach the debugger to the Project Server Eventing processes.

Tip

In Project Server 2010, there are two Eventing processes, which are the parent process and the child process. The parent process spawns the child process, and restarts the child if it is stopped. The parent process also watches for any new Project Web App site or project site that is provisioned, or sites that are deleted, and sends a refresh message to the child process.

The child Eventing process creates event receivers for each site that is provisioned, and starts the normal polling and dispatching of jobs. Both the Eventing service and the Queueing service use the parent/child model, so there are two processes for each service. You can experiment to find which is the child process, but it is easier to simply attach the debugger to both Eventing processes.

Procedure 5. To debug the event handler

  1. Register and test the event handler as described in the previous procedures.

  2. If you are debugging from a remote computer, see Remote Debugging Setup.

  3. In Visual Studio, open the event handler project that you registered, and on the Debug menu, click Attach to Process.

  4. In the Attach to Process dialog box (Figure 8), select the Default transport and browse to the Project Server computer name for the Qualifier field. Click Select, and then in the Select Code Type dalog box, select Managed for the Debug these code types option.

    Figure 8. Attaching the debugger to the Eventing processes

    Attaching the debugger to the Eventing processes

  5. Select the Show processes from all users check box and the Show processes in all sessions check box.

  6. To put Visual Studio into debug mode, in the Available Processes list, select both of the Microsoft.Office.Project.Server.Eventing.exe processes, and then click Attach.

  7. On the Tools menu, click Options. Expand the Debugging node in the options list and click Symbols. Click the folder icon and paste the TestCreatingProject project debug build directory path in the new symbol file location. Click Load all symbols to load the TestCreatingProject.pdb symbol file, and then click OK.

  8. In the code window of the TestCreatingProject.cs file, put a breakpoint at the PSSchema.ProjectDataSet projDs = e.ProjectDataSet; statement, or anywhere else that you want to start debugging.

    If the breakpoint shows only a red outline with a yellow warning symbol, either the TestCreatingProject.pdb symbol file is not loaded, or Project Server has not completed registration of the event handler. Ensure that the event handler registration settings in Project Web App are correct, and then try step 9 to start the eventing process working.

  9. Trigger the Creating event by running the CreateProject4Department application, and then step through the OnCreating event handler in Visual Studio.

  10. Debugging does not work if you change the code and recompile after registering the event handler with Project Server. If you recompile, you must reregister the recompiled event handler, as follows:

    1. Delete the event handler in Project Web App.

    2. Remove the old event handler assembly from the global assembly cache.

    3. Register the recompiled event handler in the global assembly cache.

    4. Reregister the event handler in Project Web App.

Robust Programming

Note

The code examples in this article do not include try-catch blocks for the WCF System.ServiceModel.FaultException and other exceptions. Production code should include exception handling, where exceptions are written to the ULS trace log.

For an example of trapping the FaultException, see the WCF example in QueueCreateProject. The CreateProject4Department solution in the Project 2010 SDK download also implements exception handling.

The ULS error message level in the example is set to TraceSeverity.Unexpected in the LoggingService.LogError method. That level is usually used for unexpected exceptions that occur in try-catch blocks. You should create several more methods in the LoggingService class that have a wider range of TraceSeverity levels. For example, with a FaultException, log a High level message, with an unexpected general Exception log an Unexpected message, and with a pre-event handler that cancels the event but does no real harm, such as in the TestCreatingProject example, log a Medium level message.

See Also

Tasks

Walkthrough: Developing PSI Applications Using WCF

Reference

PSEventID

QueueCreateProject

Concepts

Using the ProjTool Test Application in Project Server 2010

Other Resources

Project Server Events

Project Server 2010 Delegation Audit Event Handler

Project Server 2007 Event Handler Admin Tool

ULS Viewer

Writing to the Trace Log from Custom Code

Debugging and Logging Capabilities in SharePoint 2010

Remote Debugging Setup