Second Life of a Hungarian SharePoint Geek

November 23, 2014

Managing Project Server Enterprise Custom Fields via the Managed Client Object Model

Filed under: ALM, Managed Client OM, Project Server — Tags: , , — Peter Holpar @ 07:39

In the previous post I described, how to manage Project Server lookup tables via the managed client object model. In this current post I  provide you some code snippets that help to manage enterprise custom fields.

As I wrote in a former post, we can deploy Project Server entities declaratively, via SharePoint solution packages (WSP), see that post for the details. An example for enterprise custom field (with and without referencing lookup tables) is included below. In the first part we define a lookup table we refer to later, see the second part for the field definitions:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ProjectServerEntities xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <LookupTables>
  4.     <LookupTable Id="E7397277-1AB0-4096-B2DD-57029A055BA4" Name="YourLookupTable">
  5.       <SortOrder>UserDefined</SortOrder>
  6.       <Mask>
  7.         <MaskLength>0</MaskLength>
  8.         <MaskType>CHARACTERS</MaskType>
  9.         <MaskSeparator>.</MaskSeparator>
  10.       </Mask>
  11.       <LookupEntry>
  12.         <Id>8D3CFDD8-566B-4FA4-8C01-E315BCF13E74</Id>
  13.         <ParentId />
  14.         <SortIndex>86</SortIndex>
  15.         <Value>
  16.           <TextValue>Value1</TextValue>
  17.         </Value>
  18.       </LookupEntry>
  19.       <LookupEntry>
  20.         <Id>8A913280-C8D5-4E85-AE73-526EF5ABC686</Id>
  21.         <ParentId />
  22.         <SortIndex>100</SortIndex>
  23.         <Value>
  24.           <TextValue>Value2</TextValue>
  25.         </Value>
  26.       </LookupEntry>
  27.       <LookupEntry>
  28.         <Id>C89A98F0-795A-49EC-9031-BA892CE0D610</Id>
  29.         <ParentId />
  30.         <SortIndex>114</SortIndex>
  31.         <Value>
  32.           <TextValue>Value3</TextValue>
  33.         </Value>
  34.       </LookupEntry>
  35.     </LookupTable>
  36.   </LookupTables>
  37.  
  38.   <CustomFields>
  39.     <CustomField Name="CustomProjectFieldTextWithLookupValue" Id="7574B64B-F230-4F38-ACB6-1C8E4E3D96DD">
  40.       <FieldType>TEXT</FieldType>
  41.       <EntityType>TaskEntity</EntityType>
  42.       <LookupTableUid>E7397277-1AB0-4096-B2DD-57029A055BA4</LookupTableUid>
  43.       <LookupAllowMultiSelect>false</LookupAllowMultiSelect>
  44.       <LookupDefaultValue></LookupDefaultValue>
  45.       <IsRequired>false</IsRequired>
  46.       <IsWorkflowControlled>false</IsWorkflowControlled>
  47.       <IsMultilineText>false</IsMultilineText>
  48.     </CustomField>
  49.     <CustomField Name="CustomTaskFieldFlag" Id="3ED6B19F-BB55-46B6-B3E0-08E68F56110E">
  50.       <FieldType>FLAG</FieldType>
  51.       <EntityType>ProjectEntity</EntityType>
  52.       <LookupTableUid></LookupTableUid>
  53.       <LookupAllowMultiSelect>false</LookupAllowMultiSelect>
  54.       <LookupDefaultValue></LookupDefaultValue>
  55.       <IsRequired>false</IsRequired>
  56.       <IsWorkflowControlled>false</IsWorkflowControlled>
  57.       <IsMultilineText>false</IsMultilineText>
  58.     </CustomField>
  59.  
  60.   </CustomFields>
  61. </ProjectServerEntities>

In the first code snippet we simply dump out some of the most important information of our enterprise custom fields.

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     projectContext.Load(projectContext.CustomFields,
  4.         cfs => cfs.Include(
  5.             cf => cf.Name,
  6.             cf => cf.Id,
  7.             cf => cf.FieldType,
  8.             cf => cf.LookupTable.Name));
  9.     projectContext.ExecuteQuery();
  10.  
  11.     projectContext.CustomFields.ToList().ForEach(cf =>
  12.     {
  13.         Console.WriteLine("Name [{0}], Id [{1}], FieldType [{2}], LookupTable [{3}]",
  14.             cf.Name, cf.Id, cf.FieldType, (cf.LookupTable.ServerObjectIsNull == true) ? "none" : cf.LookupTable.Name);
  15.     });
  16. }

