How to: Use the OLP to Link an Object to a Task

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.

Methods in the ObjectLinkProvider Web service of the Project Server Interface (PSI) enable you to manage links between objects in Microsoft Office Project Server 2007 and external objects. This article shows how to link a project task with a Windows SharePoint Services list item and with any arbitrary object type. You can get information about the linked objects and delete the links. (The code in this article is based on a sample by Eric Zenz, Microsoft Corporation.)

The code samples in this article show the following procedures:

  1. Procedures for Linking Objects to a Task

  2. Link a task and a SharePoint list item

  3. Link a task and a generic object

  4. Get a list of linked SharePoint items

  5. Get a list of linked generic items

  6. Delete links to Web objects

Caution noteCaution

The code sample is for demonstration use on a test system only. Do not use the sample to create or delete object links on a Project Server production system.

The code in this article does not show a complete application. The Project 2007 SDK samples download file (pj12ProjectSDKSamples.exe ) installs a directory named Code Samples\OLPExample with a console application that includes all of the code in this article. For a link to the SDK download, see Welcome to the Microsoft Office Project 2007 SDK.

NoteNote

The OLP sample runs only on the Project Server computer with Windows SharePoint Services installed; you cannot run the application on a remote development client. The sample uses the Microsoft.SharePoint assembly which has dependencies to other SharePoint assemblies in the global assembly cache.

The following procedures show sections of the code for the ManageLinks method of the OLPTest class in the download sample. The procedures use Microsoft Visual C# and Microsoft Visual Studio 2005. For the full sample code listing of the OLPTest class, see the Example section. For more information about Windows SharePoint Services integration, see Windows SharePoint Services Infrastructure for Project Server.

Procedures for Linking Objects to a Task

The OLPTest class uses references to Microsoft.SharePoint and Microsoft.Office.Project.Server, as well as Web references to the LoginWindows, ObjectLinkProvider, Project, and WssInterop Web services in the PSI and the Lists Web service in Windows SharePoint Services. In the sample code, the Web reference namespaces are respectively, WebSvcLoginWindows, WebSvcObjectLinkProvider, WebSvcProject, WebSvcWssInterop, and WssWebSvcLists.

Add the Microsoft.Office.Project.Server.Library.dll assembly to the references; the assembly is located in [Program Files]\Microsoft Office Servers\12.0\Bin. Include the following statement:

using PSLibrary = Microsoft.Office.Project.Server.Library;

The sample code creates instances of the PSI classes, and then sets the Url, Credentials, and CookieContainer properties of each instance, as in the following example for the ObjectLinkProvider and Lists classes. The value of psiBaseUrl is "http://ServerName/ProjectServerName/_vti_bin/psi/".

WebSvcObjectLinkProvider.ObjectLinkProvider objectLinkProvider =
    new WebSvcObjectLinkProvider.ObjectLinkProvider();

objectLinkProvider.Url = psiBaseUrl + "objectlinkprovider.asmx";
objectLinkProvider.Credentials = CredentialCache.DefaultCredentials;
objectLinkProvider.CookieContainer = cookies;

// Set the workspace in wssLists.Url after we determine the workspace exists.
wssLists.Credentials = System.Net.CredentialCache.DefaultCredentials;
wssLists.CookieContainer = cookies;

If the Login method succeeds, then the application continues with Procedures 1 through 6.

