開始使用 Project Server CSOM 和 .NET

您可以使用 Project Server 2013 用戶端物件模型 (CSOM) ,使用 .NET Framework 4 開發 Project Online 和內部部署解決方案。 本文說明如何建立使用 CSOM 建立和發佈專案的控制台應用程式。 發佈項目之後,應用程式會等候 Project Server 佇列服務完成發佈動作,然後列出已發佈的專案。

如需 Project Server CSOM 的一般簡介,請參閱 Project 2013 中開發人員的 匯報。 如需 CSOM 命名空間中的參考主題,請參閱 Microsoft.ProjectServer.Client

在 Visual Studio 中建立 CSOM 專案

您可以使用 Visual Studio 2010 或 Visual Studio 2012 來開發使用 Project Server CSOM 的解決方案。 Project Server CSOM 包含三個元件,用於開發用戶端應用程式、Microsoft Silverlight 應用程式,以及使用 .NET Framework 4 Windows Phone 8 個應用程式。 CSOM 也包含用於開發 Web 應用程式的 JavaScript 檔案,如 Microsoft.ProjectServer.Client 中所述。

您可以從 Project Server 電腦或從 Project 2013 SDK 下載將所需的 CSOM 元件複製到遠端開發電腦。 本主題所述的 QueueCreateProject 控制台應用程式不是 Silverlight 應用程式或 Windows Phone 8 應用程式,因此您需要 Microsoft.ProjectServer.Client.dll 元件。 因為 CSOM 與以 WCF 或 ASMX 為基礎的 Project Server 介面 (PSI) 無關,所以您不需要設定 PSI 的服務參考或使用 Microsoft.Office.Project.Server.Library 命名空間。

QueueCreateProject 應用程式會使用命令行自變數作為要建立的項目名稱,並針對佇列逾時限制使用 。 在程式 1 中,您會建立基本控制台應用程式、新增例程來剖析命令行,並在命令行發生錯誤時新增使用訊息。

程式1。 在 Visual Studio 中建立 CSOM 專案

  1. 將 Microsoft.ProjectServer.Client.dll 元件從 %ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\ 資料夾複製到您的開發電腦。 將元件複製到您將使用之其他 Project Server 和 SharePoint 參考元件的便利資料夾,例如 C:\Project\Assemblies

  2. 將 Microsoft.SharePoint.Client.dll 元件和 Microsoft.SharePoint.Client.Runtime.dll 元件從相同的源數據夾複製到您的開發計算機。 Microsoft.ProjectServer.Client.dll 元件相依於相關的 SharePoint 元件。

  3. 在 Visual Studio 中,建立 Windows 控制台應用程式,並將目標架構設定為 .NET Framework 4。 例如,將應用程式命名為 QueueCreateProject。

    注意事項

    如果您忘記設定正確的目標,在 Visual Studio 建立項目之後,請在 [專案] 功能表中開啟 QueueCreateProject 屬性。 在 [應用程式] 索引標籤的 [目標架構] 下拉式清單中,選擇 [.NET Framework 4]。 請勿使用 .NET Framework 4 用戶端設定檔

  4. 在 方案總管 中,設定下列元件的參考:

    • Microsoft.ProjectServer.Client.dll
    • Microsoft.SharePoint.Client.dll
    • Microsoft.SharePoint.Client.Runtime.dll
  5. 在Program.cs檔案中,編輯 語 using 句,如下所示。

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using Microsoft.ProjectServer.Client;
    
  6. 新增方法來剖析專案名稱的命令行自變數和佇列逾時的秒數、顯示使用方式資訊,以及結束應用程式。 以下列程式代碼取代 Program.cs 檔案中的程式代碼主體。

     namespace QueueCreateProject
     {
         class Program
         {
             static void Main(string[] args)
             {
                 if (!ParseCommandLine(args))
                 {
                     Usage();
                     ExitApp();
                 }
                 /* Add calls to methods here to get the project context and create a project. */
                 ExitApp();
             }
             // Parse the command line. Return true if there are no errors.
             private static bool ParseCommandLine(string[] args)
             {
                 bool error = false;
                 int argsLen = args.Length;
                 try
                 {
                     for (int i = 0; i < argsLen; i++)
                     {
                         if (error) break;
                         if (args[i].StartsWith("-") || args[i].StartsWith("/"))
                             args[i] = "*" + args[i].Substring(1).ToLower();
                         switch (args[i])
                         {
                             case "*projname":
                             case "*n":
                                 if (++i >= argsLen) return false;
                                 projName = args[i];
                                 break;
                             case "*timeout":
                             case "*t":
                                 if (++i >= argsLen) return false;
                                 timeoutSeconds = Convert.ToInt32(args[i]);
                                 break;
                             case "*?":
                             default:
                                 error = true;
                                 break;
                         }
                     }
                 }
                 catch (FormatException)
                 {
                     error = true;
                 }
                 if (string.IsNullOrEmpty(projName)) error = true;
                 return !error;
             }
             private static void Usage()
             {
                 string example = "Usage: QueueCreateProject -projName | -n \"New project name\" [-timeout | -t sec]";
                 example += "\nExample: QueueCreateProject -n \"My new project\"";
                 example += "\nDefault timeout seconds = " + timeoutSeconds.ToString();
                 Console.WriteLine(example);
             }
             private static void ExitApp()
             {
                 Console.Write("\nPress any key to exit... ");
                 Console.ReadKey(true);
                 Environment.Exit(0);
             }
         }
     }
    