Note the condition we use to decide if there is a lookup table associated with this field:

cf.LookupTable.ServerObjectIsNull == true

Personally, I don’t like code where one compares a logical variable to true or false instead of using that variable as the condition itself, but the type of the ServerObjectIsNull property is nullable (bool?), and I found a direct comparison simpler as

(cf.LookupTable.ServerObjectIsNull.HasValue) && (cf.LookupTable.ServerObjectIsNull.Value)

Alternatively, you can use the IsPropertyAvailable method for the condition (it returns a bool not bool?) as shown below:

!cf.LookupTable.IsPropertyAvailable("Name")

Note the exclamation mark at the beginning, as we check the opposite as in the case of ServerObjectIsNull property (property is available with IsPropertyAvailable vs. not available in case of ServerObjectIsNull).

The next sample shows how to create a flag-type enterprise custom field for the task entity:

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     var fieldInfo = new CustomFieldCreationInformation
  4.     {
  5.         Id = Guid.NewGuid(),
  6.         Name = "FlagFieldName",
  7.         EntityType = projectContext.EntityTypes.TaskEntity,
  8.         FieldType = CustomFieldType.FLAG,
  9.     };
  10.  
  11.     projectContext.CustomFields.Add(fieldInfo);
  12.     projectContext.CustomFields.Update();
  13.  
  14.     projectContext.ExecuteQuery();
  15. }

You can simply assign a lookup table as well, as illustrated below, where we create a text-type enterprise custom field for the project entity:

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     projectContext.Load(projectContext.LookupTables, lts => lts.Include(lt => lt.Name));
  4.     projectContext.ExecuteQuery();
  5.  
  6.     var lookupTable = projectContext.LookupTables.First(lt => lt.Name == "LookupTableName");
  7.  
  8.     var fieldInfo = new CustomFieldCreationInformation
  9.     {
  10.         Id = Guid.NewGuid(),
  11.         Name = "TextFieldName",
  12.         EntityType = projectContext.EntityTypes.ProjectEntity,
  13.         FieldType = CustomFieldType.TEXT,
  14.         LookupTable = lookupTable
  15.     };
  16.  
  17.     projectContext.CustomFields.Add(fieldInfo);
  18.     projectContext.CustomFields.Update();
  19.  
  20.     projectContext.ExecuteQuery();
  21. }

Note, that we request a list of all lookup tables in a separate batch in this case, the select the right lookup table by name. Of course, if you know the ID (uid or objectId) of your lookup table, you don’t need that extra request, as you can use either

var uidOfTheLookupTable = new Guid("54338ffd-d0fa-e311-83c6-0050a3245643");
var lookupTable = projectContext.LookupTables.GetByGuid(uidOfTheLookupTable);

or

var objectIdOfTheLookupTable = "98238ffd-983a-e431-83c6-00f5e3249834";
var lookupTable = projectContext.LookupTables.GetById(objectIdOfTheLookupTable);

to get the reference directly, instead of a lookup by name.

In the next example, we delete a custom field by name. Again, we use two batches, one for the lookup of the custom field by name, and a second one for the deletion itself. As in the former example, we can reduce the number of batches to one, if we know the ID of the field, and get it either by the GetByGuid or by the GetById method,

  1. using (var projectContext = new ProjectContext(pwaUrl))
  2. {
  3.     projectContext.Load(projectContext.CustomFields, cfs => cfs.Include(cf => cf.Name));
  4.     projectContext.ExecuteQuery();
  5.  
  6.     var custField = projectContext.CustomFields.First(cf => cf.Name == "FieldName1");
  7.  
  8.     custField.DeleteObject();
  9.     projectContext.ExecuteQuery();
  10. }

If you have to delete more fields, you don’t need to send a separate request (or two requests) for each deletion. If you use this last example, you can reduce the number of requests for the field removal:

  1. var fieldNames = new List<string> { "FieldName1", "FieldName2", "FieldName3" };
  2.  
  3. using (var projectContext = new ProjectContext(pwaUrl))
  4. {
  5.     projectContext.Load(projectContext.CustomFields, cfs => cfs.Include(cf => cf.Name));
  6.     projectContext.ExecuteQuery();
  7.  
  8.     // query made case insensitive
  9.     fieldNames.ConvertAll(fn => projectContext.CustomFields
  10.         .FirstOrDefault(cf => cf.Name.ToLower() == fn.ToLower()))
  11.         .Where(fn => fn != null).ToList()
  12.         .ForEach(fn => fn.DeleteObject());
  13.  
  14.     projectContext.ExecuteQuery();
  15. }

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: