Cascade Skyline - with Microsoft Logo and Project Support header - author Brian Smith

Creating Issues and Linking to Tasks. Better than task notes?

Creating Issues and Linking to Tasks. Better than task notes?

Rate This
  • Comments 6

There are some limitations with the task notes field and the PSI which are discussed elsewhere on the blogs with a potential workaround of using VBA to access the RTF information.

Another workaround you might consider is to use SharePoint and the Project Workspaces and instead of having a task note use the issues or risks lists or document library associated with the project.

In the attached sample I have modified the LoginDemo sample with an additional button that creates a new issue list item in the project workspace. If you don't create a workspace - or click the button before the workspace has been created by the queue then you will discover how much error handling I have coded in (none!). As well as creating an issue I have modified the project creation piece to also create a task in the project - and then finally I create a link between the new issue and this single task using the PSI ObjectLinkProvider web service. I've included the code below in the posting for the main part of the application which is the only .cs file I manually changed from the original LoginDemo. I've commented my changes so you can either make just the mods or use the attached zip of the full modified LoginDemo. If you just modify then note you will also need a reference to Microsoft.SharePoint and a web reference to the Object Link Provider web service.

Once you have a link then this is visible in the Project Center view both at the task level - you could also link at the project level. The extra bonuses of using a WSS list include alerts, RSS feed and the ability to attach other files - and if you really want to get clever then you can implement some of the WSS workflow on the inserted issue. Much better than tasks notes! One possible extension of this could the the inclusion of content in a workspace template and then linking up the documents to projects/tasks based on an event.

using System;
using System.Data;
using System.Drawing;
using System.Net;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Web.Services.Protocols;
using System.Threading;
using Microsoft.SharePoint;
using PSLibrary = Microsoft.Office.Project.Server.Library;

/* NOTE: 
 * Delete the Admin, LoginForms, LoginWindows, and Project Web References, and 
 * re-add them for your own Project Server. Use the following URLs 
 * (substitute your values for ServerName and ProjectServerName):
 *
 *   http://ServerName/ProjectServerName/_vti_bin/psi/admin.asmx
 *   http://ServerName/ProjectServerName/_vti_bin/psi/project.asmx
 *   http://ServerName/ProjectServerName/_vti_bin/psi/LoginForms.asmx
 *   http://ServerName/ProjectServerName/_vti_bin/psi/loginwindows.asmx
 */

/* NOTE:
 * For the Object Link Provider example you need to add a reference and using statement for Microsoft.SharePoint 
 * and a Web Reference for the PSI Object Link Provider web service and
 */

namespace LoginDemo
{
    public partial class LogonProjectServer : Form
    {
        private const string URLPREFIX = "http://";
        private const string PROJECTWEBSERVICE = "_vti_bin/PSI/Project.asmx";
        private const string ADMINWEBSERVICE = "_vti_bin/PSI/Admin.asmx";
        // Constant for Object Link Provider URL
        private const string OLPWEBSERVICE = "_vti_bin/PSI/ObjectLinkProvider.asmx";
        private string baseUrl; // Example: http://ServerName/ProjectServerName/
        private string userName;
        private string password;
        private bool winLogon = true;
        private bool containsUrl = false;
        private bool loggedOn = false;
        private string serverName;
        // Extra definitions for OLP
        private PSLibrary.WebObjectType webObjectType;
        private string linkedItems; 

        public static LoginDemo.WebSvcProject.Project project = 
            new LoginDemo.WebSvcProject.Project();
        public static WebSvcAdmin.Admin admin =
            new LoginDemo.WebSvcAdmin.Admin();
        // Added for Object Link Provider
        public static LoginDemo.WebSvcObjectLinkProvider.ObjectLinkProvider objectLinkProvider = 
            new LoginDemo.WebSvcObjectLinkProvider.ObjectLinkProvider();


        private static LoginUtils loginUtils = new LoginUtils();
        private static AdminUtils adminUtils = new AdminUtils();
        // Defined here so they can be used in the OLP section
        private Guid projectGuid;
        private Guid taskGuid;
        

