This is now the second part of my short tutorials how to interact with the Project Server 2010 over the PSI. Here is the link to the first part, if you missed it:
http://easyguet.ch/blog/crudpsi/
In this article, I’m going to show how to…
with C# .NET 4.0!
Please follow the steps in the first part or just download the demo project here.
Now you should have at least three Web service references in your project. But to write and update custom fields, we need one more:
We need the WebSvcQueueSystem service to track our tasks inside the project server. We could also just wait 4 seconds and hope it has finished. But this is not very professional.
The URL to the specified service looks like this:
http://ServerName/ProjectServerName/_vti_bin/PSI/QueueSystem.asmx?wsdl
Now we are ready to add and update custom fields of our projects. But why do we need to “add” custom fields? The reason is simple:
Every project has a list (or table) of custom fields. Those are defined as Enterprise Custom Fields in the settings of the Project Server Web interface. But as long as they don’t have a content, the row of the custom field will not be available at the custom field list of the project. With the result that we have to check if the field we want to write into is already created or not, and then either update an existing row or create a new one!
Great, we are ready to develop our first custom field update example! We’re going to update a simple text field called “CompanyName”. I’ve created a simple flow chart to illustrate what we are going to do.
First of all we have to define our example custom field. Every custom field needs to be identified by it’s enterprise id. You find those id’s in the settings of the Project Server under “System Identification Data”:
Besides the id there are also a name and a value we want to write. I’ve created three variables to store this information. In a real project, this data would come from SAP or something like that:
//our example custom field string cfName = "CompanyName"; string cfValue = "geeklife"; Guid cfId = new Guid("db92fa3c-5eda-4d24-87db-40ca11bf4d4e");
After that we are going to read the project data set of our project as in the last post:
//reading all projects into a dataset ProjectDataSet projectList = projectSvc.ReadProjectList(); Guid myProjectUid = projectList.Project[5].PROJ_UID; ProjectDataSet myProject = projectSvc.ReadProject(myProjectUid, DataStoreEnum.WorkingStore);
The project data set contains a data table of all filled custom fields related to this project. If we iterate through it and don’t find our row, we have to create the row our self. I’m going to use a Boolean to indicate if the row has been found. If the required row is there, we are able to update it. But be aware, till now everything happens locally on the data set we’ve downloaded from the Project Server API and nothing has been changed on the server yet.
//indicator if the custom field has been found bool customFieldFound = false; foreach (ProjectDataSet.ProjectCustomFieldsRow cfRow in myProject.ProjectCustomFields) { if (cfRow.MD_PROP_UID == cfId) { cfRow.TEXT_VALUE = cfValue; customFieldFound = true; } }
Now it is time to check if the custom field has been found and updated, or if we have to create it. To create it, we just need to initialize a new row and add it to the custom field data table:
//check if the custom field has been found if (!customFieldFound) { ProjectDataSet.ProjectCustomFieldsRow cfRow = myProject.ProjectCustomFields.NewProjectCustomFieldsRow(); cfRow.SetNUM_VALUENull(); cfRow.SetFLAG_VALUENull(); cfRow.SetDUR_VALUENull(); cfRow.SetDUR_FMTNull(); cfRow.SetDATE_VALUENull(); cfRow.SetCODE_VALUENull(); cfRow.SetTEXT_VALUENull(); cfRow.MD_PROP_UID = cfId; cfRow.CUSTOM_FIELD_UID = Guid.NewGuid(); cfRow.PROJ_UID = myProjectUid; cfRow.FIELD_TYPE_ENUM = 21; cfRow.TEXT_VALUE = Convert.ToString(cfValue); myProject.ProjectCustomFields.AddProjectCustomFieldsRow(cfRow); }
After updating our local data set to the level we want, we have to write it back to the Project Server. To do this, we need a unique session id as well as some job id’s to track our jobs. With the project server web service we are able to perform the checkout directly:
//generate unique id for tracking Guid sessionId = Guid.NewGuid(); Guid jobId = Guid.NewGuid(); projectSvc.CheckOutProject(myProjectUid,sessionId, "custom field update checkout");
Now we have to merge our data set with the online version. The project server web service provides again a direct function for this case:
//update project bool validateOnly = false; projectSvc.QueueUpdateProject(jobId, sessionId, myProject, validateOnly); WaitForJob(jobId);
As you can see there is a function to wait for the job called “WaitForJob”. This function handles the waiting and will be explained in another post (Till then, you find it at the end of this post!).
At least we have to check in our project, which is, surprisingly, again provided by a web service function:
//create a new job id jobId = Guid.NewGuid(); bool force = false; string sessionDescription = "updated custom fields"; projectSvc.QueueCheckInProject(jobId, myProjectUid, force, sessionId, sessionDescription); WaitForJob(jobId);
Great! We’re done!
Now you are able to write data into custom fields which do not have reference tables. But if you wan’t to write progress indicator like traffic signals back to the Project Server, you have to get the specified Id of this value and write this Id.
To lookup this value, I’ve developed a function which requires the custom field guid and the value as a string:
/// <summary> public Guid LookUpValueIdByName(Guid fieldId, string nameValue) { CustomFieldDataSet cfResults = customfieldSvc.ReadCustomFieldsByMdPropUids(new Guid[] { fieldId }, false); Guid lookupUid = Guid.NewGuid(); CustomFieldDataSet.CustomFieldsRow cf = null; if (cfResults.Tables[0].Rows.Count > 0) { cf = cfResults.CustomFields[0]; lookupUid = cf.MD_LOOKUP_TABLE_UID; } LookupTableDataSet lookupset = loockuptableSvc.ReadLookupTablesByUids(new Guid[] { lookupUid }, false, 1031); LookupTableDataSet.LookupTableTreesRow treeRow = lookupset.LookupTableTrees.Single(tr => tr.LT_VALUE_TEXT == nameValue); return treeRow.LT_STRUCT_UID; }
The FileTypeEnum for lookup tables is 21 as the string type. All other types are listed here.
cfRow.FIELD_TYPE_ENUM = 21;
Now you are able to write every field type!
Update 07/25/2013
I’ve added the “WaitForJob” method for sharon:
/// <summary> public void WaitForJob(Guid jobId) { JobState jobState; const int QUEUE_WAIT_TIME = 2; bool jobDone = false; string xmlError = string.Empty; int wait = 0; wait = queuesystemSvc.GetJobWaitTime(jobId); Thread.Sleep(wait * 1000); do { jobState = queuesystemSvc.GetJobCompletionState(out xmlError, jobId); if (jobState == JobState.Success) { jobDone = true; } else { if (jobState == JobState.Unknown || jobState == JobState.Failed || jobState == JobState.FailedNotBlocking || jobState == JobState.CorrelationBlocked || jobState == JobState.Canceled) { Console.WriteLine("Queue request failed "" + jobState + "" Job ID: " + jobId + "."); throw new Exception("Queue request failed "" + jobState + "" Job ID: " + jobId + "."); } else { Console.WriteLine("Job State: " + jobState + " Job ID: " + jobId); Thread.Sleep(QUEUE_WAIT_TIME * 1000); } } } while (!jobDone); }
—
Copyright © 2015 easyguet.ch
sharon - 10 years ago
hi, can you please post the WaitForJob function ?
thanks.
florian - 10 years ago
I’ve added the method for you!
sharon - 10 years ago
thanks :)
Usman Sajeel Haider - 10 years ago
Good article, easy to follow through. The field type enum reference is missing, I guess you forgot to add it. Here is the link:
http://msdn.microsoft.com/en-us/library/microsoft.office.project.server.library.psdatatype(v=office.12).aspx
Again, good job!
Brent - 10 years ago
Can someone help by giving an example of how the above example can be used to update multiple Custom Fields? I’m new to Project Server development and was wondering what modifications need to be made to this. I have everything work until I try to update multiple Fields.
Brent - 10 years ago
Please disregard. I figured out how to do this. Another issue was preventing the update multiple Custom Fields
Frank - 10 years ago
Hello,
every time i get a NullReferenceException Error at this section
wait = queuesystemSvc.GetJobWaitTime(jobId);
Can you
Your Tutorial is great, Thanks!
Frank - 10 years ago
Fixed this Problem.
it happened because i used the define Webservices from the previous site.
But i have another question.
Is it possible to add this CustomField value to a Specific Task where Outline Level is 1?
Brent - 9 years ago
http://msdn.microsoft.com/en-us/library/office/gg204248(v=office.15).aspx
Aniruddha Surange - 9 years ago
Great Article…I have one issue…the updated custom field value shows in ProjectDataSet but the updated value do not show up when the Project is viewed in PWA.
florian - 9 years ago
Thank you, have you updated the dataset back to the project server and checked it in?
Sandeep - 9 years ago
Hi,
I want to create a new custom field with NULL value.
Is it possible ?
Shari - 9 years ago
Hi,
I have a requirement to check whether a “column” is empty or contains a url based on a flag. But i dont know the name of the “column”. How do i iterate through the ustomFieldDataSet.CustomFieldsRow to know whether the url is present or not???
Audrine - 9 years ago
Hi,
I am trying to add multiple new custom rows, however I always get an error CustomFieldRowAlreadyExists. Can you please help me with this.
florian - 8 years ago
Hi Audrine, have you checked that the row does not already exist? You only have to create a new row if it was never created before. Regards Florian