Procedure 1. To verify the input values:

  1. In addition to the serverName and pwaName variables, input values for the OLPTest method variables include projectName, taskName, listName, and listItemTitle. The values must be verified before OLPTest can create links. The following code verifies that the specified project exists and gets a value for projectUid. If the project does not exist, log off Project Server and exit the application. The full source for OLPTest in the Example section shows the try/catch/finally blocks and placement of variable definitions such as projectUid.

    Guid projectUid = new Guid();
    bool initError = false;
    . . .
    WebSvcProject.ProjectDataSet projectList = project.ReadProjectList();
    foreach (DataRow projectRow in projectList.Project)
    {
        if ((string)projectRow[projectList.Project.PROJ_NAMEColumn] == projectName)
        {
            projectUid = (Guid)projectRow[projectList.Project.PROJ_UIDColumn];
            break;
        }
    }
    if (projectUid == Guid.Empty)
    {
        initError = true;
        throw new SystemException("Project not found: " + projectName); 
    }
    
  2. Use similar code to get the taskUid.

    Guid taskUid = new Guid();
    . . .
    WebSvcProject.ProjectDataSet dsProject = 
        project.ReadProject(projectUid, WebSvcProject.DataStoreEnum.PublishedStore);
    
    foreach (DataRow objRow in dsProject.Task)
    {
        if ((string)objRow[dsProject.Task.TASK_NAMEColumn] == taskName)
        {
            taskUid = (Guid)objRow[dsProject.Task.TASK_UIDColumn];
            break;
        }
    }
    if (taskUid == Guid.Empty) { /* Throw exception and log off */ }
    
  3. Verify the project workspace exists, and then set the Url property of wssLists. If the URL is not for the project workspace, you cannot get the correct SharePoint lists for issues, risks, documents, or deliverables.

    String workspaceUrl = String.Empty;
    WebSvcWssInterop.ProjectWSSInfoDataSet dsProjectWssInfo = 
        wssInterop.ReadWssData(projectUid);
    
    if (dsProjectWssInfo.ProjWssInfo.Count > 0)
    {
        workspaceUrl = dsProjectWssInfo.ProjWssInfo[0].PROJECT_WORKSPACE_URL.ToString();
        wssLists.Url = workspaceUrl + "/_vti_bin/lists.asmx";
    }
    else { /* Throw exception and log off */ }
    
  4. Get the GUID for the specified SharePoint list.

    1. Create a GetListUid method, as follows.

      private Guid GetListUid(WssWebSvcLists.Lists wssLists, string listName)
      {
          const string idAttribute = "ID";
          const string defaultViewUrl = "DefaultViewUrl";
          string listNodeName = "/" + listName + "/";
          Guid listUid = Guid.Empty;
      
          XmlNode ndLists = wssLists.GetListCollection();
      
          // Get the GUID for the specified SharePoint list.  
          foreach (XmlNode ndList in ndLists.ChildNodes)
          {
              if (ndList.Attributes[defaultViewUrl].Value.Contains(listNodeName))
              {
                  listUid = new Guid(ndList.Attributes[idAttribute].Value);
                  break;
              }
          }
          return listUid;
      }
      

    GetListCollection returns data about all of the lists in the workspace (the URL is specified in the wssLists object). The following example is part of the metadata returned in ndLists for the Issues list. The code checks the DefaultViewUrl and ID attributes.

    <List DocTemplateUrl="" DefaultViewUrl="/ProjectServer/TestProject/Lists/Issues/AllItems.aspx" 
      MobileDefaultViewUrl="" ID="{0CEF7685-B7C7-4C28-93F5-7015F16E5C2C}" Title="Issues" 
      Description="Use the Issues list to manage a set of issues related to this project..." 
      [. . . more attributes . . .] />
    
    1. In OLPTest, call the GetListUid method.

      Guid listUid = GetListUid(wssLists, listName);
      if (listUid == Guid.Empty) { /* Throw exception and log off */  }
      
  5. Verify that the specified list item exists, and get the SharePoint list item ID (the TP_ID).

    1. Create a GetItemTPID method that returns the TP_ID. If the list item doesn't exist, return -1.

      private int GetItemTPID(WssWebSvcLists.Lists wssLists, string listItemTitle)
      {
          int itemTPID = -1;
          XmlDocument xmlDoc = new XmlDocument(); 
          XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
          // Query for the list item title.
          string queryFormat =
              "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>{0}</Value></Eq></Where>";
          ndQuery.InnerXml = string.Format(queryFormat, listItemTitle);
      
          XmlNode ndQueryOptions = xmlDoc.CreateNode(XmlNodeType.Element, "QueryOptions", "");
          ndQueryOptions.InnerXml = "<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>" +
              "<DateInUtc>TRUE</DateInUtc>";
      
          // Get the Title and ID fields in the returned data.
          XmlNode ndViewFields = xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
          ndViewFields.InnerXml = "<FieldRef Name='Title' /><FieldRef Name='ID'/>";
      
          string viewName = string.Empty;
          string webId = string.Empty;
          string rowLimit = string.Empty;
      
          XmlNode ndListItems = wssLists.GetListItems(listName, viewName, ndQuery, ndViewFields,
                                                   rowLimit, ndQueryOptions, webId);
      
          if (ndListItems.ChildNodes.Count > 1
              && ndListItems.ChildNodes[1].ChildNodes.Count > 1)
          {
              string tpidValue = "-1";
              tpidValue = ndListItems.ChildNodes[1].ChildNodes[1].Attributes["ows_ID"].Value;
              itemTPID = Convert.ToInt16(tpidValue);
          }
          return itemTPID;
      }
      

      GetListItems uses XML arguments to filter the results. Following is an example of the returned data in ndListItems. The TP_ID is the value of the ows_ID attribute.

      <rs:data ItemCount="1" xmlns:rs="urn:schemas-microsoft-com:rowset">
          <z:row ows_Attachments="0" ows_ID="1" 
              ows_LinkTitle="Test Issue" ows_Status="(1) Active" 
              ows_Priority="(2) Medium" ows_Category="(2) Category2" 
              ows_DueDate="2006-06-30 17:00:00" ows_MetaInfo="1;#" 
              ows__ModerationStatus="0" ows__Level="1" ows_Title="Test Issue" 
              ows_owshiddenversion="1" ows_UniqueId="1;#{2215ED1D-1A9F-4F96-9653-3161B117EF15}" 
              ows_FSObjType="1;#0" ows_Created_x0020_Date="1;#2006-06-28 11:24:00" 
              ows_Created="2006-06-28 11:24:00" ows_FileLeafRef="1;#1_.000" 
              ows_FileRef="1;#ProjectServer/PTool1/Lists/Issues/1_.000" xmlns:z="#RowsetSchema" />
      </rs:data>
      
    2. In OLPTest, call the GetItemTPID method.

      int itemTPID = GetItemTPID(wssLists, listItemTitle);
      if (itemTPID < 0) { /* Throw exception and log off */ }
      

  1. Determine whether the task has any existing links. If so, get the task Web object GUID. The MessageBox dialog box in the following code is for demonstration purposes only.

    Guid taskWebObjectUid = Guid.Empty;
    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsLinkedObjects = 
        objectLinkProvider.ReadTaskWebObject(taskUid);
    int numTaskWebObjects = dsLinkedObjects.WebObjects.Count;
    
    if (numTaskWebObjects > 0)
        taskWebObjectUid = dsLinkedObjects.WebObjects[0].WOBJ_UID;
    else
    {
        MessageBox.Show("There are no linked Web objects for task: " + taskName);
    }
    
  2. Create a row for the task in the WebObjects table of the ObjectLinkProviderDataSet.

    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsTask = 
        new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow taskRow = 
        dsTask.WebObjects.NewWebObjectsRow();
    
    // Provide all known information to the Web object row for the task.  
    // If a task Web object does not exist, AddWebObjects creates
    // a new Web object and updates WOBJ_UID in taskRow.  
    taskRow.WOBJ_UID = taskWebObjectUid;
    taskRow.WOBJ_TASK_UID = taskUid;
    taskRow.WOBJ_PROJ_UID = projectUid;
    taskRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Task;
    dsTask.WebObjects.AddWebObjectsRow(taskRow);
    
  3. Create a row for the SharePoint list item in a new ObjectLinkProviderDataSet. Include the TP_ID of the list item. As with the task, if the WOBJ_UID value is an empty Guid, AddWebObjectsRow updates WOBJ_UID with a new Guid.

    The switch statement in the following code handles only the default SharePoint list types for project workspaces. You could extend the code to handle other values of WebObjectDatabaseType and WebObjectType values such as GenericSharePointListItem. The linkedItems variable is only for demonstration use in a subsequent message box.

    NoteNote

    The following code assumes that the list item has no other links. A production application should check if the list item already has a link and get the Web object GUID.

    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsListItems = 
        new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow listItemRow = 
        dsListItems.WebObjects.NewWebObjectsRow();
    
    listItemRow.WOBJ_UID = Guid.NewGuid();
    listItemRow.WOBJ_TP_ID = itemTPID;
    listItemRow.WOBJ_LIST_NAME = listUid;
    listItemRow.WOBJ_PROJ_UID = projectUid;
    
    switch (listName)
    {
        case "Issues":
            listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Issue;
            webObjectType = PSLibrary.WebObjectType.Issue;
            linkedItems = "Issues found for task: " + taskName;
            break;
        case "Risks":
            listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Risk;
            webObjectType = PSLibrary.WebObjectType.Risk;
            linkedItems = "Risks found for task: " + taskName;
            break;
        case "Documents":
            listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Document;
            webObjectType = PSLibrary.WebObjectType.Document;
            linkedItems = "Documents found for task: " + taskName;
            break;
        case "Commitments":  // Commitments are now called Deliverables
            listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Commitment;
            webObjectType = PSLibrary.WebObjectType.Commitment;
            linkedItems = "Deliverables found for task: " + taskName;
            break;
        default:
            string errMess = listName +
                " is not a default SharePoint list type for task links.";
            throw new SystemException(errMess);
    }
    dsListItems.WebObjects.AddWebObjectsRow(listItemRow);
    
  4. Link the task Web object with the SharePoint list Web object. You can link the task to multiple list items by adding multiple rows to the ObjectLinkProviderDataSet for the list items (dsListItems) and adding corresponding ObjectLinkType values to the array of link types.

    WebSvcObjectLinkProvider.WebObjectLinkType generalLinkType = 
        WebSvcObjectLinkProvider.WebObjectLinkType.General;
    WebSvcObjectLinkProvider.WebObjectLinkType[] wssLinkTypeArray = 
        { generalLinkType };
    
    objectLinkProvider.CreateWebObjectLinks(dsTask, dsListItems, wssLinkTypeArray);
    