取得項目內容

CSOM 開發需要使用 Project Web App URL 初始化 ProjectContext 物件。 程式 2 中的程式代碼會使用 pwaPath 常數。 如果您打算將應用程式用於多個 Project Web App 實例,您可以將 pwaPath 設為變數,並新增另一個命令行自變數。

程式 2. 取得項目內容

  1. 新增 QueueCreateProject 應用程式將使用的 Program 類別常數和變數。 除了 Project Web App URL 之外,應用程式還會使用預設企業專案類型的名稱 (EPT) 、要建立的項目名稱,以及以秒為單位的最大佇列逾時。 在此情況下, timeoutSeconds 變數可讓您測試逾時的各種值如何影響應用程式。 ProjectContext 對像是存取 CSOM 的主要物件。

     private const string pwaPath = "https://ServerName /pwa/"; // Change the path to your Project Web App instance.
     private static string basicEpt = "Enterprise Project";   // Basic enterprise project type.
     private static string projName = string.Empty;
     private static int timeoutSeconds = 10;  // The maximum wait time for a queue job, in seconds.
     private static ProjectContext projContext;
    
  2. /* Add calls to methods here to get the project context and create a project. */以下列程序代碼取代 批注。 Microsoft.ProjectServer.Client.ProjectContext 對像是使用 Project Web App URL 初始化。 CreateTestProject 方法和 ListPublishedProjects 方法會顯示在程式 4 和程式 5 中。

     projContext = new ProjectContext(pwaPath);
     if (CreateTestProject())
         ListPublishedProjects();
     else
         Console.WriteLine("\nProject creation failed: {0}", projName);
    

取得企業項目類型

QueueCreateProject 範例應用程式會明確選取企業專案 EPT,以顯示應用程式如何選取專案的 EPT。 如果專案建立資訊未指定 EPT GUID,則應用程式會使用預設 EPT。 GetEptUid 方法是由程式 4 中所述的 CreateTestProject 方法使用。

GetEptUid 方法會查詢 ProjectContext 物件的 EnterpriseProjectTypes 集合,其中 EPT 名稱等於指定的名稱。 執行查詢之後,eptUid 變數會設定為 eptList 集合中第一個 EnterpriseProjectType 物件的 GUID。 因為 EPT 名稱是唯一的,所以只有一個 EnterpriseProjectType 物件具有指定的名稱。

