使用C#代码部署SharePoint 2013开发包简单总结(一)
??????這篇文章將總結下如何將自己開發的列表、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"還是有效果的。
?????? 功能有個重要的范圍屬性——SPFeatureDefinition對象的Scope,可選擇Farm、WebApplication、Site或Web,在代碼里這是個SPFeatureScope枚舉。我的Web部件如果選擇的不是Site,在發布的時候會報類似“錯誤?1?無法通過具有 WebApplication 范圍的功能部署項目項‘VisualWebPart1’。”的錯誤。因為SPWebApplication、SPSite、SPWeb對象都有一個Features的屬性,我們只要根據范圍調用SPFeatureCollection的Add方法添加就可以了。下面是添加功能的代碼(Farm和WebApplication范圍的功能我還沒有實際使用和驗證過,抱歉!):
Console.WriteLine("開始激活功能:");foreach (var featureXElement in featureXElements){Guid featureId = new Guid(featureXElement.Attribute("featureId").Value);SPFeatureDefinition definition =SPFarm.Local.FeatureDefinitions[featureId];if (definition != null){if (definition.ActivateOnDefault){bool isForceInstall = definition.AlwaysForceInstall;if (featureXElement.Attribute("isForceInstall") != null){isForceInstall = string.Equals("true", featureXElement.Attribute("isForceInstall").Value, StringComparison.OrdinalIgnoreCase);}if (definition.Scope == SPFeatureScope.Site){XAttribute siteUrlAttribute = featureXElement.Attribute("siteUrl");if (siteUrlAttribute != null && !string.IsNullOrEmpty(siteUrlAttribute.Value)){using (SPSite spSite = new SPSite(siteUrlAttribute.Value)){Console.WriteLine("正在激活功能“{0}”到網站集 “{1}”...", definition.DisplayName, spSite.Url);spSite.Features.Add(featureId, isForceInstall);}}else{foreach (SPSite spSite in spWebApplications.SelectMany(a => a.Sites)){Console.WriteLine("正在激活功能“{0}”到網站集“{1}”...", definition.DisplayName, spSite.Url);spSite.Features.Add(featureId, isForceInstall);}}}else if (definition.Scope == SPFeatureScope.Web){XAttribute siteUrlAttribute = featureXElement.Attribute("siteUrl");XAttribute webNameAttribute = featureXElement.Attribute("webName");if (siteUrlAttribute != null){using (SPSite spSite = new SPSite(siteUrlAttribute.Value)){if (webNameAttribute != null){using (SPWeb spWeb = spSite.AllWebs[webNameAttribute.Value]){Console.WriteLine("正在激活功能“{0}”到網站“{1}”...", definition.DisplayName, spWeb.Title);spWeb.Features.Add(featureId, isForceInstall);}}else{foreach (SPWeb spWeb in spSite.AllWebs){using (spWeb){Console.WriteLine("正在激活功能 “{0}”到 網站“{1}”...", definition.DisplayName, spWeb.Title);spWeb.Features.Add(featureId, isForceInstall);}}}}}else{foreach (SPWeb spWeb in spWebApplications.SelectMany(a => a.Sites.SelectMany(s => s.AllWebs))){Console.WriteLine("正在激活功能“{0}”到 網站“{1}”...", definition.DisplayName, spWeb.Title);spWeb.Features.Add(featureId, isForceInstall);}}}else if (definition.Scope == SPFeatureScope.WebApplication){foreach (SPWebApplication spWebApplication in spWebApplications){Console.WriteLine("正在激活功能“{0}”到 Web應用程序 “{1}”...", definition.DisplayName, spWebApplication.DisplayName);spWebApplication.Features.Add(featureId, isForceInstall);}}else if (definition.Scope == SPFeatureScope.Farm){//TODO:還未找到方案}else{//TODO:還未找到方案}}else{Console.WriteLine("ID為{0}的功能默認不在安裝期間激活!", featureId.ToString());}}else{Console.WriteLine("未找到ID為{0}的功能定義!", featureId.ToString());}}????? 功能成功被添加后,在盤符:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\FEATURES下找到了對應的功能文件夾,這個路徑可以通過SPFeatureDefinition對象的屬性獲取。
???? 下面是刪除功能和功能定義的代碼:
Console.WriteLine("開始回收功能:");foreach (var featureXElement in featureXElements){Guid featureId = new Guid(featureXElement.Attribute("featureId").Value);SPFeatureDefinition definition =SPFarm.Local.FeatureDefinitions[featureId];if (definition != null){bool isForceInstall = definition.AlwaysForceInstall;if (featureXElement.Attribute("isForceInstall") != null){isForceInstall = string.Equals("true", featureXElement.Attribute("isForceInstall").Value, StringComparison.OrdinalIgnoreCase);}if (definition.Scope == SPFeatureScope.Site){XAttribute siteUrlAttribute = featureXElement.Attribute("siteUrl");if (siteUrlAttribute != null && !string.IsNullOrEmpty(siteUrlAttribute.Value)){using (SPSite spSite = new SPSite(siteUrlAttribute.Value)){Console.WriteLine("正在從網站集“{0}”回收功能“{1}”...", spSite.Url, definition.DisplayName);SPFeature spFeature = spSite.Features[featureId];if (spFeature != null){spSite.Features.Remove(featureId, isForceInstall);}}}else{foreach (SPSite spSite in spWebApplications.SelectMany(a => a.Sites)){Console.WriteLine("正在從網站集“{0}”回收功能“{1}”...", spSite.Url, definition.DisplayName);SPFeature spFeature = spSite.Features[featureId];if (spFeature != null){spSite.Features.Remove(featureId, isForceInstall);}}}}else if (definition.Scope == SPFeatureScope.Web){XAttribute siteUrlAttribute = featureXElement.Attribute("siteUrl");XAttribute webNameAttribute = featureXElement.Attribute("webName");if (siteUrlAttribute != null){using (SPSite spSite = new SPSite(siteUrlAttribute.Value)){if (webNameAttribute != null){using (SPWeb spWeb = spSite.AllWebs[webNameAttribute.Value]){Console.WriteLine("正在從網站“{0}”回收功能“{1}”...", spWeb.Title, definition.DisplayName);SPFeature spFeature = spWeb.Features[featureId];if (spFeature != null){spWeb.Features.Remove(featureId, isForceInstall);}}}else{foreach (SPWeb spWeb in spSite.AllWebs){using (spWeb){Console.WriteLine("正在從網站“{0}”回收功能“{1}”...", spWeb.Title, definition.DisplayName);SPFeature spFeature = spWeb.Features[featureId];if (spFeature != null){spWeb.Features.Remove(featureId, isForceInstall);}}}}}}else{foreach (SPWeb spWeb in spWebApplications.SelectMany(a => a.Sites.SelectMany(s => s.AllWebs))){Console.WriteLine("正在從網站“{0}”回收功能“{1}”...", spWeb.Title, definition.DisplayName);SPFeature spFeature = spWeb.Features[featureId];if (spFeature != null){spWeb.Features.Remove(featureId, isForceInstall);}}}}else if (definition.Scope == SPFeatureScope.WebApplication){foreach (SPWebApplication spWebApplication in spWebApplications){Console.WriteLine("正在從Web應用程序“{0}”回收功能“{1}” ...", spWebApplication.DisplayName, definition.DisplayName);SPFeature spFeature = spWebApplication.Features[featureId];if (spFeature != null){spWebApplication.Features.Remove(featureId, isForceInstall);}}}else if (definition.Scope == SPFeatureScope.Farm){//TODO:還未找到方案}else{//TODO:還未找到方案}Console.WriteLine("正在刪除功能定義“{0}”...", definition.DisplayName);string featurePath = definition.RootDirectory;definition.Delete(); }}????? 遺憾的是,上面的代碼不能夠完美解決部署沖突的問題。例如部署過Web部件后我又修改了.webpart文件里的Title,用這種方式重新部署之后不會馬上體現更改,而Visual Studio的部署功能卻有這個能力——在輸出窗口能看到“已找到 1 個部署沖突。正在解決沖突..??? 已從服務器中刪除文件“.../_catalogs/wp/VisualWebPartProject1_VisualWebPart1.webpart”。”的信息。現在需要個很笨的方法——先到網站設置->Web設計器庫->Web部件庫把對應的Web部件刪掉。
????? 現在一個簡單的SharePoint開發包部署程序就給大家介紹完了。不過這個程序還有很多的不足,例如: 我只用它部署過列表、Web部件和事件接收器,不能支持用戶解決方案(SPUserSolution),不能部署網站模板;部署的環境只是針對一個Web前端服務器的本地部署,需要使用SharePoint管理員權限運行;另外對部署沖突和功能依賴項的處理還沒有找到比較滿意的辦法。我會繼續研究總結并和大家分享。最后,還希望各位高手專家看過此文能夠不惜賜教,多多指點!
SharePoint開發包部署程序下載地址:
http://files.cnblogs.com/CSharpDevelopers/SharePointWspDeployApplication.zip
參考資料:
http://technet.microsoft.com/zh-cn/library/cc263205(v=office.15).aspx
http://msdn.microsoft.com/zh-cn/library/microsoft.sharepoint.administration(v=office.12).aspx
?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的使用C#代码部署SharePoint 2013开发包简单总结(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 民生白条联名卡:给京东白条还款就用它
- 下一篇: 瀚蓝转债(733323)什么时候发行?附