Linking a task or project with a generic object—an object that is not a SharePoint list item—is similar to the process in Procedure 2. The difference is that you must create a Guid object for the WOBJ_LIST_NAME value of the generic Web object and set a value for WOBJ_TP_ID.

  1. Create a Guid for the "list type" and TP_ID value for the generic object. The values are arbitrary, but must be unique. That is, all objects of the same type should have the same Guid, within the scope of the Project Server installation, and each object within a type should have a unique TP_ID value. That enables the OLP methods to list and manage objects of the same type. The sample code shows only one generic object.

    int externalTPID = 1;
    Guid externalUid = new Guid("12345678-1234-1234-1234-123456789012");
    
  2. Create a row for the generic item in an ObjectLinkProviderDataSet.

    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsGeneric = 
        new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
    WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow genericRow = 
        dsGeneric.WebObjects.NewWebObjectsRow();
    genericRow.WOBJ_UID = Guid.NewGuid();
       genericRow.WOBJ_LIST_NAME = externalUid;
    genericRow.WOBJ_TP_ID = externalTPID;                    
    genericRow.WOBJ_PROJ_UID = projectUid;
    genericRow.WOBJ_TYPE = 
        (int)PSLibrary.WebObjectDatabaseType.GenericSharePointListItem;
    dsGeneric.WebObjects.AddWebObjectsRow(genericRow);
    
  3. Link the task Web object with the generic item Web object. The generalLinkType is a WebObjectLinkType enumeration value (defined in Step 4 of Procedure 2).

    WebSvcObjectLinkProvider.WebObjectLinkType[] genericLinkTypeArray =
        { generalLinkType };
    objectLinkProvider.CreateWebObjectLinks(dsTask, dsGeneric, genericLinkTypeArray);
    

