Walkthrough: Creating a PSI Extension

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

The Project Server Interface (PSI) for Microsoft Office Project Server 2007 can be extended with custom Web services. PSI extensions tightly integrate with Project Server, can call other PSI methods, and can use the same security infrastructure that the rest of the PSI uses.

Following are some scenarios that can benefit from PSI extensions:

  • Pulling data from the Reporting database (RDB). A PSI extension can directly query the RDB from the server. For example, third-party applications that are deployed outside a firewall cannot do a direct query of the RDB on Project Server, but can use a Web service that is a PSI extension to query the RDB.

  • Consolidating information from several PSI calls or manipulating and formatting data for a third-party application. A PSI extension can save bandwidth and client-side processing by doing work on the server and returning exactly what the application needs.

  • Performing impersonation for specific jobs more easily by running in the correct security context. For example, the existing Calendar.UpdateCalendars PSI method requires the application user to have ManageEnterpriseCalendars permission. A PSI extension could impersonate a fictitious user who has the necessary permissions. Team members could use an application to update their own calendar exceptions, without requiring a project manager to use Microsoft Office Project Professional 2007.

  • Integrate better with third-party and line-of-business (LOB) applications. You can extend the functionality of Project Server with PSI extensions for client applications and middleware components for LOB applications such as human resources, finance, or CRM.

Important noteImportant

Include the appropriate security checks in PSI extensions and in applications that use them. However, do not overdo the use of PSI extensions; use them efficiently to reduce the impact on server performance.

The following procedures show how to create a simple Web service for a PSI extension, make it discoverable, use the Web service in a client application, and extend and deploy a modified Web service.

  1. Creating a Simple "Hello World" Web Service

  2. Generating and Modifying Discovery and WSDL Files

  3. Creating a Client Application that Uses the Web Service

  4. Extending the Simple Web Service

  5. Deploying and Testing the Extended Web Service

  6. Best Practices for Project Server Extensions

The procedures in this article use Microsoft Visual C# and Microsoft Visual Studio 2005. When you install the SDK download, the Code Samples\PSI Extensions directory includes the complete sample files:

  • The simple HelloWorldPSI Web service and client application in the PSIExtension1 subdirectory.

  • The extended HelloWorldPSI Web service sample in the PSIExtension2 subdirectory.

For a link to the SDK download, see Welcome to the Microsoft Office Project 2007 SDK.

Creating a Simple "Hello World" Web Service

Procedure 1 shows how to create a simple Web service that has only one exposed Web method named Hello. The method simply returns the string "Hello World".

