Ejercicio: Importación de contenido externo

Completado

En este ejercicio, ampliará el conector personalizado de Microsoft Graph con el código para importar archivos markdown locales a Microsoft 365.

Antes de empezar

Antes de realizar este ejercicio, asegúrese de completar el ejercicio anterior en este módulo.

Para seguir este ejercicio, copie los archivos de contenido de ejemplo usados en este ejercicio desde GitHub y almacénelos en el proyecto, en una carpeta denominada content.

Captura de pantalla de un editor de código que muestra los archivos de contenido usados en este ejercicio.

Para que el código funcione correctamente, la carpeta de contenido y su contenido deben copiarse en la carpeta de salida de compilación.

En el editor de código:

  1. Abra el archivo .csproj y, antes de la </Project> etiqueta, agregue el código siguiente:

    <ItemGroup>
      <ContentFiles Include="content\**" CopyToOutputDirectory="PreserveNewest" />
    </ItemGroup>
    
    <Target Name="CopyContentFolder" AfterTargets="Build">
      <Copy SourceFiles="@(ContentFiles)" DestinationFiles="@(ContentFiles->'$(OutputPath)\content\%(RecursiveDir)%(Filename)%(Extension)')" />
    </Target>
    
  2. Guarde los cambios.

Adición de bibliotecas para analizar Markdown y YAML

El conector de Microsoft Graph que va a crear importa archivos markdown locales a Microsoft 365. Cada uno de estos archivos contiene un encabezado con metadatos en formato YAML, también conocido como frontmatter. Además, el contenido de cada archivo se escribe en Markdown. Para extraer metadatos y convertir el cuerpo en HTML, use bibliotecas personalizadas:

  1. Abra un terminal y cambie el directorio de trabajo al proyecto.
  2. Para agregar la biblioteca de procesamiento de Markdown, ejecute el siguiente comando: dotnet add package Markdig.
  3. Para agregar la biblioteca de procesamiento de YAML, ejecute el siguiente comando: dotnet add package YamlDotNet.

Definición de la clase para representar el archivo importado

Para simplificar el trabajo con archivos markdown importados y su contenido, vamos a definir una clase con las propiedades necesarias.

En el editor de código:

  1. Abra el archivo ContentService.cs .

  2. Agregue la using instrucción y actualice la clase de la Document siguiente manera:

    using YamlDotNet.Serialization;
    
    public interface IMarkdown
    {
      string? Markdown { get; set; }
    }
    
    class Document : IMarkdown
    {
      [YamlMember(Alias = "title")]
      public string? Title { get; set; }
      [YamlMember(Alias = "description")]
      public string? Description { get; set; }
      public string? Markdown { get; set; }
      public string? Content { get; set; }
      public string? RelativePath { get; set; }
      public string? IconUrl { get; set; }
      public string? Url { get; set; }
    }
    

    La IMarkdown interfaz representa el contenido del archivo markdown local. Debe definirse por separado para admitir la deserialización del contenido del archivo. La Document clase , representa el documento final con propiedades YAML analizadas y contenido HTML. YamlMember los atributos asignan propiedades a los metadatos del encabezado de cada documento.

  3. Guarde los cambios.

Implementación de la carga de archivos markdown

El siguiente paso es implementar la lógica que carga los archivos markdown locales, extrae metadatos y convierte el contenido en HTML.

En primer lugar, agregue métodos auxiliares para usar fácilmente las Markdig bibliotecas y YamlDotNet .

