SharePoint 外接程序模型中的模块
在新 SharePoint 外接程序模型中将项目部署到 SharePoint 环境的方法与使用完全信任代码不同。 在典型完全信任代码 (FTC)/场解决方案场景中,声明性代码(功能框架 XML)中定义的模块被添加到 SharePoint 功能。 该模块包含部署到 SharePoint 服务器的项目列表。 该模块被添加到 SharePoint 功能并通过 SharePoint 解决方案对其部署。 功能激活后,将模块中定义的项目部署到 SharePoint 环境。
在 SharePoint 外接程序模型场景中,远程预配模式用于将项目部署到 SharePoint 环境。
术语
术语“项目将在整篇文章引用。 项目指的是通常被部署到 SharePoint 环境的项目。 项目通常包括:
- JavaScript 文件
- CSS 文件
- 图像文件(.jpg、.gif、.png 等)
- 母版页
- 页面布局
- 列表项
高级别准则
作为经验法则,我们想提供以下有关将项目部署到 SharePoint 环境的高级别准则。
- 使用远程预配模式(SharePoint 客户端对象模型和 SharePoint REST API),以将项目部署到 SharePoint 环境。
- 请不要使用声明性代码模块或功能框架 XML 文件将项目部署到 SharePoint 环境。
调试
使用代码部署项目的最大优点是,使用代码时能够调试部署过程。 在使用声明性代码模块或功能框架 XML 文件将项目部署到 SharePoint 环境时,将无法调试部署过程。
入门
以下 O365 PnP 代码示例演示了如何创建使用远程预配模式的 SharePoint 外接程序,以将项目部署到 SharePoint 环境。
此示例演示了如何在样式库中新建文件夹,并向其添加 JavaScript 文件和图像。
- Branding.ClientSideRendering(O365 PnP 代码示例)
有关更多详细信息,请参阅 Default.aspx.cs 类中的 UploadJSFiles 和 UploadFileToFolder 方法。
以下介绍了这些方法,以供快速参考。
创建文件夹并将 JavaScript 文件上载到该文件夹:
void UploadJSFiles(Web web) { //Delete the folder if it exists Microsoft.SharePoint.Client.List list = web.Lists.GetByTitle("Style Library"); IEnumerable<Folder> results = web.Context.LoadQuery<Folder>(list.RootFolder.Folders.Where(folder => folder.Name == "JSLink-Samples")); web.Context.ExecuteQuery(); Folder samplesJSfolder = results.FirstOrDefault(); if (samplesJSfolder != null) { samplesJSfolder.DeleteObject(); web.Context.ExecuteQuery(); } //Create new folder samplesJSfolder = list.RootFolder.Folders.Add("JSLink-Samples"); web.Context.Load(samplesJSfolder); web.Context.ExecuteQuery(); //Upload JavaScript files to folder UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/Accordion.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/ConfidentialDocuments.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/DisableInput.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/HiddenField.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/PercentComplete.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/PriorityColor.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/ReadOnlySPControls.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/RegexValidator.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/SubstringLongText.js"), samplesJSfolder); UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/DependentFields.js"), samplesJSfolder); //Create another folder inside the folder that was just created Folder imgsFolder = samplesJSfolder.Folders.Add("imgs"); web.Context.Load(imgsFolder); web.Context.ExecuteQuery(); //Upload image files to folder UploadFileToFolder(web, Server.MapPath("../Scripts/JSLink-Samples/imgs/Confidential.png"), imgsFolder); }
创建文件夹并将图像文件上载到该文件夹:
public static void UploadFileToFolder(Web web, string filePath, Folder folder) { //Create a FileStream to the file to upload using (FileStream fs = new FileStream(filePath, FileMode.Open)) { //Create FileCreationInformation object to set file metadata FileCreationInformation flciNewFile = new FileCreationInformation(); flciNewFile.ContentStream = fs; flciNewFile.Url = System.IO.Path.GetFileName(filePath); flciNewFile.Overwrite = true; //Upload file to SharePoint Microsoft.SharePoint.Client.File uploadFile = folder.Files.Add(flciNewFile); //Check in the file uploadFile.CheckIn("CSR sample js file", CheckinType.MajorCheckIn); folder.Context.Load(uploadFile); folder.Context.ExecuteQuery(); } }
此示例演示了如何通过设置 Web 对象上的 CustomMasterUrl 属性来上传母版页、设置母版页元数据并将其应用到网站。
- Branding.ApplyBranding(O365 PnP 代码示例)
有关更多详细信息,请参阅 BrandingHelper.cs 类中的 UploadPageLayout、CreatePublishingPage 和 SetSupportCaseContent 方法。
除了在 SharePoint 中新建项目外,此示例还演示了如何删除项目。 以下列出了删除项目的方法,以供参考。 删除文件:
private static void DeleteFile(Web web, string fileName, string serverPath, string serverFolder) { var fileUrl = string.Concat(serverPath, serverFolder, (string.IsNullOrEmpty(serverFolder) ? string.Empty : "/"), fileName); var fileToDelete = web.GetFileByServerRelativeUrl(fileUrl); fileToDelete.DeleteObject(); web.Context.ExecuteQuery(); }
删除文件夹:
public static void RemoveFolder(ClientContext clientContext, string folder, string path) { var web = clientContext.Web; var filePath = web.ServerRelativeUrl.TrimEnd(Program.trimChars) + "/" + path + "/"; var folderToDelete = web.GetFolderByServerRelativeUrl(string.Concat(filePath, folder)); Console.WriteLine("Removing folder {0} from {1}", folder, path); folderToDelete.DeleteObject(); clientContext.ExecuteQuery(); }
取消分配母版页并删除母版页
public static void RemoveMasterPage(ClientContext clientContext, string name, string folder) { var web = clientContext.Web; clientContext.Load(web, w => w.AllProperties); clientContext.ExecuteQuery(); Console.WriteLine("Deactivating and removing {0} from {1}", name, web.ServerRelativeUrl); //set master pages back to the defaults that were being used if (web.AllProperties.FieldValues.ContainsKey("OriginalMasterUrl")) { web.MasterUrl = (string)web.AllProperties["OriginalMasterUrl"]; } if (web.AllProperties.FieldValues.ContainsKey("CustomMasterUrl")) { web.CustomMasterUrl = (string)web.AllProperties["CustomMasterUrl"]; } web.Update(); clientContext.ExecuteQuery(); //now that the master page is set back to its default, re-reference the web from context and delete the custom master pages web = clientContext.Web; var lists = web.Lists; var gallery = web.GetCatalog(116); clientContext.Load(lists, l => l.Include(ll => ll.DefaultViewUrl)); clientContext.Load(gallery, g => g.RootFolder.ServerRelativeUrl); clientContext.ExecuteQuery(); var masterPath = gallery.RootFolder.ServerRelativeUrl.TrimEnd(new char[] { '/' }) + "/"; DeleteFile(web, name, masterPath, folder); }
删除页面布局
public static void RemovePageLayout(ClientContext clientContext, string name, string folder) { var web = clientContext.Web; var lists = web.Lists; var gallery = web.GetCatalog(116); clientContext.Load(lists, l => l.Include(ll => ll.DefaultViewUrl)); clientContext.Load(gallery, g => g.RootFolder.ServerRelativeUrl); clientContext.ExecuteQuery(); Console.WriteLine("Removing page layout {0} from {1}", name, clientContext.Web.ServerRelativeUrl); var masterPath = gallery.RootFolder.ServerRelativeUrl.TrimEnd(Program.trimChars) + "/"; DeleteFile(web, name, masterPath, folder); }
观看通过 SharePoint 的外接程序将品牌打造应用到 SharePoint 网站(Office 365 PnP 视频)以了解此示例的整个过程。
此示例包含一些内容。 它演示了如何激活发布功能、上传页面布局、创建发布页面、创建列表、内容类型和列表项,以及创建 pblishing 页面以及向页面添加 Web 部件和外接程序部件。 它还演示了如何将列表项部署到主机 Web 和外接程序 Web。
- Branding.ClientSideRendering(O365 PnP 代码示例)
有关这些操作的示例,请参阅 Utils.cs 类中的方法。
以下列出了这些方法,以供参考。
激活网站集和网站级发布功能:
public static void ActivePublishingFeature(ClientContext ctx) { Guid publishingSiteFeatureId = new Guid("f6924d36-2fa8-4f0b-b16d-06b7250180fa"); Guid publishingWebFeatureId = new Guid("94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb"); Site clientSite = ctx.Site; ctx.Load(clientSite); FeatureCollection clientSiteFeatures = clientSite.Features; ctx.Load(clientSiteFeatures); //Activate the site feature clientSiteFeatures.Add(publishingSiteFeatureId, true, FeatureDefinitionScope.Farm); ctx.ExecuteQuery(); FeatureCollection clientWebFeatures = ctx.Web.Features; ctx.Load(clientWebFeatures); //Activate the web feature clientWebFeatures.Add(publishingWebFeatureId, true, FeatureDefinitionScope.Farm); ctx.ExecuteQuery(); }
创建列表:
public static List CreateList(ClientContext ctx, int templateType, string title, string url, QuickLaunchOptions quickLaunchOptions) { ListCreationInformation listCreationInfo = new ListCreationInformation { TemplateType = templateType, Title = title, Url = url, QuickLaunchOption = quickLaunchOptions }; List spList = ctx.Web.Lists.Add(listCreationInfo); ctx.Load(spList); ctx.ExecuteQuery(); return spList; }
创建内容类型
public static ContentType CreateContentType(ClientContext ctx, string ctyName, string group, string ctyId) { ContentTypeCreationInformation contentTypeCreation = new ContentTypeCreationInformation(); contentTypeCreation.Name = ctyName; contentTypeCreation.Description = "Custom Content Type"; contentTypeCreation.Group = group; contentTypeCreation.Id = ctyId; //Add the new content type to the collection ContentType ct = ctx.Web.ContentTypes.Add(contentTypeCreation); ctx.Load(ct); ctx.ExecuteQuery(); return ct; }
上载页面布局
public static void UploadPageLayout(ClientContext ctx, string sourcePath, string targetListTitle, string targetUrl) { using (FileStream fs = new FileStream(sourcePath, FileMode.Open, FileAccess.Read)) { byte[] data = new byte[fs.Length]; fs.Read(data, 0, data.Length); using (MemoryStream ms = new MemoryStream()) { ms.Write(data, 0, data.Length); var newfile = new FileCreationInformation(); newfile.Content = ms.ToArray(); newfile.Url = targetUrl; newfile.Overwrite = true; List docs = ctx.Web.Lists.GetByTitle(targetListTitle); Microsoft.SharePoint.Client.File uploadedFile = docs.RootFolder.Files.Add(newfile); uploadedFile.CheckOut(); uploadedFile.CheckIn("Data storage model", CheckinType.MajorCheckIn); uploadedFile.Publish("Data storage model layout."); ctx.Load(uploadedFile); ctx.ExecuteQuery(); } } }
创建发布页面并设置其页面布局:
public static void CreatePublishingPage(ClientContext clientContext, string pageName, string pagelayoutname, string url, string queryurl) { var publishingPageName = pageName + ".aspx"; Web web = clientContext.Web; clientContext.Load(web); List pages = web.Lists.GetByTitle("Pages"); clientContext.Load(pages.RootFolder, f => f.ServerRelativeUrl); clientContext.ExecuteQuery(); Microsoft.SharePoint.Client.File file = web.GetFileByServerRelativeUrl(pages.RootFolder.ServerRelativeUrl + "/" + pageName + ".aspx"); clientContext.Load(file, f => f.Exists); clientContext.ExecuteQuery(); if(file.Exists) { file.DeleteObject(); clientContext.ExecuteQuery(); } PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(clientContext, web); clientContext.Load(publishingWeb); if (publishingWeb != null) { List publishingLayouts = clientContext.Site.RootWeb.Lists.GetByTitle("Master Page Gallery"); ListItemCollection allItems = publishingLayouts.GetItems(CamlQuery.CreateAllItemsQuery()); clientContext.Load(allItems, items => items.Include(item => item.DisplayName).Where(obj => obj.DisplayName == pagelayoutname)); clientContext.ExecuteQuery(); ListItem layout = allItems.Where(x => x.DisplayName == pagelayoutname).FirstOrDefault(); clientContext.Load(layout); PublishingPageInformation publishingpageInfo = new PublishingPageInformation() { Name = publishingPageName, PageLayoutListItem = layout, }; PublishingPage publishingPage = publishingWeb.AddPublishingPage(publishingpageInfo); publishingPage.ListItem.File.CheckIn(string.Empty, CheckinType.MajorCheckIn); publishingPage.ListItem.File.Publish(string.Empty); clientContext.ExecuteQuery(); } SetSupportCaseContent(clientContext, "SupportCasesPage", url, queryurl); }
创建列表项
public static void AddDemoDataToSupportCasesList(ClientContext ctx, List list, string title, string status, string csr, string customerID) { ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation(); ListItem newItem = list.AddItem(itemCreateInfo); newItem["Title"] = title; newItem["FTCAM_Status"] = status; newItem["FTCAM_CSR"] = csr; newItem["FTCAM_CustomerID"] = customerID; newItem.Update(); ctx.ExecuteQuery(); }
预配内容至发布页面(“搜索内容”Web 部件、“脚本编辑器”Web 部件、外接程序部件)并发布页面:
public static void SetSupportCaseContent(ClientContext ctx, string pageName, string url, string queryurl) { List pages = ctx.Web.Lists.GetByTitle("Pages"); ctx.Load(pages.RootFolder, f => f.ServerRelativeUrl); ctx.ExecuteQuery(); Microsoft.SharePoint.Client.File file = ctx.Web.GetFileByServerRelativeUrl(pages.RootFolder.ServerRelativeUrl + "/" + pageName + ".aspx"); ctx.Load(file); ctx.ExecuteQuery(); file.CheckOut(); LimitedWebPartManager limitedWebPartManager = file.GetLimitedWebPartManager(PersonalizationScope.Shared); string quicklaunchmenuFormat = @"<div><a href='{0}/{1}'>Sample Home Page</a></div> <br /> <div style='font-weight:bold'>CSR Dashboard</div> <div class='cdsm_mainmenu'> <ul> <li><a href='{0}/CSRInfo/{1}'>My CSR Info</a></li> <li><a href='{0}/CallQueue/{1}'>Call Queue</a></li> <li> <span class='collapse_arrow'></span> <span><a href='{0}/CustomerDashboard/{1}'>Customer Dashboard</a></span> <ul> <li><a href='{0}/CustomerDashboard/Orders{1}'>Recent Orders</a></li> <li><a class='current' href='#'>Support Cases</a></li> <li><a href='{0}/CustomerDashboard/Notes{1}'>Notes</a></li> </ul> </li> </ul> </div> <div class='cdsm_submenu'> </div>"; string quicklaunchmenu = string.Format(quicklaunchmenuFormat, url, queryurl); string qlwebPartXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><webParts><webPart xmlns=\"http://schemas.microsoft.com/WebPart/v3\"><metaData><type name=\"Microsoft.SharePoint.WebPartPages.ScriptEditorWebPart, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c\" /><importErrorMessage>Cannot import this web part.</importErrorMessage></metaData><data><properties><property name=\"Content\" type=\"string\"><![CDATA[" + quicklaunchmenu + "]]></property><property name=\"ChromeType\" type=\"chrometype\">None</property></properties></data></webPart></webParts>"; WebPartDefinition qlWpd = limitedWebPartManager.ImportWebPart(qlwebPartXml); WebPartDefinition qlWpdNew = limitedWebPartManager.AddWebPart(qlWpd.WebPart, "SupportCasesZoneLeft", 0); ctx.Load(qlWpdNew); //Customer Dropdown List Script web part string dpwebPartXml = System.IO.File.ReadAllText(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Assets/CustomerDropDownlist.webpart"); WebPartDefinition dpWpd = limitedWebPartManager.ImportWebPart(dpwebPartXml); WebPartDefinition dpWpdNew = limitedWebPartManager.AddWebPart(dpWpd.WebPart, "SupportCasesZoneTop", 0); ctx.Load(dpWpdNew); //Support Case CBS Info web part string cbsInfoWebPartXml = System.IO.File.ReadAllText(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Assets/SupportCaseCBSWebPartInfo.webpart"); WebPartDefinition cbsInfoWpd = limitedWebPartManager.ImportWebPart(cbsInfoWebPartXml); WebPartDefinition cbsInfoWpdNew = limitedWebPartManager.AddWebPart(cbsInfoWpd.WebPart, "SupportCasesZoneMiddle", 0); ctx.Load(cbsInfoWpdNew); //Support Case Content By Search web part string cbswebPartXml = System.IO.File.ReadAllText(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Assets/SupportCase CBS Webpart/SupportCaseCBS.webpart"); WebPartDefinition cbsWpd = limitedWebPartManager.ImportWebPart(cbswebPartXml); WebPartDefinition cbsWpdNew = limitedWebPartManager.AddWebPart(cbsWpd.WebPart, "SupportCasesZoneMiddle", 1); ctx.Load(cbsWpdNew); //Support Cases App Part string appPartXml = System.IO.File.ReadAllText(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Assets/SupportCaseAppPart.webpart"); WebPartDefinition appPartWpd = limitedWebPartManager.ImportWebPart(appPartXml); WebPartDefinition appPartdNew = limitedWebPartManager.AddWebPart(appPartWpd.WebPart, "SupportCasesZoneBottom", 0); ctx.Load(appPartdNew); //Get Host Web Query String and show support case list web part string querywebPartXml = System.IO.File.ReadAllText(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Assets/GetHostWebQueryStringAndShowList.webpart"); WebPartDefinition queryWpd = limitedWebPartManager.ImportWebPart(querywebPartXml); WebPartDefinition queryWpdNew = limitedWebPartManager.AddWebPart(queryWpd.WebPart, "SupportCasesZoneBottom", 1); ctx.Load(queryWpdNew); file.CheckIn("Data storage model", CheckinType.MajorCheckIn); file.Publish("Data storage model"); ctx.Load(file); ctx.ExecuteQuery(); }
FillHostWebSupportCasesToThreshold
有关将列表项部署到主机 Web 和外接程序 Web 的更多详细信息,请参阅 SharePointService.cs 类中的 和FillAppWebNotesListToThreshold
方法。重要
此示例中演示的相同主机 Web 和外接程序 Web 方法可以应用于任何类型的项目,以将它们部署到适当的位置。
将列表项添加到主机 Web 中的列表:
public string FillHostWebSupportCasesToThreshold() { using (var clientContext = SharePointContext.CreateUserClientContextForSPHost()) { List supportCasesList = clientContext.Web.Lists.GetByTitle("Support Cases"); ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation(); for (int i = 0; i < 500; i++) { ListItem newItem = supportCasesList.AddItem(itemCreateInfo); newItem["Title"] = "Wrong product received." + i.ToString(); newItem["FTCAM_Status"] = "Open"; newItem["FTCAM_CSR"] = "bjones"; newItem["FTCAM_CustomerID"] = "thresholds test"; newItem.Update(); if (i % 100 == 0) clientContext.ExecuteQuery(); } clientContext.ExecuteQuery(); clientContext.Load(supportCasesList, l => l.ItemCount); clientContext.ExecuteQuery(); if(supportCasesList.ItemCount>=5000) return "The Host Web Support Cases List has " + supportCasesList.ItemCount + " items, and exceeds the threshold."; else return 500 + " items have been added to the Host Web Support Cases List. " + "There are " + (5000 - supportCasesList.ItemCount) + " items left to add."; } }
将列表项添加到外接程序 Web 中的列表:
public string FillAppWebNotesListToThreshold() { using (var clientContext = SharePointContext.CreateUserClientContextForSPAppWeb()) { List notesList = clientContext.Web.Lists.GetByTitle("Notes"); var itemCreateInfo = new ListItemCreationInformation(); for (int i = 0; i < 500; i++) { ListItem newItem = notesList.AddItem(itemCreateInfo); newItem["Title"] = "Notes Title." + i.ToString(); newItem["FTCAM_Description"] = "Notes description"; newItem.Update(); if (i % 100 == 0) clientContext.ExecuteQuery(); } clientContext.ExecuteQuery(); clientContext.Load(notesList, l => l.ItemCount); clientContext.ExecuteQuery(); if (notesList.ItemCount >= 5000) return "The Add-in Web Notes List has " + notesList.ItemCount + " items, and exceeds the threshold."; else return 500 + " items have been added to the Add-in Web Notes List. " + "There are " + (5000-notesList.ItemCount) + " items left to add."; } }
相关链接
- 母版页(SharePoint 外接程序方法)
- 指南文章中 https://aka.ms/OfficeDevPnPGuidance
- MSDN 中的引用位于 https://aka.ms/OfficeDevPnPMSDN
- 视频中的 https://aka.ms/OfficeDevPnPVideos
PnP 示例
- Branding.ClientSideRendering(O365 PnP 代码示例)
- Branding.ApplyBranding(O365 PnP 代码示例)
- Branding.ClientSideRendering(O365 PnP 代码示例)
- 示例和内容(网址为 https://github.com/SharePoint/PnP)
适用于
- Office 365 多租户 (MT)
- Office 365 专用 (D) 部分
- SharePoint 2013 本地 – 部分
专用模式和本地模式在使用 SharePoint 外接程序模型技术方面完全相同,但在可以使用的可能的技术方面存在差异。