Procedure 1. To create a HelloWorld Web service:

  1. In Visual Studio, on the File menu, click New, and then click Web Site.

  2. In the New Web Site dialog box, click the ASP.NET Web Service template, and then select a file system location for the site. For example, create the project in C:\Project\PSIExtension1.

  3. Add a new class to the solution. The class will contain the Web service logic. For example, in Solution Explorer, right-click the HelloWorldPSI solution, click Add, and then click New Project.

  4. In the Add New Project dialog box, click the Class Library template, name the class, and select a location. For example, create a class named HelloWorld in C:\Project\PSIExtension1.

    Note

    The sample class is named HelloWorld to avoid a subdirectory naming conflict with the HelloWorldPSI Web service. The HelloWorldPSI.asmx Web service is in the Code Samples\PSI Extensions\PSIExtension1\HelloWorldPSI directory of the SDK download.

  5. Right-click the HelloWorld project, and then click Properties. Click the Application tab in the HelloWorld properties pane. Name both the assembly and the default namespace HelloWorldPSI.

  6. Add the following references to the HelloWorld project (right-click the References folder in Solution Explorer, and then click Add Reference):

    • Click the**.NET** tab of the Add Reference dialog box, and then add System.Web.Services.

    • Click the Browse tab, navigate to the C:\Program Files\Microsoft Office Servers\12.0\Bin directory, and then add the Microsoft.Office.Project.Server.Library.dll assembly.

  7. In Solution Explorer, drag Service.cs from the App_Code folder to the top node in the HelloWorld project to make a copy, and then delete the original Service.cs file in the App_Code folder.

  8. Delete the Class1.cs file in the HelloWorld project. Figure 1 shows the resulting Solution Explorer view with all items expanded.

    Figure 1. Web service and HelloWorld class projects in Solution Explorer

    The Web service and HelloWorld class projects

  9. Open the Service.cs file to the code view and replace the [WebService(Namespace = "http://tempuri.org/")] line with the following.

    [WebService(Namespace = 
    "http://schemas.microsoft.com/office/project/server/webservices/Service/", 
    Name = "HelloWorldPSI", 
    Description = "Contains the Service web service for Project Server.")]
    
    NoteNote

    The namespace name must match the namespace you created in Step 5.

  10. Create a strong name for the HelloWorld class library.

    1. In Solution Explorer, right-click the HelloWorld project, and then click Properties.

    2. Click the Signing tab, select Signtheassembly, and then click New in the drop-down list.

    3. In the Create Strong Name Key dialog box, type a name for the key file. For example, type Key.snk.

    4. Clear Protect my key file with a password, and then click OK.

  11. Build only the HelloWorld class library. Right-click the HelloWorld project in Solution Explorer, and then click Build.

  12. Register the HelloWorld.dll assembly in the global assembly cache. You can use either of the following methods.

    • Open [Windows]\assembly in Windows Explorer, and then drag the HelloWorldPSI.dll file from the HelloWorld\bin\Debug project subdirectory to the assembly window.

    • Open a Visual Studio 2005 Command Prompt window and use the gacutil.exe utility. For example, type the following command.

      gacutil /if " C:\Project\PSIExtension1\HelloWorld\bin\Debug\HelloWorldPSI.dll"
      
  13. In Solution Explorer, give Service.asmx in the HelloWorldPSI project a more descriptive name. For example, rename it HelloWorldPSI.asmx.

  14. Open the assembly window (or if the window is already open, click Refresh on the View menu), right-click the HelloWorldPSI assembly, and then click Properties. Select and copy the public key token (Figure 2).

    Figure 2. Properties of the HelloWorldPSI assembly in the global assembly cache

    Properties of the HelloWorld assembly in the GAC

  15. Double-click HelloWorldPSI.asmx to open it, and then modify the header to use the properties of the HelloWorldPSI assembly. You must also remove the CodeBehind attribute because the Web service uses the HelloWorldPSI assembly registered in the global assembly cache, not a code file in the App_Code subdirectory. For example, change the header as follows.

    <%@ WebService Language="C#" 
        Class="Service, HelloWorldPSI, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=8e40050454db4aeb" %>
    

Generating and Modifying Discovery and WSDL Files

To provide discovery and description for your custom Web service, you must create a .disco file and a .wsdl file. Because Windows SharePoint Services virtualizes URLs, you cannot directly use the .disco and .wsdl files generated by ASP.NET. Instead, you must create the .disco and .wsdl files that provide the necessary redirection and maintain virtualization.

You can use ASP.NET to generate the .disco and .wsdl files by hosting your Web service in a virtual directory, such as /SharedServices1/PSI, and then use the Microsoft .NET Framework Web Service Discovery tool (disco.exe) to obtain the generated files.

The steps in Procedure 2 assume that you have installed Project Server in the default directory and show how to generate the .disco and .wsdl files.

