這篇文章將總結下如何將自己開發的列表、Web部件、事件接收器等元素部署到SharePoint的服務器。因水平有限,我的做法未必是最佳實踐,會有些錯誤理解和疏漏,歡迎各位高手批評指正——但一定要能給出更好的方案。如果您是SharePoint開發的新手,希望能和我一起積極的思考,也歡迎您的討論。 首先寫個簡單的通過PowerShell部署Web部件的例子。當我寫了一個SharePoint 2013 的可視化Web部件,在Visual Studio 2012發布後,將得到一個wsp文件(這是個壓縮文件),例如叫VisualWebPartProject1.wsp。然後我們打開SharePoint 2013 Management Shell,輸入下面的命令,這個Web部件就可以使用了! 1 2 3 Add-SPSolution –LiteralPath C:\VisualWebPartProject1.wsp Install-SPSolution -Identity VisualWebPartProject1.wsp -WebApplication "SharePoint - 80" -GACDeployment –FullTrustBinDeployment Enable-SPFeature -Identity c63aa2e6-527e-4de4-8e99-1729f2d052aa -Url http://sp2013-01:80/ 從上面的PowerShell命令,我們可以看出做了三件事兒:1、添加解決方案包(Add-SPSolution); 2、部署解決方案包(Install-SPSolution); 3、激活功能(Enable-SPFeature)。(參數細節請參考http://technet.microsoft.com/zh-cn/library/ee906565.aspx)。 一定有很多人會覺得PowerShell部署已經很簡單,但是我總覺得PowerShell命令不好記也不好寫。而且有時需要技術不專業的人幫忙(例如客戶IT)會多費不少口舌,不如把它做成一個exe文件,把參數寫到XML裡配置好,這樣實施人員一雙擊執行文件就OK了。 下面我們就研究下如何用C#代碼完成這部署三大步驟,不過之前要介紹幾個重要的.NET類型! 1、SPFarm:服務器場, 可通過SPFarm.Local獲取本地服務器場對象。 2、SPWebApplication:Web應用程序,可以通過SPWebService.ContentService.WebApplications獲取它們的集合,也可以在這個集合中用它的Name屬性找到您想要的對象。 3、SPSite:網站集,可以傳參網站集URL給SPSite的構造方法創建對象,也可以通過SPWebApplication對象的Sites屬性獲取集合。 4、SPWeb:網站/子網站,每個SPSite對象有一個RootWeb屬性,是它的根網站;還有個AllWebs屬性,是它的所有網站。每個SPWeb對象也有個Webs屬性,是這個網站的子網站。 5、SPSolution:解決方案,我們開發項目生成的wsp文件就靠它來管理,SPFarm對象有個Solutions的屬性,是解決方案的集合。在SharePoint管理中心主頁(SharePoint 2013 Central Administration)-〉系統設置(System Settings)-〉場管理(Farm Management)-〉管理場解決方案(Manage farm solutions)能看到已上傳的解決方案。更多了解解決方案包請參考http://technet.microsoft.com/zh-cn/library/cc262995(v=office.14).aspx。 6、SPFeatureDefinition:功能定義,SPFarm對象有個FeatureDefinitions的屬性,是功能定義的集合。PSite、SPWeb對象也有FeatureDefinitions屬性,從SPFeature(功能)對象的FeatureDefinitionScope屬性還能獲取到一個SPFeatureDefinitionScope枚舉值。 7、SPFeature:功能,SPWebApplication、SPSite、SPWeb都有一個Features屬性,是它們的功能集合。在SharePoint頁面的設置-〉系統設置-〉網站集管理-〉網站集功能 能看到網站集的功能;在SharePoint頁面的設置-〉系統設置-〉網站操作-〉管理網站功能看到網站的功能。更多了功能請參考http://technet.microsoft.com/zh-cn/library/ff607883(v=office.14).aspx。 接下來要准備寫代碼了,用Visual Studio 2012創建一個控制台應用程序,為項目添加新項-〉數據-〉XML文件,為這個文件取一個溫暖的名字——這是部署信息的配置文件,我這裡就叫DeploymentInfo.xml了。文件屬性-〉高級-〉復制到輸出目錄選擇“如果較新就復制”。然後加入類似下面的內容: 復制代碼 <configuration> <solution literalPath="WSPPackage\SharePointSolution1.wsp" webApplicationName="SharePoint - 80" isForceInstall="true" > <feature featureId="cc7c09d1-023c-4917-82ab-b82b846631a8" siteUrl="http://sharepointsiteurl/" webName="" isForceInstall="true" /> <feature featureId="74f7c14b-dcca-4d4f-b2f7-7be3e7955bd1" siteUrl="http://sharepointsiteurl/" webName="" isForceInstall="true" /> </solution> <solution literalPath="WSPPackage\SharePointSolution2.wsp" webApplicationName="SharePoint - 80" isForceInstall="true" > <feature featureId="f60f8bfe-5d65-43de-86b4-cc10fbcab800" siteUrl="http://sharepointsiteurl/" webName="webName" isForceInstall="true" /> <feature featureId="963241f7-b33e-4c3e-bf00-cbcaf1c22412" siteUrl="http://sharepointsiteurl/" webName="webName" isForceInstall="true" /> <feature featureId="26dab42a-0f42-449b-84c0-111a8474dbc4" siteUrl="http://sharepointsiteurl/" webName="webName" isForceInstall="true" /> </solution> </configuration> 復制代碼 一個solution節點對應一個解決方案包,可以配置部署多個解決方案;一個feature節點對應一個功能,feature節點在solution節點下,也可以有N個。 再解釋下配置參數:literalPath是解決方案包的路徑,我會在程序文件夾下再放個WSPPackage文件夾,發布生成的wsp文件就放在這裡;featureId是功能的ID;webApplicationName是web應用程序的名稱,siteUrl是網站集的URL,webName是網站的名稱,這三個參數如果沒有將遍歷全部(根網站的webName是空字符串)。isForceInstall是是否強制參數,其實我們在Visual Studio裡可以設置這個參數,但是默認是"False ",我不願意改它。 在Web部件代碼Visual Studio解決方案管理器裡選中項目-〉屬性窗口可以看到解決方案的屬性。 雙擊Features文件夾下的某個Feature文件,在屬性窗口就能看到功能的屬性,包括功能ID。 我們程序代碼要做的事就是利用這些部署信息將開發元素的解決方案包添加部署,功能激活。 下面正式要上C#代碼了,進入代碼文件Program.cs 的Main方法,先來段簡單的配置文件遍歷,取出解決方案和功能信息,要引用命名空間 System.Xml.Linq: 復制代碼 using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace SharePointWspDeployApplication { class Program { static void Main(string[] args) { XElement root = XElement.Load("DeploymentInfo.xml"); IEnumerable<XElement> solutionXElements = root.Elements("solution"); foreach (XElement solutionXElement in solutionXElements) { string literalPath = solutionXElement.Attribute("literalPath") != null ? solutionXElement.Attribute("literalPath").Value : null; XElement[] featureXElements = solutionXElement.Elements("feature").ToArray(); foreach (var featureXElement in featureXElements) { Guid featureId = new Guid(featureXElement.Attribute("featureId").Value); } } } } } 復制代碼 這裡解決方案添加只要為場對象添加就行,我直接調用SPFarm對象的SPSolutionCollection類型Solutions屬性的Add方法,傳給wsp文件的路徑。(SPSite、SPWeb對象也有Solutions屬性,但是SPUserSolutionCollection類型)。添加完成後我們可以到SharePoint配置數據庫SharePoint_Config的Objects表裡找到兩條name是wsp文件名的記錄(wsp文件名是小寫的),有一條能夠在 Binaries表中找到對應記錄(Id和ObjectId關聯),用下面的方法可以把wsp文件取出來。 復制代碼 private static void GetFileFromConfigDataBase(string fileName,string path) { byte[] file = null; const string connectionString = "data source=.;initial catalog=SharePoint_Config;Trusted_Connection=Yes;"; const string commandText = "SELECT [FileImage] FROM [Objects] INNER JOIN [Binaries] ON [Objects].[Id] = [Binaries].[ObjectId] WHERE [name] = @fileName"; using (SqlConnection sqlConnection = new SqlConnection(connectionString)) { using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)) { sqlCommand.Parameters.Add(new SqlParameter("@fileName", fileName)); sqlConnection.Open(); using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow)) { if (sqlDataReader.Read()) { file = (byte[])sqlDataReader[0]; } } } } if (file != null) { using (FileStream fileStream = new FileStream(Path.Combine(path,fileName), FileMode.CreateNew)) { fileStream.Write(file, 0, file.Length); fileStream.Close(); } } } 復制代碼 文件取出來後,我們可以把這個wsp文件解壓,和上傳之前的對比下。我的電腦上的解壓縮軟件可以直接解壓,如果不行可嘗試把擴展名改成cab的。解壓縮後可以看到文件夾裡有manifest.xml文件、DLL文件、Feature文件夾。 部署解決方案稍微復雜些,SPSolution對象有兩個部署方法LocalDeploy和Deploy(Deploy要傳個時間點啟用Timer job,是多個Web前端服務器可用部署方式),每個部署方法又有個重載。 在Visual Studio項目屬性窗口有個程序集部署目標的屬性,可選擇GlobalAssemblyCache或WebApplication,如果沙盒解決方案選擇“True”,這個屬性就不能選了。那麼我們直接用Visual Studio部署時如果選擇了GlobalAssemblyCache會是什麼樣呢?我試著將一個Web部件項目屬性的程序集部署目標設置為GlobalAssemblyCache,然後選擇項目右鍵部署,再到SharePoint管理中心主頁的管理場解決方案列表,發現Deployed To列是個URL而不是“Globally deployed.”! 然後我在SharePoint 2013 Management Shell執行下面的命令部署(未指定Web應用程序): 1 Install-SPSolution -Identity VisualWebPartProject1.wsp -GACDeployment –FullTrustBinDeployment 執行後發現也報錯! 看來是我理解亂了。原來這個程序集部署目標的屬性決定的是DLL的部署位置,如果我們選擇GlobalAssemblyCache,將在盤符:\Windows\Microsoft.NET\assembly\GAC_MSIL\ 找到對應文件;選擇WebApplication,則在盤符:\inetpub\wwwroot\wss\VirtualDirectories\80\bin\找到對應文件。SPSolution對象有個ContainsGlobalAssembly屬性,程序集部署目標如果選擇了GlobalAssemblyCache,它就是“true”。 為了避免上述 “此解決方案包含Web應用程序范圍的資源,必須將其部署到一個或多個Web應用程序。”的異常,可以通過SPSolution對象的ContainsWebApplicationResource屬性來判斷,如果為“True”,部署時要指定Web應用程序,即使用帶Collection<SPWebApplication>參數的部署重載方法。 解決方案的添加和部署操作代碼要寫在外層對解決方案信息節點遍歷的foreach循環裡。下面是我添加和部署解決方案的代碼(要添加程序集-擴展-Microsoft.SharePoint引用,命名空間要引入Microsoft.SharePoint.Administration,如果系統是64位的,生成-目標平台也要改成x64): 復制代碼 XElement root = XElement.Load("DeploymentInfo.xml"); IEnumerable<XElement> solutionXElements = root.Elements("solution"); SPWebApplicationCollection spWebApplicationCollection = SPWebService.ContentService.WebApplications; foreach (XElement solutionXElement in solutionXElements) { string literalPath = solutionXElement.Attribute("literalPath") != null ? solutionXElement.Attribute("literalPath").Value : null; XAttribute webApplicationNameAttribute = solutionXElement.Attribute("webApplicationName"); SPWebApplication[] spWebApplications; if (webApplicationNameAttribute != null && !string.IsNullOrEmpty(webApplicationNameAttribute.Value)) { spWebApplications = new[] { spWebApplicationCollection[webApplicationNameAttribute.Value] }; } else { spWebApplications = spWebApplicationCollection.ToArray(); } Console.WriteLine("開始添加解決方案:"); string wspName = Path.GetFileName(literalPath).ToLower(); SPSolution spSolution = SPFarm.Local.Solutions[wspName]; if (spSolution != null) { if (spSolution.Deployed) { if (spSolution.ContainsWebApplicationResource) { Console.WriteLine("正在從Web應用程序回收解決方案 “{0}”...", spSolution.Name); spSolution.RetractLocal(spSolution.DeployedWebApplications); } else { Console.WriteLine("正在回收解決方案 “{0}”...", spSolution.Name); spSolution.RetractLocal(); } } Console.WriteLine("正在刪除解決方案 “{0}”...", spSolution.Name); spSolution.Delete(); } if (!string.IsNullOrEmpty(literalPath) && File.Exists(literalPath)) { Console.WriteLine("正在添加解決方案 “{0}”...", wspName); spSolution = SPFarm.Local.Solutions.Add(literalPath); bool isForceInstall = false; if (solutionXElement.Attribute("isForceInstall") != null) { isForceInstall = string.Equals("true", solutionXElement.Attribute("isForceInstall").Value, StringComparison.OrdinalIgnoreCase); } if (spSolution.ContainsWebApplicationResource) { Console.WriteLine(@"正在部署解決方案 “{0}” 到 Web應用程序 “{1}”...", spSolution.Name, string.Join(",", spWebApplications.Select(a => a.DisplayName).ToArray())); spSolution.DeployLocal(spSolution.ContainsGlobalAssembly, new Collection<SPWebApplication>(spWebApplications), isForceInstall); } else { Console.WriteLine("正在部署解決方案 “{0}”...", spSolution.Name); spSolution.DeployLocal(spSolution.ContainsGlobalAssembly, isForceInstall); } } else { Console.WriteLine("literalPath為空或指定路徑文件不存在!"); } } 復制代碼 解決方案添加並部署後,可以到激活功能了。我們在開發時設置的功能屬性可以通過SPFeatureDefinition的對象得到(這個SPFeatureDefinition對象可以用SPFarm對象的FeatureDefinitions屬性以功能ID或名稱作索引獲取)。例如始終強制安裝,應該對應SPFeatureDefinition對象的AlwaysForceInstall屬性;ActivationDependencies屬性應該對應功能激活依賴項集合。還有個默認激活(Activate On Default)的屬性,我本以為就是SPFeatureDefinition對象的ActivateOnDefault屬性,但是在屬性窗口改了一下發現竟然沒效果!後來我在功能清單的XML裡找到了個ActivateOnDefault="true"描述。一般的情況下,您修改功能的屬性後,這個manifest.xml裡的內容要跟著變的,但是這個默認激活屬性在我當前的Visual Studio版本怎麼設置都沒反應。不過您如果在下面的編輯選項裡強制更改ActivateOnDefault="false"還是有效果的。