For at least 9 years I worked on a project called mpFx, which is just marketing-speak for a library that encapsulated the Project Server Interface to ease Project Server development. It started out open source and then shipped with a product later on as an internal asset.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public static CloudClient Connect() { try { ServerOptions serverOptions = new ServerOptions { Url = ConfigurationExtensions.Get(@"POLInstance"), UserName = ConfigurationExtensions.Get(@"UserName"), Password = ConfigurationExtensions.Get(@"Password").ToSecureString(), DefaultTimeoutInMilliseconds = ConfigurationExtensions.Get(@"Timeout"), MaxRetryCount = ConfigurationExtensions.Get(@"MaxRetryCount"), Scope = ServerOptions.Union(Scope.ProjectCSOM, Scope.SharePointCSOM, Scope.ProjectOData) }; CloudClient cloudClient = new CloudClient(serverOptions); cloudClient.OnConnectionStatusChanged += CloudClient_OnConnectionStatusChanged cloudClient.OnStatusChanged += CloudClient_OnStatusChanged; cloudClient.OnError += CloudClient_OnError; cloudClient.Connect(); return cloudClient; } catch (ServerException serverException) { DebugFx.WriteLine(serverException.GetMessage()); } catch (CloudClientException exception) { DebugFx.WriteLine(exception.CreateMessage(false)); } catch (Exception exception) { DebugFx.WriteLine(exception.GetType().Name); DebugFx.WriteLine(exception.CreateMessage(false)); } throw new CloudClientException(Resources.ERROR_FAILED_TO_CONNECT); } |
Another unit test enumerates through the projects and tasks. In this case I am doing some additional processing on a project called “CTA Test I”, which is where we will dive into my “how-to” for this post.
How-To Read Text Custom Fields Backed by a Lookup Table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
[TestMethod] [Localizable(false)] public void EnumerateProjectTasks() { ClientTester.TestContext = TestContext; using (CloudClient client = ClientTester.Connect()) { Assert.IsTrue(client != null); foreach (KeyValuePair<Guid, PSProject> kvp in client.PublishedProjects) { PSProject project = kvp.Value; if (!project.Name.Equals("CTA Test I")) { continue; } ClientTester.WriteLine("Project Name: " + project.Name); foreach (PublishedTask task in project.Tasks) { ClientTester.WriteLine(DebugFx.Tab + "Task Name: " + task.Name); ClientTester.WriteLine(DebugFx.Tab + "Task Start: " + task.Start); ClientTester.WriteLine(DebugFx.Tab + "Task Finish: " + task.Finish); ClientTester.WriteLine(DebugFx.Tab + "-------------------------------------------"); ClientTester.WriteLine("Custom Field Data"); client.PSLoad(task); foreach (CustomField customField in task.CustomFields) { if (task[customField.InternalName] == null) { continue; } ClientTester.WriteLine(DebugFx.Tab + "Custom Field Name: " + customField.Name); ClientTester.WriteLine(DebugFx.Tab + "Custom Field Type: " + customField.FieldType); switch (customField.FieldType) { case CustomFieldType.COST: case CustomFieldType.DATE: case CustomFieldType.FINISHDATE: case CustomFieldType.DURATION: case CustomFieldType.FLAG: case CustomFieldType.NUMBER: ClientTester.WriteLine(DebugFx.Tab + "Custom Field Value: " + task[customField.InternalName]); break; case CustomFieldType.TEXT: bool isList; object result = client.CustomFields.GetTaskValue(task, customField, out isList); if (isList) { foreach (string value in result as List) { ClientTester.WriteLine(DebugFx.Tab + "Custom Field Value: " + value); } } else { ClientTester.WriteLine(DebugFx.Tab + "Custom Field Value: " + result); } break; default: throw new ArgumentOutOfRangeException(); } } } } Assert.IsTrue(client.PublishedProjects != null); client.Close(); } } |
The situation is this: I have two custom fields backed by lookup fields. One allows for more than one selection, and another allows a single selection. I call client.PSLoad(task); which is a shortcut for LoadQuery and Execute, which in a production application would use generics to include just what you need, like this:
1 2 3 4 5 6 7 8 9 10 11 |
_CloudClient.PSContext.Load(PublishedProject.Tasks, tasks => tasks.Include(t => t.Name, t => t.Start, t => t.Finish, t => t.TaskType, t => t.Predecessors, t => t.Successors, t => t.IsSubProject, t => t.CustomFields)); _CloudClient.PSContext.ExecuteQuery(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
object result = client.CustomFields.GetTaskValue(task, customField, out isList); public object GetTaskValue(PublishedTask task, CustomField customField, out bool isList) { if (task == null) { throw new ArgumentNullException(nameof(task)); } if (customField == null) { throw new ArgumentNullException(nameof(customField)); } isList = false; if (!(task[customField.InternalName] is string[])) { return task[customField.InternalName]; } if (!customField.IsObjectPropertyInstantiated(@"LookupEntries")) { CloudClient.PSLoad(customField.LookupEntries); } string[] values = task[customField.InternalName] as string[]; if (values.Length == 1) { LookupEntry lookupEntry = customField.LookupEntries.FirstOrDefault(e => e.InternalName.Equals(values[0])); if (lookupEntry != null) { return lookupEntry.FullValue; } throw new InvalidOperationException(Resources.ERROR_INVALID_CF_STATE_LT_MISSING_ENTRY); } List valueList = new List(); foreach (string entry in values) { LookupEntry lookupEntry = customField.LookupEntries.FirstOrDefault(e=>e.InternalName.Equals(entry)); if (lookupEntry == null) { throw new InvalidOperationException(Resources.ERROR_INVALID_CF_STATE_LT_MISSING_ENTRY); } valueList.Add(lookupEntry.FullValue); } isList = true; return valueList |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
:Creating connection to Project Server Online: 'https://server/sites/pwa' :Project Server Online Version = 16.0.6420.1203 :Connected to Project Server Online Reporting Feed :Connected to SharePoint Online :SharePoint Server Online Version = 16.0.6420.1203 :Project Name: CTA Test I :Loading Tasks :Task Name: Define process for gathering requirements :Task Start: 5/2/2017 8:00:00 AM :Task Finish: 5/2/2017 5:00:00 PM :------------------------------------------- :Custom Field Data :------------------------------------------- :Custom Field Name: CTA Task Test Field Cost :Custom Field Type: COST :Custom Field Value: 320.000000 :Custom Field Name: CTA Task Test Field Date :Custom Field Type: DATE :Custom Field Value: 10/10/2010 8:00:00 AM :Custom Field Name: CTA Task Test Field Flag :Custom Field Type: FLAG :Custom Field Value: True <strong>-- Multiple Value field: :Custom Field Name: CTA Task Test Field Lookup :Custom Field Type: TEXT :Custom Field Value: Brazil :Custom Field Value: Canada :Custom Field Value: Denmark :Custom Field Value: Germany :Custom Field Value: Italy :Custom Field Value: Sweden -- Single Value field: :Custom Field Name: CTA Task Test Field Lookup Single Lookup :Custom Field Type: TEXT :Custom Field Value: Denmark </strong> |
Okay, that’s it for this evening.[/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]