Procedure 2: To create the .DISCO and .WSDL files for the Web service:

  1. Copy the HelloWorldPSI.asmx file to [Program Files]\Microsoft Office Servers\12.0\WebServices\Shared\PSI.

  2. Open the web.config file in the same directory and add the HelloWorldPSI assembly as a child of the <assemblies> tag. The result should have two assemblies, where each assembly is all on one line. Replace the public key token for the HelloWorldPSI assembly with the value of the registered assembly on your Project Server computer (Procedure 1, step 13).

    <assemblies>
        <add assembly="Microsoft.Office.Project.Server.WebService, 
             Version=12.0.0.0, Culture=neutral, 
             PublicKeyToken=71e9bce111e9429c" />
       <add assembly="HelloWorldPSI, Version=1.0.0.0, Culture=neutral, 
            PublicKeyToken=8e40050454db4aeb" />
     </assemblies>
    
  3. Restart Internet Information Services (IIS) (type iisreset in any Command Prompt window).

  4. Run the disco.exe command to save the .discomap, .disco, and .wsdl, files to the shared PSI directory. In IIS Manager, check the name of the Shared Services Provider (SSP) in the Office Server Web Services site. If the name is SharedServices1, for example, run the following commands in a Visual Studio 2005 Command Prompt window.

    cd [Program Files]\Common Files\Microsoft Shared\web server extensions\12\ISAPI\PSI
    disco http://localhost:56737/SharedServices1/PSI/HelloWorldPSI.asmx > results.htm 2>&1
    

    When you run disco.exe in the PSI directory, you do not need to specify the output directory with the /o or /out parameter. If there are errors in the path, file name, namespace, or other properties of the assembly specified in web.config, it is easier to send the standard and error output to a results.htm file and then view it in a browser than to read the HTML code returned in the Command Prompt window. Successful execution of the disco.exe command saves the following output in results.htm.

    Microsoft (R) Web Services Discovery Utility
    [Microsoft (R) .NET Framework, Version 2.0.50727.42]
    Copyright (C) Microsoft Corporation. All rights reserved.
    Disco found documents at the following URLs:
    http://localhost:56737/SharedServices1/PSI/HelloWorldPSI.asmx?disco
    http://localhost:56737/SharedServices1/PSI/HelloWorldPSI.asmx?wsdl
    
    The following files hold the content found at the corresponding URLs:
      .\HelloWorldPSI.disco <- http://localhost:56737/SharedServices1/PSI/HelloWorldPSI.asmx?disco
      .\HelloWorldPSI.wsdl <- http://localhost:56737/SharedServices1/PSI/HelloWorldPSI.asmx?wsdl
    The file .\results.discomap holds links to each of these files.
    
  5. Rename the .disco and .wsdl files in the PSI directory. For example, run the following commands.

    ren HelloWorldPSI.disco HelloWorldPSIdisco.aspx
    ren HelloWorldPSI.wsdl HelloWorldPSIwsdl.aspx
    
  6. To register namespaces of the Windows SharePoint Services object model, open both the HelloWorldPSIdisco.aspx and HelloWorldPSIwsdl.aspx files, and replace the opening XML processing instruction <?xml version="1.0" encoding="utf-8"?> with the following code.

    <%@ Page Language="C#" Inherits="System.Web.UI.Page" %> 
    <%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
    <%@ Import Namespace="Microsoft.SharePoint.Utilities" %> 
    <%@ Import Namespace="Microsoft.SharePoint" %>
    <% Response.ContentType = "text/xml"; %>
    

    The previous code matches the ASP.NET header in other PSI Web service disco.aspx and wsdl.aspx files in the PSI directory.

  7. In the HelloWorldPSIdisco.aspx file, modify the contract reference and SOAP address tags as in the following example. There are two main changes:

    • Replace literal paths with code-generated paths through use of the Microsoft.SharePoint.Utilities.SPEncode class.

    • Replace the method name that is specified in the binding attribute.

    NoteNote

    The code in bold font, such as Service and ServiceSoap, is specific to the Web service name. For example, compare the modified HelloWorldPSIdisco.aspx with Calendardisco.aspx.

    <discovery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
       xmlns="http://schemas.xmlsoap.org/disco/">
       <contractRef 
          ref=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request) + "?wsdl"),Response.Output); %> 
          docRef=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> 
          xmlns="http://schemas.xmlsoap.org/disco/scl/" 
       />
       <soap 
          address=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> 
          xmlns:q1="http://schemas.microsoft.com/projectserver/soap/Service/" 
          binding="q1:ServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" 
       />
       <soap 
          address=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> 
          xmlns:q2="http://schemas.microsoft.com/projectserver/soap/Service/" 
          binding="q2:ServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" 
       />
    </discovery>
    
  8. In the HelloWorldPSIwsdl.aspx file, make similar substitutions for the SOAP address.

    1. Replace the entire soap:address element with the following (all on one line):

      <soap:address location=<% 
      SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),
      Response.Output); %> />
      
    2. Replace the entire soap12:address element with the following (all on one line):

      <soap12:address location=<% 
      SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),
      Response.Output); %> />
      