Procedure 4. To get a list of SharePoint items linked to a task:

  1. Use ReadTaskLinkedWebObjectsto get an ObjectLinkProviderDataSet for SharePoint Web objects linked to a task. Similar methods that get links for projects and SharePoint list items are ReadProjectLinkedWebObjects and ReadSharePointItemLinkedWebObjects. In the following code, webObjectType is set to a value for one of the default SharePoint types for a project workspace. The WebObjectTypeenumeration includes Issue, Risk, Document, and Commitment.

    dsLinkedObjects = objectLinkProvider.ReadTaskLinkedWebObjects(taskUid, 
        (int)webObjectType);
    
    int itemsFound = 0;
    foreach (WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow objRow
        in dsLinkedObjects.WebObjects)
    {
        if (objRow.WOBJ_TASK_UID == taskUid)
        {
            // The OLP Read*LinkedWebObjects methods return the Web object UIDs of  
            // the linked items AND of the parent task, project, or list item.  
            // Here you can get the Web object UID for the task or project.   
            // You should exclude the parent item from the result set.                    
        }
        else
        {
            // Add the internal OLP GUID, List GUID, and TP_ID. 
            linkedItems += string.Format(
                "\n\n\tWebObjectUid:\t{0}\n\tList UID:\t\t{1}\n\tTP_ID:\t\t{2}",
                objRow.WOBJ_UID.ToString(), 
                objRow.WOBJ_LIST_NAME, 
                objRow.WOBJ_TP_ID.ToString());
            itemsFound++;
        }
    }
    
  2. The sample application displays the results.

    if (itemsFound == 0) 
        linkedItems = "No " + listName.ToLower() + " found, for task: " + taskName; 
    MessageBox.Show(linkedItems, listName);
    

Getting a list of generic items that are linked to a project, task, or SharePoint list item is essentially the same as getting a list of SharePoint items in Procedure 4, except the WebObjectType is GenericSharePointListItem.

Procedure 5. To get a list of generic items linked to a task:

  1. Get an ObjectLinkProviderDataSet for generic Web objects linked to a task.

    dsLinkedObjects = objectLinkProvider.ReadTaskLinkedWebObjects(taskUid, 
        (int)PSLibrary.WebObjectType.GenericSharePointListItem);
    linkedItems = "Generic items found:";
    itemsFound = 0;
    
    foreach (WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow objRow in 
        dsLinkedObjects.WebObjects)
    {
        if (objRow.WOBJ_TASK_UID != taskUid)
        {
            linkedItems += string.Format(
               "\n\n\tWebObjectUid:\t{0}\n\tExternal UID:\t{1}\n\tFake TP_ID:\t{2}",
               objRow.WOBJ_UID.ToString(),
               objRow.WOBJ_LIST_NAME,
               objRow.WOBJ_TP_ID.ToString());
            itemsFound++;
        }
    }
    
  2. Display the results.

    if (itemsFound == 0) 
        linkedItems = "No generic items found for task: " + taskName;
    MessageBox.Show(linkedItems, "Generic Linked Items");
    

Procedure 6 shows the use of DeleteWebObjectLink in a general method that deletes links to Web objects.

  1. Create a general method that deletes Web object links. For example, DeleteTaskLinks deletes any kind of Web object link to a task.

    private int DeleteTaskLinks(WebSvcObjectLinkProvider.ObjectLinkProvider olp, 
        Guid taskUid, Guid objectUid, int itemTPID)
    {
        int deleteResult;
        WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsDeleteTask =
            olp.ReadTaskWebObject(taskUid);
        Guid delTaskUid = dsDeleteTask.WebObjects[0].WOBJ_UID;
    
        WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsDeleteItem =
            olp.ReadSharePointWebObject(itemTPID, objectUid);
        Guid delItemUid = dsDeleteItem.WebObjects[0].WOBJ_UID;
    
        deleteResult = olp.DeleteWebObjectLink(delTaskUid, delItemUid);
        return deleteResult;
    }
    

    You can make DeleteTaskLinks a more general method. For example, name the method DeleteLinks, rename taskUid as sourceUid, rename dsDeleteTask as dsDeleteSource, and add a parameter such as sourceType that is a WebObjectType. Select which method to use for populating the dsDeleteSource object based on the sourceType value; that is, call the ReadTaskWebObject, ReadProjectWebObject, or ReadSharePointWebObject method.

  2. Call DeleteTaskLinks to delete a link from a task to a SharePoint or a generic Web object. If deleteLinks in the sample application is true, the application deletes both of the links it created.

    if (deleteLinks)
    {
        int[] deletedItems = { 0, 0 };
    
        // Delete the link from the task to the SharePoint list item.
        deletedItems[0] = DeleteTaskLinks(objectLinkProvider, taskUid, listUid, itemTPID);
    
        // Delete the link from the task to the generic item.
        deletedItems[1] = DeleteTaskLinks(objectLinkProvider, taskUid, externalUid, externalTPID);
    
        string deletedResults = "For task: " + taskName + "\n\n";
        deletedResults += "Deleted SharePoint list items: ";
        deletedResults +=
            string.Format("{0}\n\nDeleted generic items: {1}",
                deletedItems[0].ToString(), deletedItems[1].ToString());
        MessageBox.Show(deletedResults, "Deleted Links");
    }
    