程式 3. 取得新專案之 EPT 的 GUID

  • GetEptUid 方法新增至 Program 類別。

      // Get the GUID of the specified enterprise project type.
      private static Guid GetEptUid(string eptName)
      {
          Guid eptUid = Guid.Empty;
          try
          {
              // Get the list of EPTs that have the specified name. 
              // If the EPT name exists, the list will contain only one EPT.
              var eptList = projContext.LoadQuery(
                  projContext.EnterpriseProjectTypes.Where(
                      ept => ept.Name == eptName));
              projContext.ExecuteQuery();
              eptUid = eptList.First().Id;
          }
          catch (Exception ex)
          {
              string msg = string.Format("GetEptUid: eptName = \"{0}\"\n\n{1}",
                  eptName, ex.GetBaseException().ToString());
              throw new ArgumentException(msg);
          }
          return eptUid;
      }
    

有幾種方式可以尋找EPT GUID。 GetEptUid 方法中顯示的查詢很有效率,因為它只會下載一個符合 EPT 名稱的 EnterpriseProjectType 物件。 下列替代例程較沒有效率,因為它會將 EPT 的完整清單下載到用戶端應用程式,並逐一查看清單。

foreach (EnterpriseProjectType ept in projSvr.EnterpriseProjectTypes)
{
    if (ept.Name == eptName)
    {
        eptUid = ept.Id;
        break;
    }
}

下列例程會使用LINQ查詢和 Lambda 運算式來選取 EPT 物件,但仍會下載所有 EnterpriseProjectType 物件。

var eptList = projContext.LoadQuery(projContext.EnterpriseProjectTypes);
projContext.ExecuteQuery();
eptUid = eptList.First(ept => ept.Name == eptName).Id;

設定建立資訊併發佈專案

CreateTestProject 方法會建立 ProjectCreationInformation 物件,並指定建立專案所需的資訊。 需要專案 GUID 和名稱;開始日期、專案描述和 EPT GUID 是選擇性的。

設定新的專案屬性之後, Projects.Add 方法會將專案新增至 Projects 集合。 若要儲存併發佈專案,您必須呼叫 Projects.Update 方法,將訊息傳送至 Project Server 佇列並建立專案。

程式 4. 若要設定新的項目屬性,請建立專案併發佈專案

  1. CreateTestProject 方法新增至 Program 類別。 下列程式代碼會建立併發佈專案,但不會等待佇列作業完成。

     // Create a project.
     private static bool CreateTestProject()
     {
         bool projCreated = false;
         try
         {
             Console.Write("\nCreating project: {0} ...", projName);
             ProjectCreationInformation newProj = new ProjectCreationInformation();
             newProj.Id = Guid.NewGuid();
             newProj.Name = projName;
             newProj.Description = "Test creating a project with CSOM";
             newProj.Start = DateTime.Today.Date;
             // Setting the EPT GUID is optional. If no EPT is specified, Project Server  
             // uses the default EPT. 
             newProj.EnterpriseProjectTypeId = GetEptUid(basicEpt);
             PublishedProject newPublishedProj = projContext.Projects.Add(newProj);
             QueueJob qJob = projContext.Projects.Update();
             /* Add code here to wait for the queue. */
         }
         catch(Exception ex)
         {
             Console.ForegroundColor = ConsoleColor.Red;
             Console.WriteLine("\nError: {0}", ex.Message);
             Console.ResetColor();
         }
         return projCreated;
     }
    
  2. 將 批 /* Add code here to wait for the queue. */ 注取代為下列程序代碼,以等候佇列作業。 例程會等候指定的 timeoutSeconds 秒數上限,如果佇列作業在逾時之前完成,則會繼續進行。 如需可能的佇列作業狀態,請參閱 Microsoft.ProjectServer.Client.JobState

    呼叫 QueueJob 物件的 Load 方法和 ExecuteQuery 方法是選擇性的。 如果在呼叫 WaitForQueue 方法時未初始化 QueueJob 物件,Project Server 會將它初始化。

     // Calling Load and ExecuteQuery for the queue job is optional.
     // projContext.Load(qJob);
     // projContext.ExecuteQuery();
     JobState jobState = projContext.WaitForQueue(qJob, timeoutSeconds);
     if (jobState == JobState.Success)
     {
         projCreated = true;
     }
     else
     {
         Console.ForegroundColor = ConsoleColor.Yellow;
         Console.WriteLine("\nThere is a problem in the queue. Timeout is {0} seconds.", 
             timeoutSeconds);
         Console.WriteLine("\tQueue JobState: {0}", jobState.ToString());
         Console.ResetColor();
     }
     Console.WriteLine();
    