After you complete the steps in Procedure 2, the HelloWorldPSI Web service is available. Because the HelloWorldPSI.asmx file is in the [Program Files]\Common Files\Microsoft Shared\web server extensions\12\ISAPI\PSI directory with the other PSI Web services, it is available to all of the SSP and Project Web Access instances on that computer.

Figure 3 shows the browser view of the Web service with the http://localhost/PWA/_vti_bin/psi/helloworldpsi.asmx URL for the local Project Server computer and the default Project Web Access instance named PWA. If you have a second SSP on the same computer named SSP2 that hosts a Project Web Access instance on port 610 named PWADemo, for example, the URL for remote access of the Web service would be http://ServerName:610/PWADemo/_vti_bin/psi/helloworldpsi.asmx.

Figure 3. Viewing the HelloWorldPSI Web service in a browser

Viewing the HelloWorldPSI Web service

Creating a Client Application that Uses the Web Service

You can test the HelloWorldPSI Web service with a simple client application.

Procedure 3: To create a client application that consumes the Web service:

  1. In Visual Studio, create a new Windows Application project. For example, name the project HelloWorldPSIClient and set the location to C:\Project\PSIExtension1.

  2. Add a Web reference to the HelloWorldPSI Web service. For example, if you are developing on a test Project Server computer, paste http://localhost/pwa/_vti_bin/PSI/HelloWorldPSI.asmx in the URL drop-down list of the Add Web Reference dialog box. Name the Web reference WSHelloWorldPSI.

  3. Rename Form1.cs to HelloWorld.cs, and then double-click HelloWorld.cs in Solution Explorer to open the form in the design mode. Set the form Text property to Hello World Tester.

  4. Add a button named cmdHelloWorld to the form, and then set the button Text property to Connect to the HelloWorldPSI Web service.

  5. Open the HelloWorld.cs code file and replace all of the code with the following.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Net;
    
    namespace HelloWorldPSIClient
    {
        public partial class HelloWorld : Form
        {
            public HelloWorld()
            {
                InitializeComponent();
            }
    
            private void cmdHelloWorld_Click(object sender, EventArgs e) 
            { 
               WSHelloWorldPSI.HelloWorldPSI helloWorldWS = 
                 new HelloWorldPSIClient.WSHelloWorldPSI.HelloWorldPSI(); 
    
               helloWorldWS.Url = "http://localhost/pwa/_vti_bin/psi/HelloWorldPSI.asmx"; 
               helloWorldWS.Credentials = CredentialCache.DefaultCredentials; 
    
               string hello = helloWorldWS.HelloWorld(); 
               MessageBox.Show(hello); 
            }
        }
    }
    
    NoteNote

    If you develop or install the client application on a remote computer, change localhost in the URL to the name of your Project Server computer.

  6. Compile and then run the application. When you click the button (Figure 4), the application connects with Project Server, gets the "Hello World" string from the HelloWorld method, and then displays the string.

    Figure 4. Running the Hello World Tester application

    Running the Hello World Tester application

When you complete Procedure 3, you have successfully created a Web service that extends the PSI and a client application that calls into the Web service. The PSIExtension1 sample in the SDK download includes the source code for the Web service, the client application, and the modified HelloWorldPSI.disco and HelloWorldPSI.wsdl files.

Extending the Simple Web Service

The Web service in the PSIExtension1 sample simply returns "Hello World." The PSIExtension2 sample in the download extends the HelloWorldPSI Web service to get the user's security context from the HttpContext object and the user's e-mail address from the Resource Web service.

