Creating Issues and Linking to Tasks. Better than task notes?
23 June 2007There 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() + “nnLog 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
Originally by BriSmith from Brian Smith’s Project Support WebLog on June 22, 2007, 6:46pm
Popularity: 44% [?]
If you enjoyed this post, make sure you subscribe to my RSS feed!
Other posts like this one:


3 Responses to “Creating Issues and Linking to Tasks. Better than task notes?”
October 7th, 2008 at 5:41 pm
Hi!
I want to make better my SQL knowledge.
I red really many SQL books and want to
read more about SQL for my position as db2 database manager.
What would you recommend?
Thanks,
Werutz
December 22nd, 2008 at 8:27 am
Can I use this to link an issue to a risk? I’ve been playing with this for a while and while it seems to run without an error, the link doesn’t show up in Sharepoint.
August 13th, 2009 at 10:43 am
Interesting,
You sample is so clear,I learned a lot from it
Anyway, thanks for the post