列出已發佈的專案

ListPublishedProjects 方法會取得在 Project Web App 中發行之所有專案的集合。 如果在程式 4 中建立專案的佇列作業未順利完成或逾時,則新專案不會包含在 Projects 集合中。

程式 5. 列出已發佈的專案

  1. ListPublishedProjects 方法新增至 Program 類別。

     // List the published projects.
     private static void ListPublishedProjects()
     {
         // Get the list of projects on the server.
         projContext.Load(projContext.Projects);
         projContext.ExecuteQuery();
         Console.WriteLine("\nProject ID : Project name : Created date");
         foreach (PublishedProject pubProj in projContext.Projects)
         {
             Console.WriteLine("\n\t{0} :\n\t{1} : {2}", pubProj.Id.ToString(), pubProj.Name,
                 pubProj.CreatedDate.ToString());
         }
     }
    
  2. 設定 Project Web App URL 的正確值,編譯 QueueCreateProject 應用程式,然後在程式 6 中測試應用程式。

測試 QueueCreateProject 應用程式

當您第一次在 Project Web App的測試實例上執行 QueueCreateProject 應用程式時,特別是當 Project Server 安裝在虛擬機上時,應用程式可能需要比預設佇列逾時 10 秒更多的運行時間。

程式 6. 測試 QueueCreateProject 應用程式

  1. 開啟 [ QueueCreateProject 屬性] 視窗,選取 [ 偵錯 ] 索引卷標,然後在 [ 開始選項 ] 區段中新增下列命令行自變數: -n "Test proj 1" -t 20

    執行應用程式 (例如,按 F5) 。 如果逾時值夠長,應用程式會顯示下列輸出 (如果您的 Project Web App 實例中有其他已發佈的專案,它們也會顯示) :

     Creating project: Test proj 1 ...
     Project ID : Project name : Created date
             b34d7009-753f-4abb-9191-f4b15a82aac3 :
             Test proj 1 : 9/22/2011 11:27:57 AM
     Press any key to exit...
    
  2. 使用下列命令行自變數執行另一個測試,以使用預設的 10 秒佇列逾時: -n "Test proj 1"

    因為 Test proj 1 已經存在,所以應用程式會顯示下列輸出。

     Creating project: Test proj 1 ...
     Error: PJClientCallableException: ProjectNameAlreadyExists
     ProjectNameAlreadyExists
     projName = Test proj 1
     Project creation failed: Test proj 1
     Press any key to exit...
    
  3. 使用下列命令行自變數執行另一個測試,以使用預設的 10 秒佇列逾時: -n "Test proj 2"

    QueueCreateProject 應用程式會建立併發佈名為Test proj 2的專案。

  4. 使用下列命令行自變數執行另一個測試,並將逾時設定為太短,讓佇列作業無法完成: -n "Test proj 3" -t 1

    因為佇列逾時太短,所以不會建立專案。 應用程式會顯示下列輸出。

     Creating project: Test proj 3 ...
     There is a problem in the queue. Timeout is 1 seconds.
             Queue JobState: Unknown
     Project creation failed: Test proj 3
     Press any key to exit...
    
  5. 修改程式代碼,讓應用程式不會等候佇列作業。 例如,將等候佇列的程式代碼批注化,但行除外 projCreated = true ,如下所示。

     //JobState jobState = projContext.WaitForQueue(qJob, timeoutSeconds);
     //if (jobState == JobState.Success)
     //{
     projCreated = true;
     //}
     //else
     //{
     //    Console.ForegroundColor = ConsoleColor.Yellow;
     //    Console.WriteLine("\nThere is a problem in the queue. Timeout is {0} seconds.",
     //        timeoutSeconds);
     //    Console.WriteLine("\tQueue JobState: {0}", jobState.ToString());
     //    Console.ResetColor();
     //}
    
    
  6. 使用下列命令行自變數重新編譯應用程式並執行另一個測試: -n "Test proj 4"

    因為 WaitForQueue 例程已加上批注,所以應用程式不會使用預設的逾時值。 即使應用程式不會等候佇列,但如果 Project Server 中的發佈動作夠快,它仍可能會顯示 Test proj 4。

     Creating project: Test proj 4 ...
     Project ID : Project name : Created date
             cdd54103-082f-425c-b075-9ff52ac7d4e6 :
             Test proj 2 : 9/25/2011 4:28:55 PM
             b34d7009-753f-4abb-9191-f4b15a82aac3 :
             Test proj 1 : 9/22/2011 11:27:57 AM
             5c0c73f2-f5dd-499b-8bd8-ebb74bf8c122 :
             Test proj 4 : 9/25/2011 4:39:21 PM
     Press any key to exit...
    