        public LogonProjectServer()
        {
            InitializeComponent();
            lblUserName.Enabled = false;
            lblPassword.Enabled = false;
            txtUserName.Enabled = false;
            txtPassword.Enabled = false;
            radFormsAuthentication.Enabled = false;
            radWindowsAuthentication.Enabled = false;
            btnLogon.Enabled = false;
            btnLogOff.Enabled = false;
            lblProjectCreated.Visible = false;
            txtWorkspaceSubSite.Enabled = false;
            lblVersion.Text = "";

            //Get the user.config or the default for the ProjectServerUrl property setting
            txtProjectServerUrl.Text = Properties.Settings.Default.ProjectServerUrl;
            if (txtProjectServerUrl.Text != "http://ServerName/ProjectServer/")
            {
                radFormsAuthentication.Enabled = true;
                radWindowsAuthentication.Enabled = true;
                btnLogon.Enabled = true;
                btnLogon.Select();
            }
        }

        private void radFormsAuthentication_CheckedChanged(object sender, EventArgs e)
        {
            if (radFormsAuthentication.Checked)
            {
                lblUserName.Enabled = true;
                lblPassword.Enabled = true;
                txtUserName.Enabled = true;
                txtPassword.Enabled = true;
                winLogon = false;
                loggedOn = false;
                lblLoggedOn.Visible = false;
                btnLogOff.Enabled = false;
                lblProjectCreated.Text = "";
                lblWorkspaceUrl.Text = "";
            }
        }

        private void radWindowsAuthentication_CheckedChanged(object sender, EventArgs e)
        {
            if (radWindowsAuthentication.Checked)
            {
                lblUserName.Enabled = false;
                lblPassword.Enabled = false;
                txtUserName.Enabled = false;
                txtPassword.Enabled = false;
                winLogon = true;
                loggedOn = false;
                lblLoggedOn.Visible = false;
                btnLogOff.Enabled = false;
                lblProjectCreated.Text = "";
                lblWorkspaceUrl.Text = "";
            }
        }

        private void txtProjectServerUrl_TextChanged(object sender, EventArgs e)
        {
            string url = txtProjectServerUrl.Text.Trim();

            if (url.StartsWith(URLPREFIX) && url.Length > 10)
                containsUrl = true;
            else
            {
                containsUrl = false;
            }
        }

