I am taking a little break from my current project. Whew. Only such much you can cram into your brain about SharePoint in one unbroken period of time.
Today’s topic starts at the beginning of mpFx: How to create a ProjectServer object. I will also demonstrate enumerating project information. The code sample and an update to the core mpFx library are available on Code Gallery. Here is the updated mpFx core library and here is the walkthrough.
The following references are required to build and execute:
NOTE: mpFx 1.0 PREVIEW is intended to be a learning aide. There are problems with parts of the code related to resource acquisition and clean up, performance, and generally some design decisions I made well before I had a full understanding of what the PSI was up to. I am slowly purging those areas that need purging…
NOTE II: Parts of the PREVIEW edition implement IDisposable where it isn’t really a benefit. DataSets implement IDispose as part of MarshalByValueComponent, but the source of IDisposable in this case is IComponent! It is recommended that you call Dispose on any object the implements it, but in this case I can’t see any reason to. No resources are freed as part of the call, as far as I can tell.
Okay, on to the sample!
Let’s begin with the sample:
1: using System;
2: using System.IO;
3: using Mcs.Epm.MicrosoftProject.mpFx;
4: using Mcs.Epm.MicrosoftProject.mpFx.ProjectsWebService;
5:
6: namespace CreateProjectServerObject
7: {
8: class Program
9: {
10: static void Main()
11: {
12: /* There are four constructors for the ProjectServer object. This demo describes
13: * the constructor most commonly used in my projects:
14: *
15: * ProjectServer(string projectServerUrl, DataStoreEnum store, ILog log)
16: *
17: * The constructor takes three parameters:
18: *
19: * 1.) projectServerUrl: The full path to the Project Server PWA site
20: * 2.) DataStorEnum: The store on which operations will be performed
21: * 3.) ILog: An object implementing the ILog interface, which
22: * performs logging operations.
23: *
24: * At the time of writing, a single class implements ILog and implements
25: * logging to the file system. Other classes might implement ILog and
26: * persist events to the Event Log or a database.
27: */
28:
29: string logDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MpFx");
30:
31: try
32: {
33: using (Log log = new Log(logDirectory, "Demo", LogPeriod.Hourly, true))
34: using (ProjectServer projectServer = new ProjectServer("http://projectserver/pwa", DataStoreEnum.WorkingStore, log))
35: {
36: ProjectCollection projects = projectServer.Projects;
37:
38: foreach (EnterpriseProject project in projects)
39: {
40: string projectInformation = string.Format("Project Name: {0} - Start Date: {1}",
41: project.Name,
42: EnterpriseProject.StandardInfo(project).PROJ_INFO_START_DATE);
43:
44: Console.WriteLine(projectInformation);
45: }
46: }
47: }
48: catch (MpFxException exception)
49: {
50: Console.Write(Errors.ProcessMpFxException(exception));
51: }
52: catch (Exception exception)
53: {
54: Console.Write(exception.Message);
55: }
56:
57: Console.WriteLine("Projects enumerated Press any key to close.");
58: Console.ReadKey();
59: }
60: }
61: }
We will skip the details on logging for this post. To begin, let’s look at one of the four constructors implemented on ProjectServer (I chose the one I most commonly use). In the sample above, the constructor is called on line 34. Here is the constructor source:
1: public ProjectServer(string projectServerUrl, DataStoreEnum store, ILog log)
2: {
3: try
4: {
5: Log = log;
6:
7: if (!Utilities.IsValidUrl(projectServerUrl))
8: {
9: throw new ArgumentException(LibraryResources.InvalidProjectServerUrl);
10: }
11:
12: Settings = new ProjectServerSettings();
13: WebServices = new WebServices(this);
14: Site = new Uri(projectServerUrl);
15: Store = store;
16:
17: NetworkCredential = CredentialCache.DefaultNetworkCredentials;
18: AuthenticationType = AuthenticationType.Windows;
19:
20: WebServices.LoginWindows = new LoginWindows();
21:
22: WebServices.LoginWindows.Url = WebServices.AppendPath(projectServerUrl, ServicePaths.WindowsLoginService);
23: WebServices.LoginWindows.UseDefaultCredentials = true;
24:
25: WriteLogEntry(LogArea.Constructor,
26: LogEntryType.Information,
27: string.Format(LibraryResources.LogConstructor, AuthenticationType, projectServerUrl));
29: WebServices.LoginWindows.Login();
31: Settings.ListSeparator = WebServices.Projects.ReadServerListSeparator();
32:
33: }
34: catch (SoapException exception)
36: throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
37: }
38: catch (ArgumentException exception)
40: throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
41: }
42: catch (Exception exception)
43: {
44: throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
Here are a few elaborations and considerations:
1: /// <summary>
2: /// Create a project in Project Server.
3: /// </summary>
4: /// <param name="project">Project</param>
5: /// <param name="validateOnly">Indicates whether the operation should validate only or perform the creation</param>
6: /// <param name="wait">Indicates whether the call should wait on the queued job.</param>
7: /// <returns>Queue job GUID</returns>
8: public Guid Create(ProjectDataSet project, bool validateOnly, bool wait)
10: try
12: Guid jobGuid = Guid.NewGuid();
13:
14: Parent.WebServices.Projects.QueueCreateProject(jobGuid, project, validateOnly);
15:
16: if (wait)
17: {
18: string errorMessage;
20: Parent.Queue.WaitOnJobStatus(jobGuid,
21: JobState.Success,
22: Parent.Settings.QueueStatusRetryCount,
23: Parent.Settings.QueueStatusSleepDuration,
24: out errorMessage);
25:
26: MpFxException.ThrowIfError(errorMessage, Parent.Log, LogArea.CreateProject, LogEntryType.Error);
27:
28: }
29:
30: return jobGuid;
31:
32: }
33: catch (SoapException exception)
34: {
35: throw MpFxException.Create(exception, Parent.Log, LogArea.CreateProject, LogEntryType.Error);
36: }
37: catch (Exception exception)
38: {
39: throw MpFxException.Create(exception, Parent.Log, LogArea.CreateProject, LogEntryType.Error);
40: }
Returning to the sample:
The Projects property returns a ProjectCollection object. Looking at the property implementation uncovers another design decision:
1: public ProjectCollection Projects
3: get
5: if (_ProjectCollection == null)
6: {
7: _ProjectCollection = new ProjectCollection(this);
8: }
9:
10: return _ProjectCollection;
11: }
12: }
The lazy load pattern is widely employed (with various degrees of success and cleanliness!) throughout mpFx. Examining the internals of the next line of code further illustrates this point:
Let’s take a look at the enumerator implementation. First, the ProjectCollection implements two IEnumerables:
1: public class ProjectCollection : IEnumerable<EnterpriseProject>, IEnumerable<Guid>
The first enumerates EnterpriseProjects (we will get to this later) and the second enumerates Guids. Let’s take a look at the EnterpriseProject enumerator, which is the one used in the sample:
1: public IEnumerator<EnterpriseProject> GetEnumerator()
3: return ((IEnumerable<EnterpriseProject>)this).GetEnumerator();
4: }
And:
1: IEnumerator<EnterpriseProject> IEnumerable<EnterpriseProject>.GetEnumerator()
3: if (_projectsCollection == null)
5: LoadProjectCollection();
6: }
7:
8: ThrowLoadCollectionLoadException();
10: foreach (KeyValuePair<Guid, EnterpriseProject> pair in _projectsCollection)
12: yield return pair.Value;
13: }
14: }
Note that the backing field is first checked for nullness. If it is null, the project collection is loaded:
1: internal void LoadProjectCollection()
5: _projectsCollection = new Dictionary<Guid, EnterpriseProject>();
7: else
9: _projectsCollection.Clear();
12: using (ProjectDataSet projectDataSet = Parent.WebServices.Projects.ReadProjectStatus(Guid.Empty, Parent.Store, string.Empty, (int)Project.ProjectType.Project))
13: {
14: foreach (ProjectDataSet.ProjectRow project in projectDataSet.Project.Rows)
15: {
16: if (project.PROJ_UID != Guid.Empty)
18: _projectsCollection.Add(project.PROJ_UID, new EnterpriseProject(this, project.PROJ_UID, project.PROJ_NAME, Parent.Store));
19: }
20: }
21: }
22: }
Pretty straightforward so far. The ProjectCollection contains EnterpriseProjects. Rather than build a full model of all of the ProjectDataSet.ProjectRow members, it exposes data and helper methods. See the class diagram to the left.
The properties and methods that are currently implement reflect the learning requirements I experienced while developing mpFx in the early days. Essentially, every time I needed to read or act on project data in a new way, I would implement a property or method appropriate to the requirement.
The project name is one of a few properties that wrap elements of the ProjectDataSet.ProjectRow data row class. The helper method EnterpriseProject.StandardInfo is short hand for project.StandardInformation.Project[0]. Accessing the StandardInformation property causes a call to be made to Project Server:
1: public ProjectDataSet StandardInformation
5: return Parent.Parent.WebServices.Projects.ReadProjectEntities(ProjectGuid, (int)ProjectEntityType.Project, Parent.Parent.Store);
7: }
That’s pretty much it!
More later. Back to SharePoint.