重新整理 Project Web App () https://ServerName/ProjectServerName/Projects.aspx 中的 [專案中心] 頁面,以顯示已發佈的專案。 下圖顯示測試專案已發佈。

Checking the published projects in Project Web App

在中檢查已發佈的專案 Project Web App

QueueCreateProject 範例應用程式示範如何使用 ProjectCreationInformation 類別建立具有 CSOM 的項目實體、如何將專案新增至已發行集合、如何使用 WaitForQueue 方法等候佇列作業,以及如何列舉已發行專案的集合的典型範例。

完整程式代碼範例

以下是 QueueCreateProject 範 例應用程式的完整程式碼。 Microsoft.ProjectServer.Client.ProjectCreationInformation 類別參考也包含本主題中的程序代碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ProjectServer.Client;
namespace QueueCreateProject
{
    class Program
    {
        private const string pwaPath = "https://ServerName /pwa/"; // Change the path to your Project Web App instance.
        private static string basicEpt = "Enterprise Project";   // Basic enterprise project type.
        private static string projName = string.Empty;
        private static int timeoutSeconds = 10;  // The maximum wait time for a queue job, in seconds.
        private static ProjectContext projContext;
        static void Main(string[] args)
        {
            if (!ParseCommandLine(args))
            {
                Usage();
                ExitApp();
            }
            projContext = new ProjectContext(pwaPath);
            if (CreateTestProject())
                ListPublishedProjects();
            else
                Console.WriteLine("\nProject creation failed: {0}", projName);
            ExitApp();
        }
        // Create a project.
        private static bool CreateTestProject()
        {
            bool projCreated = false;
            try
            {
                Console.Write("\nCreating project: {0} ...", projName);
                ProjectCreationInformation newProj = new ProjectCreationInformation();
                newProj.Id = Guid.NewGuid();
                newProj.Name = projName;
                newProj.Description = "Test creating a project with CSOM";
                newProj.Start = DateTime.Today.Date;
                // Setting the EPT GUID is optional. If no EPT is specified, Project Server uses 
                // the default EPT. 
                newProj.EnterpriseProjectTypeId = GetEptUid(basicEpt);
                PublishedProject newPublishedProj = projContext.Projects.Add(newProj);
                QueueJob qJob = projContext.Projects.Update();
                // Calling Load and ExecuteQuery for the queue job is optional. If qJob is 
                // not initialized when you call WaitForQueue, Project Server initializes it.
                // projContext.Load(qJob);
                // projContext.ExecuteQuery();
                JobState jobState = projContext.WaitForQueue(qJob, timeoutSeconds);
                if (jobState == JobState.Success)
                {
                    projCreated = true;
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("\nThere is a problem in the queue. Timeout is {0} seconds.", 
                        timeoutSeconds);
                    Console.WriteLine("\tQueue JobState: {0}", jobState.ToString());
                    Console.ResetColor();
                }
                Console.WriteLine();
            }
            catch(Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("\nError: {0}", ex.Message);
                Console.ResetColor();
            }
            return projCreated;
        }
        // Get the GUID of the specified enterprise project type.
        private static Guid GetEptUid(string eptName)
        {
            Guid eptUid = Guid.Empty;
            try
            {
                // Get the list of EPTs that have the specified name. 
                // If the EPT name exists, the list will contain only one EPT.
                var eptList = projContext.LoadQuery(
                    projContext.EnterpriseProjectTypes.Where(
                        ept => ept.Name == eptName));
                projContext.ExecuteQuery();
                eptUid = eptList.First().Id;
                // Alternate routines to find the EPT GUID. Both (a) and (b) download the entire list of EPTs.
                // (a) Using a foreach block:
                //foreach (EnterpriseProjectType ept in projSvr.EnterpriseProjectTypes)
                //{
                //    if (ept.Name == eptName)
                //    {
                //        eptUid = ept.Id;
                //        break;
                //    }
                //}
                // (b) Querying for the EPT list, and then using a lambda expression to select the EPT:
                //var eptList = projContext.LoadQuery(projContext.EnterpriseProjectTypes);
                //projContext.ExecuteQuery();
                //eptUid = eptList.First(ept => ept.Name == eptName).Id;
            }
            catch (Exception ex)
            {
                string msg = string.Format("GetEptUid: eptName = \"{0}\"\n\n{1}",
                    eptName, ex.GetBaseException().ToString());
                throw new ArgumentException(msg);
            }
            return eptUid;
        }
        // List the published projects.
        private static void ListPublishedProjects()
        {
            // Get the list of projects on the server.
            projContext.Load(projContext.Projects);
            projContext.ExecuteQuery();
            Console.WriteLine("\nProject ID : Project name : Created date");
            foreach (PublishedProject pubProj in projContext.Projects)
            {
                Console.WriteLine("\n\t{0} :\n\t{1} : {2}", pubProj.Id.ToString(), pubProj.Name,
                    pubProj.CreatedDate.ToString());
            }
        }
        // Parse the command line. Return true if there are no errors.
        private static bool ParseCommandLine(string[] args)
        {
            bool error = false;
            int argsLen = args.Length;
            try
            {
                for (int i = 0; i < argsLen; i++)
                {
                    if (error) break;
                    if (args[i].StartsWith("-") || args[i].StartsWith("/"))
                        args[i] = "*" + args[i].Substring(1).ToLower();
                    switch (args[i])
                    {
                        case "*projname":
                        case "*n":
                            if (++i >= argsLen) return false;
                            projName = args[i];
                            break;
                        case "*timeout":
                        case "*t":
                            if (++i >= argsLen) return false;
                            timeoutSeconds = Convert.ToInt32(args[i]);
                            break;
                        case "*?":
                        default:
                            error = true;
                            break;
                    }
                }
            }
            catch (FormatException)
            {
                error = true;
            }
            if (string.IsNullOrEmpty(projName)) error = true;
            return !error;
        }
        private static void Usage()
        {
            string example = "Usage: QueueCreateProject -projName | -n \"New project name\" [-timeout | -t sec]";
            example += "\nExample: QueueCreateProject -n \"My new project\"";
            example += "\nDefault timeout seconds = " + timeoutSeconds.ToString();
            Console.WriteLine(example);
        }
        private static void ExitApp()
        {
            Console.Write("\nPress any key to exit... ");
            Console.ReadKey(true);
            Environment.Exit(0);
        }
    }
}

另請參閱