En el editor de código:

  1. Cree un nuevo archivo denominado MarkdownExtensions.cs.

  2. En el archivo, agregue el código siguiente:

    // from: https://khalidabuhakmeh.com/parse-markdown-front-matter-with-csharp
    using Markdig;
    using Markdig.Extensions.Yaml;
    using Markdig.Syntax;
    using YamlDotNet.Serialization;
    
    public static class MarkdownExtensions
    {
      private static readonly IDeserializer YamlDeserializer =
          new DeserializerBuilder()
          .IgnoreUnmatchedProperties()
          .Build();
    
      private static readonly MarkdownPipeline Pipeline
          = new MarkdownPipelineBuilder()
          .UseYamlFrontMatter()
          .Build();
    }
    

    La YamlDeserializer propiedad define un nuevo deserializador para el bloque YAML en cada uno de los archivos markdown que se van a extraer. Configure el deserializador para omitir todas las propiedades que no forman parte de la clase a la que se deserializa el archivo.

    La Pipeline propiedad define una canalización de procesamiento para el analizador de Markdown. Se configura para analizar el encabezado YAML. Sin esta configuración, se descartaría la información del encabezado.

  3. A continuación, extienda la MarkdownExtensions clase con el código siguiente:

    public static T GetContents<T>(this string markdown) where T : IMarkdown, new()
    {
      var document = Markdown.Parse(markdown, Pipeline);
      var block = document
          .Descendants<YamlFrontMatterBlock>()
          .FirstOrDefault();
    
      if (block == null)
        return new T { Markdown = markdown };
    
      var yaml =
          block
          // this is not a mistake
          // we have to call .Lines 2x
          .Lines // StringLineGroup[]
          .Lines // StringLine[]
          .OrderByDescending(x => x.Line)
          .Select(x => $"{x}\n")
          .ToList()
          .Select(x => x.Replace("---", string.Empty))
          .Where(x => !string.IsNullOrWhiteSpace(x))
          .Aggregate((s, agg) => agg + s);
    
      var t = YamlDeserializer.Deserialize<T>(yaml);
      t.Markdown = markdown.Substring(block.Span.End + 1);
      return t;
    }
    

    El GetContents método convierte una cadena markdown con metadatos YAML en el encabezado en el tipo especificado, que implementa la IMarkdown interfaz. De la cadena markdown, extrae el encabezado YAML y lo deserializa en el tipo especificado. A continuación, extrae el cuerpo del artículo y lo establece en la Markdown propiedad para su posterior procesamiento.

  4. Guarde los cambios.

Extracción de contenido de Markdown y YAML

Con los métodos auxiliares implementados, implemente el Extract método para cargar los archivos markdown locales y extraer información de ellos.

En el editor de código:

  1. Abra el archivo ContentService.cs .

  2. En la parte superior del archivo, agregue la siguiente instrucción using:

    using Markdig;
    
  3. A continuación, en la ContentService clase , implemente el Extract método mediante el código siguiente:

    static IEnumerable<Document> Extract()
    {
      var docs = new List<Document>();
    
      var contentFolder = "content";
      var baseUrl = new Uri("https://learn.microsoft.com/graph/");
      var contentFolderPath = Path.Combine(Directory.GetCurrentDirectory(), contentFolder);
      var files = Directory.GetFiles(contentFolder, "*.md", SearchOption.AllDirectories);
    
      foreach (var file in files)
      {
        try
        {
          var contents = File.ReadAllText(file);
          var doc = contents.GetContents<Document>();
          doc.Content = Markdown.ToHtml(doc.Markdown ?? "");
          doc.RelativePath = Path.GetRelativePath(contentFolderPath, file);
          doc.Url = new Uri(baseUrl, doc.RelativePath!.Replace(".md", "")).ToString();
          doc.IconUrl = "https://raw.githubusercontent.com/waldekmastykarz/img/main/microsoft-graph.png";
          docs.Add(doc);
        }
        catch (Exception ex)
        {
          Console.WriteLine(ex.Message);
        }
      }
    
      return docs;
    }
    

    El método comienza con la carga de archivos markdown desde la carpeta de contenido . Para cada archivo, carga su contenido como una cadena. Convierte la cadena en un objeto con los metadatos y el contenido almacenados en propiedades independientes mediante el GetContents método de extensión definido anteriormente en la MarkdownExtensions clase . A continuación, convierte la cadena markdown en HTML. Compila su dirección URL en Internet mediante la ruta de acceso relativa del archivo. Por último, almacena la ruta de acceso relativa al archivo y agrega el objeto a una colección para su posterior procesamiento.

  4. Guarde los cambios.

Transformar contenido en elementos externos

Después de leer el contenido externo, el siguiente paso es transformarlo en elementos externos, que se cargarán en Microsoft 365.

Comience con la implementación del GetDocId método que genera un identificador único para cada elemento externo en función de su ruta de acceso de archivo relativa.

En el editor de código:

  1. Confirme que está editando el archivo ContentService.cs .

  2. En la ContentService clase , agregue el método siguiente:

    static string GetDocId(Document doc)
    {
      var id = doc.RelativePath!
        .Replace(Path.DirectorySeparatorChar.ToString(), "__")
        .Replace(".md", "");
      return id;
    }
    

    El GetDocId método usa la ruta de acceso de archivo relativa del documento y reemplaza todos los separadores de directorios por un carácter de subrayado doble. Esto es necesario porque los caracteres separadores de ruta de acceso no se pueden usar en un identificador de elemento externo.

  3. Guarde los cambios.