        private void txtProjectServerUrl_Leave(object sender, EventArgs e)
        {
            if (containsUrl)
            {
                if (!txtProjectServerUrl.Text.EndsWith("/"))
                    txtProjectServerUrl.Text += "/";
                radFormsAuthentication.Enabled = true;
                radWindowsAuthentication.Enabled = true;
                btnLogon.Enabled = true;
            }
            else
            {
                btnLogon.Enabled = false;
                MessageBox.Show("Invalid Project Server URL", "Invalid URL",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                radFormsAuthentication.Enabled = false;
                radWindowsAuthentication.Enabled = false;
                btnLogon.Enabled = false;
            }
        }

        private void txtUserName_TextChanged(object sender, EventArgs e)
        {
            userName = txtUserName.Text;
        }

        private void txtPassword_TextChanged(object sender, EventArgs e)
        {
            password = txtPassword.Text;
        }

        private void btnLogon_Click(object sender, EventArgs e)
        {
            baseUrl = txtProjectServerUrl.Text;
            lblLoggedOn.Visible = false;
            string errMess = "";
            serverName = loginUtils.GetServerName(baseUrl);

            this.Cursor = Cursors.WaitCursor;
            try
            {
                loggedOn = loginUtils.LogonPS(winLogon, baseUrl, userName, password);
            }
            catch (SoapException ex)
            {
                errMess = ex.Message.ToString();
            }
            catch (WebException ex)
            {
                errMess = ex.Message.ToString();
            }
            this.Cursor = Cursors.Default;

            if (loggedOn)
            {
                AddContextInfo();
                lblLoggedOn.Text = "Logon Succeeded!";
                lblLoggedOn.ForeColor = Color.Green;
                btnLogOff.Enabled = true;

                string version = adminUtils.ProjectServerVersion(admin);
                if (version.StartsWith("Error"))
                {
                    MessageBox.Show(version, "Admin Web service error",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                    lblVersion.Text = "Project Server Version: " + version;
            }
            else
            {
                MessageBox.Show(errMess, "Logon Error", MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                lblLoggedOn.Text = "Logon Failed!";
                lblLoggedOn.ForeColor = Color.Red;
            }

            lblLoggedOn.Visible = true;
        }

        private void btnLogOff_Click(object sender, EventArgs e)
        {
            bool loggedOff = false;
            string errMess = "";
            try
            {
                loggedOff = loginUtils.LogoffPS(winLogon);
            }
            catch (SoapException ex)
            {
                errMess = ex.Message.ToString();
            }
            catch (WebException ex)
            {
                errMess = ex.Message.ToString();
            }
            if (loggedOff)
            {
                RemoveContextInfo();
                lblLoggedOn.Text = "Logged Off!";
                lblLoggedOn.ForeColor = Color.Black;
                lblLoggedOn.Visible = true;
                lblProjectCreated.Text = "";
                lblVersion.Text = "";
                lblWorkspaceLabel.Visible = false;
                lblWorkspaceUrl.Text = "";
            }
            else
            {
                MessageBox.Show(errMess, "Logoff Error", MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            }
        }

        private void btnSaveUrl_Click(object sender, EventArgs e)
        {
            //Save the ProjectServerUrl property to the user.config file.
            Properties.Settings.Default.ProjectServerUrl = txtProjectServerUrl.Text;
            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Add the URL, and the user credentials or logon cookie, to each 
        /// PSI Web service object in the application.
        /// </summary>
        public void AddContextInfo()
        {
            // Add the Url property first.
            admin.Url = loginUtils.BaseUrl + ADMINWEBSERVICE;
            project.Url = loginUtils.BaseUrl + PROJECTWEBSERVICE;
            // Url property for OLP
            objectLinkProvider.Url = loginUtils.BaseUrl + OLPWEBSERVICE;


            if (winLogon)   // Add Windows credentials
            {
                admin.Credentials = CredentialCache.DefaultCredentials;
                project.Credentials = CredentialCache.DefaultCredentials;
                // Windows credentials for OLP
                objectLinkProvider.Credentials = CredentialCache.DefaultCredentials;
            }
            else            // Add Project Server logon cookie for Forms logon
            {
                admin.CookieContainer = loginUtils.Cookies;
                project.CookieContainer = loginUtils.Cookies;
                // Forms logon cookie for OLP
                objectLinkProvider.CookieContainer = loginUtils.Cookies;
            }
        }

        /// <summary>
        /// Remove the user credentials or logon cookie from each PSI Web service object.
        /// </summary>
        public void RemoveContextInfo()
        {
            if (winLogon)
            {
                admin.Credentials = null;
                project.Credentials = null;
                // Added for OLP
                objectLinkProvider.Credentials = null;
            }
            else
            {
                admin.CookieContainer = null;
                project.CookieContainer = null;
                // Adde for OLP
                objectLinkProvider.CookieContainer = null;
            }
        }

        /// <summary>
        /// Test the application with a PSI call. For example, create a project. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCreateProject_Click(object sender, EventArgs e)
        {
            string projectCreatedLabel = "Project created!";
            string wssUrl;
            string projectWorkspace = ResetWorkspaceUrl();
            bool created = false;
            lblProjectCreated.Text = "";
            lblWorkspaceUrl.Text = projectWorkspace;
            this.Cursor = Cursors.WaitCursor;

            try
            {
                WebSvcProject.ProjectDataSet dsProject = 
                    new WebSvcProject.ProjectDataSet();
                WebSvcProject.ProjectDataSet.ProjectRow projectRow = 
                    dsProject.Project.NewProjectRow();

                projectGuid = Guid.NewGuid();
                projectRow.PROJ_UID = projectGuid;
                projectRow.PROJ_NAME = this.txtProjectName.Text;
                projectRow.PROJ_TYPE = 
                    Convert.ToInt32(PSLibrary.Project.ProjectType.Project);
                
                dsProject.Project.AddProjectRow(projectRow);
                
                // Adding a task to the project so we can have something to link to
                WebSvcProject.ProjectDataSet.TaskRow taskRow =
                    dsProject.Task.NewTaskRow();
                taskGuid = Guid.NewGuid();
                taskRow.PROJ_UID = projectGuid;
                taskRow.TASK_UID = taskGuid;
                taskRow.TASK_NAME = "Task 1";
                taskRow.TASK_DUR = 480000;
                dsProject.Task.AddTaskRow(taskRow);
                

                Guid jobGuid = Guid.NewGuid();
                bool validateOnly = false;
                // Create and save project to the Draft db
                project.QueueCreateProject(jobGuid, dsProject, validateOnly);

                // Wait 3 seconds (more or less) for Queue job to complete.
                // Or, add a routine that checks the QueueSystem for job completion.
                System.Threading.Thread.Sleep(3000);

                WebSvcProject.ProjectRelationsDataSet dsProjectRelations =
                    new WebSvcProject.ProjectRelationsDataSet();
                jobGuid = Guid.NewGuid();

                // Set wssUrl = "" to have default WSS project workspace, or null to have no workspace.
                if (chkDefaultWorkspace.Checked)
                    wssUrl = "";
                else if (projectWorkspace == "")
                    wssUrl = null;
                else
                    wssUrl = projectWorkspace;

                bool fullPublish = true;
                // Publishes project to the Published db
                dsProjectRelations = project.QueuePublish(jobGuid, projectGuid, fullPublish, wssUrl);
                created = true;
            }
            catch (SoapException ex)
            {
                string errMess = "";
                // Pass the exception to the PSClientError constructor to get 
                // all error information.
                PSLibrary.PSClientError error = new PSLibrary.PSClientError(ex);
                PSLibrary.PSErrorInfo[] errors = error.GetAllErrors();

                for (int j = 0; j < errors.Length; j++)
                {
                    errMess += errors[j].ErrId.ToString() + "\n";
                }
                errMess += "\n" + ex.Message.ToString();

                MessageBox.Show(errMess, "Error", MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
            catch (WebException ex)
            {
                string message = ex.Message.ToString() +
                    "\n\nLog on, or check the Project Server Queuing Service";
                MessageBox.Show(message, "Project Creation Error", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            this.Cursor = Cursors.Default;
           
            if (created)
            {
                lblProjectCreated.ForeColor = Color.Green;
                lblWorkspaceUrl.Visible = true;
                lblWorkspaceLabel.Visible = true;
            }
            else
            {
                projectCreatedLabel = "Project not created";
                lblProjectCreated.ForeColor = Color.Red;
                lblWorkspaceUrl.Visible = false;
                lblWorkspaceLabel.Visible = true;
            }
            lblProjectCreated.Text = projectCreatedLabel;
            lblProjectCreated.Visible = true;
        }

        private string ResetWorkspaceUrl()
        {
            string workspace;
            if (chkDefaultWorkspace.Checked)
            {
                workspace = "http://" + serverName + "/" + txtProjectName.Text;
            }
            else
            {
                workspace = "http://" + serverName + "/" + txtWorkspaceSubSite.Text;
            }
            return workspace;
        }

        private void txtProjectName_TextChanged(object sender, EventArgs e)
        {
            lblProjectCreated.Text = "";
            if (chkDefaultWorkspace.Checked)
                txtWorkspaceSubSite.Text = txtProjectName.Text;
            lblWorkspaceLabel.Visible = false;
            lblWorkspaceUrl.Visible = false;
        }

        private void chkDefaultWorkspace_CheckedChanged(object sender, EventArgs e)
        {
            if (chkDefaultWorkspace.Checked)
            {
                txtWorkspaceSubSite.Enabled = false;
            }
            else
            {
                txtWorkspaceSubSite.Enabled = true;
            }
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void btnAddIssue_Click(object sender, EventArgs e)
        {
            //First find the Issues list in the site collection for the new project
            // siteCollection will fidn the collection for http://servename/pwa
            SPSite siteCollection = new SPSite(txtProjectServerUrl.Text + txtProjectName.Text);
            
            // sites will hold collection of sites under pwa - which will be he workspaces
            SPWebCollection sites = siteCollection.AllWebs;

            // lists will be the collection of lists in the specific site with the project name we just created
            SPListCollection lists = sites[txtProjectName.Text].Lists;
            
            // and finally list will be the Issues list for this site
            SPList list = lists["Issues"];

            // create a list collection
            SPListItemCollection listItems = list.Items;

            // add a new item
            SPListItem item = listItems.Add();

            // set some properties - in this sample I just set Title
            // but you can set any of the properties of the Project Workspace Issues list
            item["Title"] = "New Issue for Project " + txtProjectName.Text;
            
            // and update - we now have a new issue
            item.Update();
            
            // get some properties of the new item we will need later
            int itemTPID = item.ID;
            Guid listGuid = list.ID;
            string listName = list.Title.ToString();

            //  This code is basicall from the SDK OLP example but using the properties 
            // of the project task and items created earlier in this sample
            Guid taskWebObjectUid = Guid.Empty;
            WebSvcObjectLinkProvider.ObjectLinkProviderDataSet dsLinkedObjects =
                objectLinkProvider.ReadTaskWebObject(taskGuid);
            int numTaskWebObjects = dsLinkedObjects.WebObjects.Count;
            // In this sample there will be no existing objects - but would be useful in other scenarios
            if (numTaskWebObjects > 0)
                taskWebObjectUid = dsLinkedObjects.WebObjects[0].WOBJ_UID;
            

            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 = taskGuid;
            taskRow.WOBJ_PROJ_UID = projectGuid;
            taskRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Task;
            dsTask.WebObjects.AddWebObjectsRow(taskRow);

            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 = listGuid;
            listItemRow.WOBJ_PROJ_UID = projectGuid;
            
            // I left the switch in but in this sample it will always be an issue
            switch (listName)
            {
                case "Issues":
                    listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Issue;
                    webObjectType = PSLibrary.WebObjectType.Issue;
                    linkedItems = "Issues found for task: " + taskGuid;
                    break;
                case "Risks":
                    listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Risk;
                    webObjectType = PSLibrary.WebObjectType.Risk;
                    linkedItems = "Risks found for task: " + taskGuid;
                    break;
                case "Documents":
                    listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Document;
                    webObjectType = PSLibrary.WebObjectType.Document;
                    linkedItems = "Documents found for task: " + taskGuid;
                    break;
                case "Commitments":  // Commitments are now called Deliverables
                    listItemRow.WOBJ_TYPE = (int)PSLibrary.WebObjectDatabaseType.Commitment;
                    webObjectType = PSLibrary.WebObjectType.Commitment;
                    linkedItems = "Deliverables found for task: " + taskGuid;
                    break;
                default:
                    string errMess = listName +
                        " is not a default SharePoint list type for task links.";
                    throw new SystemException(errMess);
            }
            dsListItems.WebObjects.AddWebObjectsRow(listItemRow);

            WebSvcObjectLinkProvider.WebObjectLinkType generalLinkType =
    WebSvcObjectLinkProvider.WebObjectLinkType.General;
            WebSvcObjectLinkProvider.WebObjectLinkType[] wssLinkTypeArray = 
    { generalLinkType };

            objectLinkProvider.CreateWebObjectLinks(dsTask, dsListItems, wssLinkTypeArray);


            
        }
    }
}
Technorati Tags: Project Server 2007
Attachment: LoginDemoWithIssues.zip
Leave a Comment
  • Please add 2 and 2 and type the answer here:
  • Post
  • Hi Brian,

    What did I miss?  Why not just create a WSS template with the additional issue list.  I understand your comments re notes vs WSS lists, but is the code here just to show how you can do this programatically?

    Perhaps its too late in the night to fully comprehend?

  • Hi Ben,

    I probably didn't explain very well - sorry. I understoood it perfectly when I wrote it :).  The code does not contain another issues list - but creates a new issue in the existing list.  In this sample the same could have been acheived by having the issue in the list in the template as you point out - but really this was just an easy way for me to show the code and re-use the LoginDemo.  The basic idea is to show both how to push items into the list programmatically and then to use the Object Link Provider code to link the issue to the task/project etc.

  • Hi Brian, thanks for the explanation, I just wanted to make sure I wasn't missing a trick.

  • MS has indicated the additional column in Issues and Risks will not be updated by RDB.

    What is the best way to do so? Subscribe to RDB events and update with code?

    Is the best practice to create WssRisk and WssIssue table of your own or just extend the schema?

  • Good help. When I click Add a Issue I am getting the following error.

    System.IO.FileNotFoundException was unhandled

     Message="Could not load file or assembly 'Microsoft.SharePoint.Library, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The system cannot find the file specified."

     Source="LoginDemo"

     FileName="Microsoft.SharePoint.Library, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"

  • Hi Arun,

    Did the refeence load OK?

    "For the Object Link Provider example you need to add a reference and using statement for Microsoft.SharePoint and a Web Reference for the PSI Object Link Provider web service"

    Best regards,

    Brian

Page 1 of 1 (6 items)