Note

Deleting a link does not delete the Web objects that were linked. You can use the read methods for Web objects to get the datasets again and then re-link them. If the Web object datasets are still available in your application, you can use ReadOrCreateWebObjects to get an updated ObjectLinkProviderDataSet for each Web object.

Example

The following code shows the OLPTest class. The complete console application is in the Project 2007 SDK samples download file (pj12ProjectSDKSamples.exe); for a link to the download files, see Welcome to the Microsoft Office Project 2007 SDK.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Data;
using System.Xml;
// Add for SoapException error handler:
using System.Web.Services.Protocols;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace Microsoft.Office.Project.Samples.OLP_Example
{
    class OLPTest
    {
        #region Private class variables
        private const string URLPREFIX = "http://";
        private const string PSIPATH = "/_vti_bin/psi/";
        private const string WSSPATH = "/_vti_bin/";

        private string serverName;    // Name of server where Project Web Access is installed.
        private string pwaName;       // Name of Project Web Access instance.
        private string projectName;   // Name of the project.
        private string taskName;      // Name of the task.
        private string listName;      // Name of the SharePoint list
        private string listItemTitle; // Title of the SharePoint list item.
        private bool deleteLinks;     // Delete links after they are created.
        private PSLibrary.WebObjectType webObjectType; // Type of Web object (issue, risk, etc.)
        private string linkedItems;   // List of linked items to be displayed.
        private string psiBaseUrl;
        private string wssBaseUrl;
        #endregion

        public OLPTest(string server, string pwa, string project, string task, 
            string list, string item, bool delete)
        {
            serverName = server;
            pwaName = pwa;
            psiBaseUrl = URLPREFIX + serverName + "/" + pwa + PSIPATH;
            wssBaseUrl = URLPREFIX + serverName + "/" + pwa + WSSPATH;
            projectName = project;
            taskName = task;
            listName = list;
            listItemTitle = item;
            deleteLinks = delete;
        }

        public void ManageLinks()
        {
        #region Initialize the Web service classes
            WebSvcLoginWindows.LoginWindows loginWindows = 
                new WebSvcLoginWindows.LoginWindows();
            WebSvcWssInterop.WssInterop wssInterop =
                new WebSvcWssInterop.WssInterop();
            WebSvcProject.Project project =
                new WebSvcProject.Project();
            WebSvcObjectLinkProvider.ObjectLinkProvider objectLinkProvider =
                new WebSvcObjectLinkProvider.ObjectLinkProvider();

            // Lists is a SharePoint Web service.
            WssWebSvcLists.Lists wssLists = new WssWebSvcLists.Lists();

            CookieContainer cookies = new CookieContainer();

            // Set properties of Web service objects
            loginWindows.Url = psiBaseUrl + "loginwindows.asmx";
            loginWindows.Credentials = System.Net.CredentialCache.DefaultCredentials;
            loginWindows.CookieContainer = cookies;

            wssInterop.Url = psiBaseUrl + "wssinterop.asmx";
            wssInterop.Credentials = System.Net.CredentialCache.DefaultCredentials;
            wssInterop.CookieContainer = cookies;

            project.Url = psiBaseUrl + "project.asmx";
            project.Credentials = System.Net.CredentialCache.DefaultCredentials;
            project.CookieContainer = cookies;

            objectLinkProvider.Url = psiBaseUrl + "objectlinkprovider.asmx";
            objectLinkProvider.Credentials = System.Net.CredentialCache.DefaultCredentials;
            objectLinkProvider.CookieContainer = cookies;

            // Set the workspace in wssLists.Url after we determine the workspace exists.
            wssLists.Credentials = System.Net.CredentialCache.DefaultCredentials;
            wssLists.CookieContainer = cookies;

            Guid taskUid = new Guid();
            Guid projectUid = new Guid();
            String workspaceUrl = String.Empty;
            int itemTPID = -1;
            Guid listUid = Guid.Empty;
            bool loggedOn = false;
            bool initError = false;

        #endregion

        #region Log on and verify the input values
            try
            {
                if (loginWindows.Login())
                {
                    MessageBox.Show("Logon succeeded", serverName,
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                    loggedOn = true;
                }
                else
                {
                    initError = true;
                    throw new SystemException("Logon failed.");
                }

                // Get a project UID for the specified project.
                WebSvcProject.ProjectDataSet projectList = project.ReadProjectList();
                foreach (DataRow projectRow in projectList.Project)
                {
                    if ((string)projectRow[projectList.Project.PROJ_NAMEColumn] == projectName)
                    {
                        projectUid = (Guid)projectRow[projectList.Project.PROJ_UIDColumn];
                        break;
                    }
                }

                if (projectUid == Guid.Empty)
                {
                    initError = true;
                    throw new SystemException("Project not found: " + projectName);
                }

                // Get a task UID for the specified task.
                WebSvcProject.ProjectDataSet dsProject =
                    project.ReadProject(projectUid, WebSvcProject.DataStoreEnum.PublishedStore);

                foreach (DataRow objRow in dsProject.Task)
                {
                    if ((string)objRow[dsProject.Task.TASK_NAMEColumn] == taskName)
                    {
                        taskUid = (Guid)objRow[dsProject.Task.TASK_UIDColumn];
                        break;
                    }
                }

                if (taskUid == Guid.Empty)
                {
                    initError = true;
                    throw new SystemException("Task not found: " + taskName);
                }

                // Set the workspace URL for the SharePoint Lists Web service.
                WebSvcWssInterop.ProjectWSSInfoDataSet dsProjectWssInfo =
                    wssInterop.ReadWssData(projectUid);

                if (dsProjectWssInfo.ProjWssInfo.Count > 0)
                {
                    workspaceUrl = dsProjectWssInfo.ProjWssInfo[0].PROJECT_WORKSPACE_URL.ToString();
                    wssLists.Url = workspaceUrl + "/_vti_bin/lists.asmx";
                }
                else
                {
                    initError = true;
                    throw new SystemException("Workspace not found for project: " + projectName);
                }

                // Get the list GUID in the project workspace from the Lists Web service.
                listUid = GetListUid(wssLists, listName);
                if (listUid == Guid.Empty)
                {
                    initError = true;
                    throw new SystemException("SharePoint list not found: " + listName);
                }

                // Get the TP_ID for the specified SharePoint list item.
                itemTPID = GetItemTPID(wssLists, listItemTitle);
                if (itemTPID < 0)
                {
                    initError = true;
                    throw new SystemException(string.Format("List {0} does not contain item: {1}",
                        listName, listItemTitle));
                }
            }
            catch (WebException ex)
            {
                string errMess = ex.Message;
                errMess += "\n\nCheck value of serverName variable.";
                MessageBox.Show(errMess, "Web Exception");
                //return;
            }
            catch (SoapException ex)
            {
                string errMess = ex.Message;
                errMess += "\n\nCheck value of pwaName variable.";
                MessageBox.Show(errMess, "Soap Exception");
                //return;
            }
            catch (SystemException ex)
            {
                string errMess = ex.Message;
                MessageBox.Show(errMess, "Exception");
            }
            finally
            {
                if (initError && loggedOn) loginWindows.Logoff();
            }
            if (initError) return;
        #endregion

            try
            {
                #region Link task and SharePoint list item
                // Get the task Web object, if there are any existing links to the task.  
                Guid taskWebObjectUid = Guid.Empty;
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsLinkedObjects =
                    objectLinkProvider.ReadTaskWebObject(taskUid);
                int numTaskWebObjects = dsLinkedObjects.WebObjects.Count;

                if (numTaskWebObjects > 0)
                    taskWebObjectUid = dsLinkedObjects.WebObjects[0].WOBJ_UID;
                else
                {
                    MessageBox.Show("There are no linked Web objects for task: " + taskName);
                }

                // Create a Web object for the specified task.
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsTask =
                    new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow taskRow =
                    dsTask.WebObjects.NewWebObjectsRow();

                // Provide all known information to the Web object row for the task.  
                // If a task Web object does not exist, AddWebObjects creates
                // a new Web object and updates WOBJ_UID in taskRow.  
                taskRow.WOBJ_UID = taskWebObjectUid;
                taskRow.WOBJ_TASK_UID = taskUid;
                taskRow.WOBJ_PROJ_UID = projectUid;
                taskRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Task;
                dsTask.WebObjects.AddWebObjectsRow(taskRow);

                // Create a Web object for the list item, with the TP_ID of the list item.
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsListItems =
                    new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow listItemRow =
                    dsListItems.WebObjects.NewWebObjectsRow();

                // Provide all known information to the Web object row for the list item.  
                // If a list item Web object does not exist, AddWebObjects creates
                // a new Web object and updates WOBJ_UID in listItemRow.  
                listItemRow.WOBJ_UID = Guid.NewGuid();
                listItemRow.WOBJ_TP_ID = itemTPID;
                listItemRow.WOBJ_LIST_NAME = listUid;
                listItemRow.WOBJ_PROJ_UID = projectUid;

                switch (listName)
                {
                    case "Issues":
                        listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Issue;
                        webObjectType = PSLibrary.WebObjectType.Issue;
                        linkedItems = "Issues found for task: " + taskName;
                        break;
                    case "Risks":
                        listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Risk;
                        webObjectType = PSLibrary.WebObjectType.Risk;
                        linkedItems = "Risks found for task: " + taskName;
                        break;
                    case "Documents":
                        listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Document;
                        webObjectType = PSLibrary.WebObjectType.Document;
                        linkedItems = "Documents found for task: " + taskName;
                        break;
                    case "Commitments":  // Commitments are now called Deliverables
                        listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Commitment;
                        webObjectType = PSLibrary.WebObjectType.Commitment;
                        linkedItems = "Deliverables found for task: " + taskName;
                        break;
                    default:
                        string errMess = listName +
                            " is not a default SharePoint list type for task links.";
                        throw new SystemException(errMess);
                        //break;  // this line is unreachable
                }
                dsListItems.WebObjects.AddWebObjectsRow(listItemRow);

                // You can link the task to multiple list items by adding multiple rows  
                // to dsListItems and adding ObjectLinkType values to the array of link types. 
                WebSvcObjectLinkProvider.WebObjectLinkType generalLinkType =
                    WebSvcObjectLinkProvider.WebObjectLinkType.General;
                WebSvcObjectLinkProvider.WebObjectLinkType[] wssLinkTypeArray = 
                    { generalLinkType };

                objectLinkProvider.CreateWebObjectLinks(dsTask, dsListItems, wssLinkTypeArray);
                #endregion

                #region Link task and a generic object
                // Link a generic external item to the task. 
                // WOBJ_TP_ID is an arbitrary constant in this case, 
                // because the GUID is the important part for the link.
                int externalTPID = 1;
                // The external item must have a GUID; folowing is a sample GUID.
                Guid externalUid = new Guid("12345678-1234-1234-1234-123456789012");

                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsGeneric =
                    new WebSvcObjectLinkProvider.ObjectLinkProviderDataSet();
                WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow genericRow =
                    dsGeneric.WebObjects.NewWebObjectsRow();

                genericRow.WOBJ_UID = Guid.NewGuid();

                // Instead of a SharePoint list GUID, you can substitute any GUID here.  
                // If the object doesn't have a GUID, or you need to use the GUID and  
                // another ID, you can use the WOBJ_TP_ID field.
                genericRow.WOBJ_LIST_NAME = externalUid;

                genericRow.WOBJ_TP_ID = externalTPID;
                genericRow.WOBJ_PROJ_UID = projectUid;
                genericRow.WOBJ_TYPE =
                    (int)PSLibrary.WebObjectDatabaseType.GenericSharePointListItem;
                dsGeneric.WebObjects.AddWebObjectsRow(genericRow);

                // Create a new link type array for the generic link.
                WebSvcObjectLinkProvider.WebObjectLinkType[] genericLinkTypeArray =
                    { generalLinkType };

                objectLinkProvider.CreateWebObjectLinks(dsTask, dsGeneric, genericLinkTypeArray);
                #endregion

                #region Get list of SharePoint items linked to the task
                // Get the OLP DataSet for all SharePoint items of specified type 
                // that are linked to the task.
                dsLinkedObjects = objectLinkProvider.ReadTaskLinkedWebObjects(taskUid,
                    (int)webObjectType);

                int itemsFound = 0;
                foreach (WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow objRow
                    in dsLinkedObjects.WebObjects)
                {
                    if (objRow.WOBJ_TASK_UID == taskUid)
                    {
                        // The OLP Read*LinkedWebObjects methods return the Web object UIDs of  
                        // the linked items AND of the parent task, project, or list item.  
                        // Here you can get the Web object UID for the task or project.   
                        // You should exclude the parent item from the result set.                    
                    }
                    else
                    {
                        //Add the internal OLP GUID, List GUID, and TP_ID. 
                        linkedItems += string.Format(
                            "\n\n\tWebObjectUid:\t{0}\n\tList UID:\t\t{1}\n\tTP_ID:\t\t{2}",
                            objRow.WOBJ_UID.ToString(),
                            objRow.WOBJ_LIST_NAME,
                            objRow.WOBJ_TP_ID.ToString());
                        itemsFound++;
                    }
                }
                if (itemsFound == 0)
                    linkedItems = "No " + listName.ToLower() + " found, for task: " + taskName;
                MessageBox.Show(linkedItems, listName);
                #endregion

                #region Get list of generic items linked to the task
                // Get the OLP DataSet for all generic items linked to the task.
                dsLinkedObjects = objectLinkProvider.ReadTaskLinkedWebObjects(taskUid,
                    (int)PSLibrary.WebObjectType.GenericSharePointListItem);

                linkedItems = "Generic items found:";

                itemsFound = 0;
                foreach (WebSvcObjectLinkProvider.ObjectLinkProviderDataSet.WebObjectsRow objRow in
                    dsLinkedObjects.WebObjects)
                {
                    if (objRow.WOBJ_TASK_UID != taskUid)
                    {
                        linkedItems += string.Format(
                           "\n\n\tWebObjectUid:\t{0}\n\tExternal UID:\t{1}\n\tFake TP_ID:\t{2}",
                           objRow.WOBJ_UID.ToString(),
                           objRow.WOBJ_LIST_NAME,
                           objRow.WOBJ_TP_ID.ToString());
                        itemsFound++;
                    }
                }
                if (itemsFound == 0)
                    linkedItems = "No generic items found for task: " + taskName;
                MessageBox.Show(linkedItems, "Generic Linked Items");
                #endregion

                #region Delete the links
                if (deleteLinks)
                {
                    int[] deletedItems = { 0, 0 };

                    // Delete the link from the task to the SharePoint list item.
                    deletedItems[0] = DeleteTaskLinks(objectLinkProvider, taskUid, listUid, itemTPID);

                    // Delete the link from the task to the generic item.
                    deletedItems[1] = DeleteTaskLinks(objectLinkProvider, taskUid, externalUid, externalTPID);

                    string deletedResults = "For task: " + taskName + "\n\n";
                    deletedResults += "Deleted SharePoint list items: ";
                    deletedResults +=
                        string.Format("{0}\n\nDeleted generic items: {1}",
                            deletedItems[0].ToString(), deletedItems[1].ToString());
                    MessageBox.Show(deletedResults, "Deleted Links");
                }
                #endregion
            }
            catch (SoapException ex)
            {
                MessageBox.Show("Message:\n" + ex.Message +
                    "\nDetail:\n" + ex.Detail.InnerText +
                    "\nStackTrace:\n" + ex.StackTrace);
            }
            catch (SystemException ex)
            {
                string errMess = ex.Message;
                MessageBox.Show(errMess, "Exception");
            }
            finally
            {
                loginWindows.Logoff();
            }
        }

        #region Private methods
        private Guid GetListUid(WssWebSvcLists.Lists wssLists, string listName)
        {
            const string idAttribute = "ID";
            const string defaultViewUrl = "DefaultViewUrl";
            string listNodeName = "/" + listName + "/";
            Guid listUid = Guid.Empty;
            
            XmlNode ndLists = wssLists.GetListCollection();

            // Get the GUID for the specified SharePoint list.  
            foreach (XmlNode ndList in ndLists.ChildNodes)
            {
                if (ndList.Attributes[defaultViewUrl].Value.Contains(listNodeName))
                {
                    listUid = new Guid(ndList.Attributes[idAttribute].Value);
                    break;
                }
            }
            return listUid;
        }

        private int GetItemTPID(WssWebSvcLists.Lists wssLists, string listItemTitle)
        {
            int itemTPID = -1;
            XmlDocument xmlDoc = new XmlDocument(); 
            XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
            // Query for the list item title.
            string queryFormat =
                "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>{0}</Value></Eq></Where>";
            ndQuery.InnerXml = string.Format(queryFormat, listItemTitle);

            XmlNode ndQueryOptions = xmlDoc.CreateNode(XmlNodeType.Element, "QueryOptions", "");
            ndQueryOptions.InnerXml = "<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>" +
                "<DateInUtc>TRUE</DateInUtc>";

            // Get the Title and ID fields in the returned data.
            XmlNode ndViewFields = xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
            ndViewFields.InnerXml = "<FieldRef Name='Title' /><FieldRef Name='ID'/>";

            string viewName = string.Empty;
            string webId = string.Empty;
            string rowLimit = string.Empty;

            XmlNode ndListItems = wssLists.GetListItems(listName, viewName, ndQuery, ndViewFields,
                                                     rowLimit, ndQueryOptions, webId);

            // GetListItems returns <listitems> element with the child <rs:data ItemCount="1">,
            // which contains the following <z:row> child element: 
            //     <z:row ows_Title="Test Issue 1" ows_ID="1" ...[additional metadata] /> 
            // The TP_ID is in the ows_ID attribute, so we can jump to that row. 
            if (ndListItems.ChildNodes.Count > 1
                && ndListItems.ChildNodes[1].ChildNodes.Count > 1)
            {
                string tpidValue = "-1";
                tpidValue = ndListItems.ChildNodes[1].ChildNodes[1].Attributes["ows_ID"].Value;
                itemTPID = Convert.ToInt16(tpidValue);
            }
            return itemTPID;
        }

        private int DeleteTaskLinks(WebSvcObjectLinkProvider.ObjectLinkProvider olp, 
            Guid taskUid, Guid objectUid, int itemTPID)
        {
            int deleteResult;
            WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsDeleteTask =
                olp.ReadTaskWebObject(taskUid);
            Guid delTaskUid = dsDeleteTask.WebObjects[0].WOBJ_UID;

            WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsDeleteItem =
                olp.ReadSharePointWebObject(itemTPID, objectUid);
            Guid delItemUid = dsDeleteItem.WebObjects[0].WOBJ_UID;

            deleteResult = olp.DeleteWebObjectLink(delTaskUid, delItemUid);
            return deleteResult;
        }
        #endregion
    }
}

In the SDK sample download, the OLP Example solution for Visual Studio 2005 contains the OLP Example project. The Debug tab of the OLP Examplewindow (click OLP Example Properties on the Project menu) includes the following command line arguments.

-server ServerName -pwa ProjectServer -project TestProject -task "T1" -list Issues -item "Test issue 1"

If you include the optional argument -delete, the application creates, lists, and then deletes links from the task to the specified list item and to a generic object. If the -delete argument is missing, the application does not delete the links. You can use Project Web Access or Project Professional to see links to SharePoint list items in the Indicators column.

Update the Web references before you compile the application. The sample application was built on a different system than the one you are using, so the easiest way to update the Web references is to delete and then re-create them using your own URL. For example, modify the following URL to add the ObjectLinkProvider Web service.

http://ServerName/ProjectServerName/_vti_bin/psi/ObjectLinkProvider.asmx

Before you run the sample application, perform the following steps:

  1. Use Project Professional 2007 to create and publish a test project with at least one task.

  2. Use Project Web Access to open the test project workspace.

  3. Create at least one issue or risk in the Issue or Risk SharePoint list of the project workspace.

  4. To debug the sample for your test installation of Project Server, change the values of the command line arguments on the Debug tab of the Visual Studio project properties to match the values for your test project.

See Also

Concepts

Windows SharePoint Services Infrastructure for Project Server

Using the Object Link Provider