Update Project Server 2010 CustomFields over PSI

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…

  • …check out a project
  • …edit project data
  • …write values into custom fields
  • …check in a project

with C# .NET 4.0!

Setup Web service and Project

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

Add & Update Custom Fields

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!

Example

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”:

000009_csk

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!

Lookup Fields

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!

Source Code Download

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); }

Comments are now closed for this article

  • 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 - 9 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 - 9 years ago

      Please disregard. I figured out how to do this. Another issue was preventing the update multiple Custom Fields

  • Frank - 9 years ago

    Hello,
    every time i get a NullReferenceException Error at this section
    wait = queuesystemSvc.GetJobWaitTime(jobId);
    Can you

    Your Tutorial is great, Thanks!

  • 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 - 8 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

Pings & Trackbacks