Ahora, implemente el Transform método , que convierte objetos que representan archivos markdown locales en elementos externos de Microsoft Graph.

En el editor de código:

  1. Confirme que está en el archivo ContentService.cs .

  2. Implemente el Transform método con el código siguiente:

    static IEnumerable<ExternalItem> Transform(IEnumerable<Document> documents)
    {
      return documents.Select(doc =>
      {
        var docId = GetDocId(doc);
        return new ExternalItem
        {
          Id = docId,
          Properties = new()
          {
            AdditionalData = new Dictionary<string, object> {
                { "title", a.Title ?? "" },
                { "description", a.Description ?? "" },
                { "url", new Uri(baseUrl, a.RelativePath!.Replace(".md", "")).ToString() }
            }
          },
          Content = new()
          {
            Value = a.Content ?? "",
            Type = ExternalItemContentType.Html
          },
          Acl = new()
          {
              new()
              {
                Type = AclType.Everyone,
                Value = "everyone",
                AccessType = AccessType.Grant
              }
          }
        };
      });
    }
    

    En primer lugar, defina una dirección URL base. Esta dirección URL se usa para crear una dirección URL completa para cada elemento, de modo que cuando el elemento se muestre a los usuarios, puedan navegar hasta el elemento original. A continuación, transformará cada elemento de en DocsArticle un ExternalItemobjeto . Para empezar, obtenga un identificador único para cada elemento en función de su ruta de acceso de archivo relativa. A continuación, cree una nueva instancia de ExternalItem y rellene sus propiedades con información de DocsArticle. A continuación, establezca el contenido del elemento en el contenido HTML extraído del archivo local y establezca el tipo de contenido del elemento en HTML. Por último, configure el permiso del elemento para que sea visible para todos los usuarios de la organización.

  3. Guarde los cambios.

Carga de elementos externos en Microsoft 365

El último paso para procesar el contenido es cargar los elementos externos transformados en Microsoft 365.

En el editor de código:

  1. Compruebe que está editando el archivo ContentService.cs .

  2. En la ContentService clase , implemente el Load método mediante el código siguiente:

    static async Task Load(IEnumerable<ExternalItem> items)
    {
      foreach (var item in items)
      {
        Console.Write(string.Format("Loading item {0}...", item.Id));
    
        try
        {
          await GraphService.Client.External
            .Connections[Uri.EscapeDataString(ConnectionConfiguration.   ExternalConnection.Id!)]
            .Items[item.Id]
            .PutAsync(item);
    
          Console.WriteLine("DONE");
        }
        catch (Exception ex)
        {
          Console.WriteLine("ERROR");
          Console.WriteLine(ex.Message);
        }
      }
    }
    

    Para cada elemento externo, use el SDK de .NET de Microsoft Graph para llamar a Microsoft Graph API y cargar el elemento. En la solicitud, especifique el identificador de la conexión externa creada anteriormente, el identificador del elemento que se va a cargar y el contenido completo del elemento.

  3. Guarde los cambios.

Agregar el comando de carga de contenido

Para poder probar el código, debe ampliar la aplicación de consola con un comando que invoque la lógica de carga de contenido.

En el editor de código:

  1. Abra el archivo Program.cs .

  2. Agregue un nuevo comando para cargar contenido mediante el código siguiente:

    var loadContentCommand = new Command("load-content", "Loads content   into the external connection");
    loadContentCommand.SetHandler(ContentService.LoadContent);
    
  3. Registre el comando recién definido con el comando raíz para que se pueda invocar mediante el código siguiente:

    rootCommand.AddCommand(loadContentCommand);
    
  4. Guarde los cambios.

Comprobación del código

Lo último que queda es probar que el conector de Microsoft Graph importa correctamente contenido externo.

  1. Abra un terminal
  2. Cambie el directorio de trabajo al proyecto.
  3. Compile el proyecto mediante la ejecución del dotnet build comando .
  4. Empiece a cargar el contenido ejecutando el dotnet run -- load-content comando .
  5. Espere a que el comando se complete y cargue el contenido.