Procedure 4: To extend the "Hello World" Web service:

  1. Create another directory on the Project Server computer such as C:\Project\PSIExtension2, and then copy HelloWorldPSI.sln and the HelloWorldPSI and HelloWorld subdirectories from C:\Project\PSIExtension1.

  2. Open C:\Project\PSIExtension2\HelloWorldPSI.sln, and then add a Web reference to the Resource Web service (http://localhost/_vti_bin/PSI/Resource.asmx).

  3. Add a reference to System.Web. The HttpContext class is in the System.Web namespace.

  4. Open the code window of Service.cs. Add the following directive to the top of the file. The CredentialCache class is in the System.Net namespace.

    using System.Net;
    
  5. Delete the line return "Hello World"; in the HelloWorld method, and then add the following code to get the user's context information.

    // Get the user context of the calling user.
    HttpContext context = HttpContext.Current;
    string pjAuthHeader = context.Request.Headers["PjAuth"];
    PSContextInfo contextInfo = PSContextInfo.DeserializeFromString(pjAuthHeader);
    
    String message = "Hello World\r\n\r\n";
    message += "The following user called the custom Project Server Web service\r\n";
    message += String.Format(System.Globalization.CultureInfo.InvariantCulture,
                     "UserName = {0}, SiteGuid = {1}, Lcid = {2}\r\n",
                     contextInfo.UserName, contextInfo.SiteGuid, contextInfo.Lcid);
    
  6. Add the following code to get the user's e-mail address, and then return the results for the HelloWorld method. The context information includes the user GUID.

    // Call the Resource Web service for the user's e-mail address.
    HelloWorldPSI.WSResource.Resource resWS = 
        new HelloWorldPSI.WSResource.Resource(); 
    
    resWS.Url = "http://localhost/pwa/_vti_bin/psi/Resource.asmx"; 
    resWS.Credentials = CredentialCache.DefaultCredentials; 
    
    HelloWorldPSI.WSResource.ResourceDataSet resDS = 
        resWS.ReadResource(contextInfo.UserGuid); 
    
    message += "E-Mail Address: " + 
    resDS.Tables[resDS.Resources.TableName].Rows[0]
        [resDS.Resources.WRES_EMAILColumn.ColumnName].ToString(); 
    
    return message;
    
  7. Compile only the HelloWorld project to create an updated HelloWorldPSI.dll assembly.

Deploying and Testing the Extended Web Service

The updated HelloWorldPSI.dll assembly still has the same public key token and other properties, because you copied the Key.snk to PSIExtension2 and did not change the namespace or class name. Therefore, you do not need to change the contents of the Web.config, HelloWorldPSIdisco.aspx, or HelloWorldPSIwsdl.aspx file.

Procedure 5: To deploy and test the extended Web service:

  1. Register the updated assembly. Open a Visual Studio 2005 Command Prompt window and type the following commands.

    gacutil /if " C:\Project\PSIExtension2\HelloWorld\bin\Debug\HelloWorldPSI.dll"
    iisreset
    
  2. Run the original Hello World Tester application (Figure 5). Because the client application calls the same HelloWorldPSI Web service, it does not need to be changed.

    NoteNote

    Rerunning the client application can take several seconds for the first execution of the updated HelloWorldPSI assembly.

    Figure 5. Using the updated HelloWorldPSI Web service

    Using the updated HelloWorldPSI Web service

Figure 5 shows that the client application has obtained the HttpContext of the calling user and has also successfully called the existing ReadResource method in the Resource Web service of the PSI.

Best Practices for Project Server Extensions

The following are guidelines for developing PSI extensions for Project Server 2007.

  • Do not modify any Project Server database objects (tables, views, stored procedures, and so forth).

  • Read and manipulate Project Server data by calling existing PSI methods.

  • Read Project Server data from the RDB.

  • Incorporate security into your PSI extensions. Do not return data back to users who should not have access to it.

  • PSI methods can throw runtime exceptions in many situations. Make sure all your code is protected with try/catch blocks so the Web service can return sensible reply messages to the client application.

See Also

Concepts

Working with the PSI and DataSets