Módulos en el modelo de complemento de SharePoint

El enfoque que se usa para implementar artefactos en un entorno de SharePoint es diferente en el nuevo modelo de complemento de SharePoint que con código de plena confianza. En un escenario típico de solución de granja o código de plena confianza (FTC), se agregaron módulos definidos en código declarativo (archivos XML del marco de características) a las características de SharePoint. Los módulos incluían la lista de artefactos que se van a implementar en el servidor de SharePoint. Los módulos se agregaron a las características de SharePoint e implementaron a través de soluciones de SharePoint. Tras la activación de características, los artefactos definidos en los módulos se implementaron en el entorno de SharePoint.

En un escenario de modelo de complemento de SharePoint, el patrón de aprovisionamiento remoto se usa para implementar artefactos en entornos de SharePoint.

Terminología

En este artículo se hace referencia al término artefactos . Los artefactos hacen referencia a elementos que normalmente se implementan en un entorno de SharePoint. Los artefactos suelen incluir:

  • Archivos JavaScript
  • archivos CSS
  • Archivos de imagen (.jpg, .gif, .png, etc.)
  • Páginas maestras
  • Diseños de página
  • Elementos de lista

Directrices importantes

Como regla general, nos gustaría proporcionar las siguientes directrices de alto nivel para implementar artefactos en entornos de SharePoint.

  • Use el patrón de aprovisionamiento remoto (Modelo de objetos del lado cliente de SharePoint y API REST de SharePoint) para implementar artefactos en entornos de SharePoint.
  • No use módulos de código declarativos ni archivos XML del marco de características para implementar artefactos en entornos de SharePoint.

Depuración

Una gran ventaja del uso de código para implementar artefactos es que puede depurar el proceso de implementación cuando se usa código. Es imposible depurar el proceso de implementación cuando se usan módulos de código declarativos o archivos XML del marco de características para implementar artefactos en entornos de SharePoint.

Introducción

Los siguientes ejemplos de código PnP de Microsoft 365 muestran cómo crear complementos de SharePoint que usan el patrón de aprovisionamiento remoto para implementar artefactos en un entorno de SharePoint.

En este ejemplo se muestra cómo crear una nueva carpeta en la biblioteca de estilos y agregar archivos e imágenes de JavaScript a los nuevos archivos.

Creación de una carpeta y carga de archivos JavaScript en la carpeta

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);
}

Creación de una carpeta y carga de archivos de imagen en la carpeta

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();
  }
}

En este ejemplo se muestra cómo cargar páginas maestras, establecer metadatos de página maestra y aplicar la página maestra al sitio estableciendo la CustomMasterUrl propiedad en el objeto Web.

Eliminación de un archivo

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();
}

Eliminar una carpeta

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();
}

Desasigne una página maestra y elimine la página maestra.

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);
}

Eliminación de un diseño de página

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);
}

Este ejemplo tiene un poco de todo. Muestra cómo activar las características de publicación, cargar diseños de página, crear páginas de publicación, crear listas, tipos de contenido y elementos de lista, y crear páginas de publicación y agregar elementos web y elementos de complemento a las páginas. También se muestra cómo implementar elementos de lista tanto en la web host como en la web del complemento.

Activación de las características de publicación de la colección de sitios y el nivel de sitio

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();
}

Creación de una lista

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;
}

Crear un tipo de contenido

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;
}

Carga de un diseño de página

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();
    }
  }
}

Creación de una página de publicación y establecimiento de su diseño de página

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);
}

Crear elementos de lista

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();
}

Aprovisionamiento de contenido en una página de publicación (elemento web Content By Search, elemento web Editor de scripts, elemento de complemento) y publicación de la página

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();
}
  • Consulte los FillHostWebSupportCasesToThreshold métodos y FillAppWebNotesListToThreshold de la clase SharePointService.cs para obtener más información sobre cómo implementar elementos de lista tanto en la web host como en la web del complemento.

Importante

Los mismos enfoques web de host y web de complemento que se muestran en este ejemplo se pueden aplicar a cualquier tipo de artefacto para implementarlos en la ubicación adecuada.

Adición de elementos de lista a una lista en la web host

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.";
  }
}

Adición de elementos de lista a una lista en la web de complementos

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++)
    {
      ListIgit pushientContext.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.";
  }
}

Ejemplos de PnP

Se aplica a

  • Multiinquilino de Microsoft 365 (MT)
  • Microsoft 365 Dedicado (D) parcialmente
  • SharePoint 2013 local parcial

Los modelos para Office 365 dedicado y local son idénticos a las técnicas del modelo de complemento de SharePoint, pero existen algunas diferencias en las posibles tecnologías que se pueden usar.