Crear el primer conector de Microsoft Graph personalizado
Los conectores de Microsoft Graph permiten agregar sus propios datos a Microsoft Graph y hacer que puedan alimentar varias experiencias de Microsoft 365.
Esta aplicación de .NET Core muestra cómo usar la API de conectores de Microsoft Graph para crear un conector personalizado y usarlo para impulsar Microsoft Search. En este tutorial se usa un inventario de piezas de dispositivo de datos de ejemplo para la organización Contoso Appliance Repair.
¿Cómo funciona el ejemplo?
El ejemplo crea una aplicación de escritorio de Windows que adquiere un token de la Plataforma de identidad de Microsoft y lo usa para enviar solicitudes a la API de conectores de Microsoft Graph. La API de conectores envía su respuesta después de validar el acceso.
Requisitos previos
Instale Visual Studio 2019 con el SDK de .NET Core 3.1 en el equipo de desarrollo.
Asegúrese de que tiene una cuenta microsoft personal o una cuenta profesional o educativa.
Instale Entity Framework Core Tools como una herramienta global mediante el siguiente comando:
dotnet tool install --global dotnet-ef
Instale una herramienta para actualizar una base de datos SQLite. Por ejemplo, el explorador de base de datos para SQLite.
Descargue el archivo ApplianceParts.csv del repositorio de ejemplo del conector de búsqueda.
Sugerencia
La mejor manera de descargar archivos de GitHub es ir al nivel superior del proyecto. En el botón de descarga de código verde de la derecha, elija Descargar ZIP. El archivo ZIP contiene el contenido del repositorio.
Registro de la aplicación en el portal
Una vez que se cumplan todos los requisitos previos, podrá registrar una aplicación en el Centro de administración de Azure AD. El registro es necesario para autenticar la aplicación y usarla para realizar llamadas a la API de conectores de Microsoft Graph.
Vaya al Centro de administración de Azure Active Directory e inicie sesión con una cuenta de administrador.
En el panel izquierdo, seleccione Azure Active Directory y, en Administrar, seleccione Registros de aplicaciones.
Seleccione Nuevo registro.
Complete el formulario Registrar una aplicación con los valores siguientes y, a continuación, seleccione Registrar.
Nombre: Conector de inventario de piezas
Tipos de cuenta admitidos: solo cuentas en este directorio organizativo (solo Microsoft: inquilino único)
URI de redirección: deje en blanco
En la página información general de Parts Inventory Connector, copie los valores de Id. de aplicación (cliente) e Id. de directorio (inquilino). Necesitará ambos en la sección siguiente.
Seleccione Permisos de API en Administrar.
Seleccione Agregar un permiso y, a continuación, seleccione Microsoft Graph.
Seleccione Permisos de aplicación y, a continuación, seleccione el permiso ExternalItem.ReadWrite.All . Seleccione Agregar permisos.
Seleccione Conceder consentimiento de administrador para {TENANT}y, a continuación, seleccione Sí cuando se le solicite.
Seleccione Certificados & secretos en Administrar y, a continuación, seleccione Nuevo secreto de cliente.
Escriba una descripción, elija una hora de expiración para el secreto y, a continuación, seleccione Agregar.
Copie y guarde el nuevo secreto; lo necesitará en la sección siguiente.
Compilación de la aplicación
En este paso, creará una aplicación de consola de .NET Core. Después, creará una nueva conexión, registrará el esquema y sincronizará los elementos.
Crear una aplicación de consola de .NET Core
Inicie Visual Studio 2019 y vaya a Archivo>nuevo>proyecto.
Seleccione la plantilla Aplicación de consola (.NET Core) y, a continuación, seleccione Siguiente.
Escriba el nombre del proyecto: PartsInventoryConnector.
Active la casilla Colocar solución y proyecto en el mismo directorio y, a continuación, seleccione Crear.
Importante
Antes de pasar al paso siguiente, copie el archivo ApplianceParts.csv en la carpeta raíz del proyecto.
Agregar paquetes NuGet
Para agregar paquetes NuGet, primero haga clic con el botón derecho en Solución de proyecto y, a continuación, seleccione Abrir en terminal.
A continuación, ejecute los siguientes comandos de la interfaz de línea de comandos (CLI) en el símbolo del sistema para desarrolladores.
dotnet add package CsvHelper --version 12.1.2
dotnet add package Microsoft.EntityFrameworkCore.Design --version 3.1.3
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 3.1.3
dotnet add package Microsoft.Extensions.Configuration.UserSecrets --version 3.1.3
dotnet add package Microsoft.Graph.Beta --version 0.17.0-preview
dotnet add package Microsoft.Identity.Client --version 4.13.0
Sugerencia
Si se produce un error en el add package
comando, compruebe el origen del paquete del proyecto:
- Seleccione el proyecto en el Explorador de soluciones.
- Vaya a Herramientas>Configuración del Administrador de paquetes nuget del Administrador> depaquetes.
- Compruebe los orígenes de paquetes y asegúrese de que nuget.org está instalado como origen del paquete.
- Nombre: nuget.org
- Origen: https://api.nuget.org/v3/index.json
Agregar autenticación de Azure AD
Esta autenticación es necesaria para obtener el token de acceso de OAuth necesario para llamar a la API de conectores.
Cree un nuevo directorio denominado Authentication en el directorio PartsInventoryConnector .
Cree un nuevo archivo en el directorio Authentication denominado ClientCredentialAuthProvider.cs y, a continuación, coloque el código siguiente en ese archivo:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Microsoft.Identity.Client; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; namespace PartsInventoryConnector.Authentication { public class ClientCredentialAuthProvider : IAuthenticationProvider { private IConfidentialClientApplication _msalClient; private int _maxRetries = 3; public ClientCredentialAuthProvider(string appId, string tenantId, string secret) { _msalClient = ConfidentialClientApplicationBuilder .Create(appId) .WithTenantId(tenantId) .WithClientSecret(secret) .Build(); } public async Task AuthenticateRequestAsync(HttpRequestMessage request) { int retryCount = 0; do { try { var result = await _msalClient .AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" }) .ExecuteAsync(); if (!string.IsNullOrEmpty(result.AccessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken); break; } } catch (MsalServiceException serviceException) { if (serviceException.ErrorCode == "temporarily_unavailable") { await Task.Delay(10000); } else { throw serviceException; } } catch (Exception exception) { throw exception; } retryCount++; } while (retryCount < _maxRetries); } } }
Agregar experiencia de usuario
Cree un nuevo directorio en el directorio PartsInventoryConnector denominado Console.
Cree un nuevo archivo en el directorio de consola denominado MenuChoice.cs y, a continuación, coloque el código siguiente en ese archivo:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. namespace PartsInventoryConnector.Console { public enum MenuChoice { Invalid = 0, CreateConnection, RegisterSchema, PushAllItems, Exit } }
Configuración del modelo de datos
Cree un nuevo directorio en el directorio PartsInventoryConnector denominado Models.
Cree un nuevo archivo en el directorio Models denominado AppliancePart.cs y, a continuación, coloque el código siguiente en ese archivo:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Newtonsoft.Json; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PartsInventoryConnector.Models { public class AppliancePart { [Key] public int PartNumber { get; set; } public string Name { get; set; } public string Description { get; set; } public double Price { get; set; } public int Inventory { get; set; } [JsonProperty("appliances@odata.type")] private const string AppliancesODataType = "Collection(String)"; public List<string> Appliances { get; set; } public Properties AsExternalItemProperties() { var properties = new Properties { AdditionalData = new Dictionary<string, object> { { "partNumber", PartNumber }, { "name", Name }, { "description", Description }, { "price", Price }, { "inventory", Inventory }, { "appliances@odata.type", "Collection(String)" }, { "appliances", Appliances } } }; return properties; } } }
Cree un nuevo archivo en el directorio Models denominado ApplianceDbContext.cs y, a continuación, coloque el código siguiente en ese archivo:
using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; namespace PartsInventoryConnector.Models { public class ApplianceDbContext : DbContext { public DbSet<AppliancePart> Parts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseSqlite("Data Source=parts.db"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { // EF Core can't store lists, so add a converter for the Appliances // property to serialize as a JSON string on save to DB modelBuilder.Entity<AppliancePart>() .Property(ap => ap.Appliances) .HasConversion( v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject<List<string>>(v) ); // Add LastUpdated and IsDeleted shadow properties modelBuilder.Entity<AppliancePart>() .Property<DateTime>("LastUpdated") .HasDefaultValueSql("datetime()") .ValueGeneratedOnAddOrUpdate(); modelBuilder.Entity<AppliancePart>() .Property<bool>("IsDeleted") .IsRequired() .HasDefaultValue(false); // Exclude any soft-deleted items (IsDeleted = 1) from // the default query sets modelBuilder.Entity<AppliancePart>() .HasQueryFilter(a => !EF.Property<bool>(a, "IsDeleted")); } public override int SaveChanges() { // Prevent deletes of data, instead mark the item as deleted // by setting IsDeleted = true. foreach(var entry in ChangeTracker.Entries() .Where(e => e.State == EntityState.Deleted)) { if (entry.Entity.GetType() == typeof(AppliancePart)) { SoftDelete(entry); } } return base.SaveChanges(); } private void SoftDelete(EntityEntry entry) { var partNumber = new SqliteParameter("@partNumber", entry.OriginalValues["PartNumber"]); Database.ExecuteSqlRaw( "UPDATE Parts SET IsDeleted = 1 WHERE PartNumber = @partNumber", partNumber); entry.State = EntityState.Detached; } } }
Cree un nuevo directorio denominado Data en el directorio PartsInventoryConnector .
Cree un nuevo archivo en el directorio Data denominado CsvDataLoader.cs y, a continuación, coloque el código siguiente en ese archivo:
using CsvHelper; using CsvHelper.Configuration; using CsvHelper.TypeConversion; using PartsInventoryConnector.Models; using System.Collections.Generic; using System.IO; using System.Linq; namespace PartsInventoryConnector.Data { public static class CsvDataLoader { public static List<AppliancePart> LoadPartsFromCsv(string filePath) { using (var reader = new StreamReader(filePath)) using (var csv = new CsvReader(reader)) { csv.Configuration.RegisterClassMap<AppliancePartMap>(); return new List<AppliancePart>(csv.GetRecords<AppliancePart>()); } } } public class ApplianceListConverter : DefaultTypeConverter { public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) { var appliances = text.Split(';'); return new List<string>(appliances); } } public class AppliancePartMap : ClassMap<AppliancePart> { public AppliancePartMap() { Map(m => m.PartNumber); Map(m => m.Name); Map(m => m.Description); Map(m => m.Price); Map(m => m.Inventory); Map(m => m.Appliances).TypeConverter<ApplianceListConverter>(); } } }
Escritura del servicio auxiliar de Microsoft Graph
Cree un nuevo directorio denominado MicrosoftGraph en el directorio PartsInventoryConnector .
Cree un nuevo archivo en el directorio MicrosoftGraph denominado CustomSerializer.cs y, a continuación, coloque el código siguiente en ese archivo:
using Microsoft.Graph; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using System; using System.IO; namespace PartsInventoryConnector.MicrosoftGraph { // The Microsoft Graph SDK serializes enumerations in camelCase. // The Microsoft Graph service currently requires the PropertyType enum // to be PascalCase. This will override the Microsoft Graph serialization // If the Microsoft Graph service changes to accept camelCase this will no // longer be necessary. class CustomContractResolver : DefaultContractResolver { protected override JsonConverter ResolveContractConverter(Type objectType) { if (typeof(PropertyType).IsAssignableFrom(objectType)) { // This default converter uses PascalCase return new StringEnumConverter(); } return base.ResolveContractConverter(objectType); } } // In order to hook up the custom contract resolver for // PropertyType, we need to implement a custom serializer to // pass to the MicrosoftGraphServiceClient. public class CustomSerializer : ISerializer { private Serializer _microsoftGraphSerializer; private JsonSerializerSettings _jsonSerializerSettings; public CustomSerializer() { _microsoftGraphSerializer = new Serializer(); _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CustomContractResolver() }; } // For deserialize, just pass through to the default // Microsoft Graph SDK serializer public T DeserializeObject<T>(Stream stream) { return _microsoftGraphSerializer.DeserializeObject<T>(stream); } // For deserialize, just pass through to the default // Microsoft Graph SDK serializer public T DeserializeObject<T>(string inputString) { return _microsoftGraphSerializer.DeserializeObject<T>(inputString); } public string SerializeObject(object serializeableObject) { // If a Schema object is being serialized, do the conversion // ourselves if (serializeableObject is Schema) { var foo = JsonConvert.SerializeObject(serializeableObject, _jsonSerializerSettings); return foo; } // Otherwise, just pass through to the default Microsoft Graph SDK serializer return _microsoftGraphSerializer.SerializeObject(serializeableObject); } } }
Cree un nuevo archivo en el directorio de Microsoft Graph denominado MicrosoftGraphHelper.cs y, a continuación, coloque el código siguiente en ese archivo. Este código contiene métodos que usan MicrosoftGraphServiceClient para compilar y enviar llamadas al servicio Microsoft Graph y procesar la respuesta.
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Newtonsoft.Json; using System.Net.Http; using System.Threading.Tasks; namespace PartsInventoryConnector.MicrosoftGraph { public class MicrosoftGraphHelper { private GraphServiceClient _microsoftGraphClient; public MicrosoftGraphHelper(IAuthenticationProvider authProvider) { // Configure a default HttpProvider with our // custom serializer to handle the PropertyType serialization var serializer = new CustomSerializer(); var httpProvider = new HttpProvider(serializer); // Initialize the Microsoft Graph client _microsoftGraphClient = new GraphServiceClient(authProvider, httpProvider); } } }
Inicializar el servicio auxiliar de Microsoft Graph
Abra Program.cs y reemplace todo el contenido por el código siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using PartsInventoryConnector.Authentication;
using PartsInventoryConnector.Console;
using PartsInventoryConnector.Data;
using PartsInventoryConnector.MicrosoftGraph;
using PartsInventoryConnector.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace PartsInventoryConnector
{
class Program
{
private static MicrosoftGraphHelper _microsoftGraphHelper;
private static ExternalConnection _currentConnection;
private static string _tenantId;
static async Task Main(string[] args)
{
try
{
// Load configuration from appsettings.json
var appConfig = LoadAppSettings();
if (appConfig == null)
{
return;
}
// Save tenant ID for setting ACL on items
_tenantId = appConfig["tenantId"];
// Initialize the auth provider
var authProvider = new ClientCredentialAuthProvider(
appConfig["appId"],
appConfig["tenantId"],
appConfig["appSecret"]
);
// Check if the database is empty
using (var db = new ApplianceDbContext())
{
if (db.Parts.IgnoreQueryFilters().Count() <= 0)
{
ImportCsvToDatabase(db, "ApplianceParts.csv");
}
}
_microsoftGraphHelper = new MicrosoftGraphHelper(authProvider);
do
{
var userChoice = DoMenuPrompt();
switch (userChoice)
{
case MenuChoice.CreateConnection:
await CreateConnectionAsync();
break;
case MenuChoice.RegisterSchema:
await RegisterSchemaAsync();
break;
case MenuChoice.PushAllItems:
await UpdateItemsFromDatabase();
break;
case MenuChoice.Exit:
// Exit the program
return;
case MenuChoice.Invalid:
default:
break;
}
} while (true);
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}
}
private static void ImportCsvToDatabase(ApplianceDbContext db, string partsFilePath)
{
var parts = CsvDataLoader.LoadPartsFromCsv(partsFilePath);
db.AddRange(parts);
db.SaveChanges();
}
private static MenuChoice DoMenuPrompt()
{
System.Console.WriteLine($"Current connection: {(_currentConnection == null ? "NONE" : _currentConnection.Name)}");
System.Console.WriteLine("Please choose one of the following options:");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.CreateConnection)}. Create a connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.RegisterSchema)}. Register schema for current connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.PushAllItems)}. Push ALL items to current connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.Exit)}. Exit");
try
{
var choice = int.Parse(System.Console.ReadLine());
return (MenuChoice)choice;
}
catch (FormatException)
{
return MenuChoice.Invalid;
}
}
private static string PromptForInput(string prompt, bool valueRequired)
{
string response = null;
do
{
System.Console.WriteLine($"{prompt}:");
response = System.Console.ReadLine();
if (valueRequired && string.IsNullOrEmpty(response))
{
System.Console.WriteLine("You must provide a value");
}
} while (valueRequired && string.IsNullOrEmpty(response));
return response;
}
private static IConfigurationRoot LoadAppSettings()
{
var appConfig = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
// Check for required settings
if (string.IsNullOrEmpty(appConfig["appId"]) ||
string.IsNullOrEmpty(appConfig["appSecret"]) ||
string.IsNullOrEmpty(appConfig["tenantId"]))
{
return null;
}
return appConfig;
}
}
}
Creación de la conexión
En MicrosoftGraph, abra el archivo MicrosoftGraphHelper.cs y agregue el código siguiente después del método Constructor :
public async Task<ExternalConnection> CreateConnectionAsync(string id, string name, string description) { var newConnection = new ExternalConnection { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, Id = id, Name = name, Description = description }; return await _microsoftGraphClient.External.Connections.Request().AddAsync(newConnection); }
Abra el archivo Program.cs y agregue el código siguiente después del método Main :
private static async Task CreateConnectionAsync() { var connectionId = PromptForInput("Enter a unique ID for the new connection", true); var connectionName = PromptForInput("Enter a name for the new connection", true); var connectionDescription = PromptForInput("Enter a description for the new connection", false); try { // Create the connection _currentConnection = await _microsoftGraphHelper.CreateConnectionAsync(connectionId, connectionName, connectionDescription); System.Console.WriteLine("New connection created"); System.Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(_currentConnection, Newtonsoft.Json.Formatting.Indented)); } catch (ServiceException serviceException) { System.Console.WriteLine(serviceException.Message); return; } }
Registro del esquema
En MicrosoftGraph, abra el archivo MicrosoftGraphHelper.cs y agregue el código siguiente después del método Constructor :
public async Task RegisterSchemaAsync(string connectionId, Schema schema) { // Need access to the HTTP response here since we are doing an // async request. The new schema object isn't returned, we need // the Location header from the response var asyncNewSchemaRequest = _microsoftGraphClient.External.Connections[connectionId].Schema .Request() .Header("Prefer", "respond-async") .GetHttpRequestMessage(); asyncNewSchemaRequest.Method = HttpMethod.Post; asyncNewSchemaRequest.Content = _microsoftGraphClient.HttpProvider.Serializer.SerializeAsJsonContent(schema); var response = await _microsoftGraphClient.HttpProvider.SendAsync(asyncNewSchemaRequest); if (response.IsSuccessStatusCode) { // Get the operation ID from the Location header var operationId = ExtractOperationId(response.Headers.Location); await CheckSchemaStatusAsync(connectionId, operationId); } else { throw new ServiceException( new Error { Code = response.StatusCode.ToString(), Message = "Registering schema failed" } ); } } private string ExtractOperationId(System.Uri uri) { int numSegments = uri.Segments.Length; return uri.Segments[numSegments - 1]; } public async Task CheckSchemaStatusAsync(string connectionId, string operationId) { do { var operation = await _microsoftGraphClient.External.Connections[connectionId] .Operations[operationId] .Request() .GetAsync(); if (operation.Status == ConnectionOperationStatus.Completed) { return; } else if (operation.Status == ConnectionOperationStatus.Failed) { throw new ServiceException( new Error { Code = operation.Error.ErrorCode, Message = operation.Error.Message } ); } await Task.Delay(3000); } while (true); }
Abra el archivo Program.cs y agregue el código siguiente después del método Main :
private static async Task RegisterSchemaAsync() { if (_currentConnection == null) { System.Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } System.Console.WriteLine("Registering schema, this may take a moment..."); try { // Register the schema var schema = new Schema { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, BaseType = "microsoft.graph.externalItem", Properties = new List<Property> { new Property { Name = "partNumber", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "name", Type = PropertyType.String, IsQueryable = true, IsSearchable = true, IsRetrievable = true }, new Property { Name = "description", Type = PropertyType.String, IsQueryable = false, IsSearchable = true, IsRetrievable = true }, new Property { Name = "price", Type = PropertyType.Double, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "inventory", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "appliances", Type = PropertyType.StringCollection, IsQueryable = true, IsSearchable = true, IsRetrievable = true } } }; await _microsoftGraphHelper.RegisterSchemaAsync(_currentConnection.Id, schema); System.Console.WriteLine("Schema registered"); } catch (ServiceException serviceException) { System.Console.WriteLine($"{serviceException.StatusCode} error registering schema:"); System.Console.WriteLine(serviceException.Message); return; } }
Sincronizar elementos
En Microsoft Graph, abra el archivo MicrosoftGraphHelper.cs y agregue el código siguiente después del método Constructor :
public async Task AddOrUpdateItem(string connectionId, ExternalItem item) { // The SDK's auto-generated request builder uses POST here, // which isn't correct. For now, get the HTTP request and change it // to PUT manually. var putItemRequest = _microsoftGraphClient.External.Connections[connectionId] .Items[item.Id].Request().GetHttpRequestMessage(); putItemRequest.Method = HttpMethod.Put; putItemRequest.Content = _microsoftGraphClient.HttpProvider.Serializer.SerializeAsJsonContent(item); var response = await _microsoftGraphClient.HttpProvider.SendAsync(putItemRequest); if (!response.IsSuccessStatusCode) { throw new ServiceException( new Error { Code = response.StatusCode.ToString(), Message = "Error indexing item." } ); } }
Abra el archivo Program.cs y agregue el código siguiente después del método Main :
private static async Task UpdateItemsFromDatabase() { if (_currentConnection == null) { System.Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } List<AppliancePart> partsToUpload = null; using (var db = new ApplianceDbContext()) { partsToUpload = db.Parts.ToList(); } System.Console.WriteLine($"Processing {partsToUpload.Count()} add/updates"); foreach (var part in partsToUpload) { var newItem = new ExternalItem { Id = part.PartNumber.ToString(), Content = new ExternalItemContent { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, Type = ExternalItemContentType.Text, Value = part.Description }, Acl = new List<Acl> { new Acl { AccessType = AccessType.Grant, Type = AclType.Everyone, Value = _tenantId, IdentitySource = "Azure Active Directory" } }, Properties = part.AsExternalItemProperties() }; try { System.Console.Write($"Uploading part number {part.PartNumber}..."); await _microsoftGraphHelper.AddOrUpdateItem(_currentConnection.Id, newItem); System.Console.WriteLine("DONE"); } catch (ServiceException serviceException) { System.Console.WriteLine("FAILED"); System.Console.WriteLine($"{serviceException.StatusCode} error adding or updating part {part.PartNumber}"); System.Console.WriteLine(serviceException.Message); } } }
Configuración de la aplicación
Abra la interfaz de línea de comandos (CLI) en el directorio donde se encuentra PartsInventoryConnector.csproj.
Ejecute el siguiente comando para inicializar los secretos de usuario para el proyecto.
dotnet user-secrets init
Ejecute los siguientes comandos para almacenar el identificador de aplicación, el secreto de aplicación y el identificador de inquilino en el almacén de secretos de usuario.
dotnet user-secrets set appId "YOUR_APP_ID_HERE" dotnet user-secrets set appSecret "YOUR_APP_SECRET_HERE" dotnet user-secrets set tenantId "YOUR_TENANT_ID_HERE"
Creación de la base de datos a partir de CSV
Abra la interfaz de línea de comandos (CLI) en el directorio donde se encuentra PartsInventoryConnector.csproj.
Ejecute los comandos siguientes:
dotnet ef migrations add InitialCreate dotnet ef database update
Nota:
Ejecute los siguientes comandos si un esquema cambia en el archivo CSV y refleje esos cambios en la base de datos SQLite.
dotnet ef database drop
dotnet ef database update
Ejecución de la aplicación
En este paso, compilará y ejecutará el ejemplo. Este ejemplo de código creará una nueva conexión, registrará el esquema y, a continuación, insertará elementos del archivo ApplianceParts.csv en esa conexión.
- Abra la interfaz de línea de comandos (CLI) en el directorio PartsInventoryConnector .
- Use el comando
dotnet build
para compilar el ejemplo. - Use el comando
dotnet run
para ejecutar el ejemplo. - Seleccione 1. Cree una conexión. Escriba un identificador, un nombre y una descripción únicos para esa conexión.
- Seleccione 2. Registre el esquema de la opción de conexión actual y espere a que se complete la operación.
- Seleccione 3. Inserte todos los elementos en la conexión actual.
Nota:
Si el paso 5 produce un error, espere unos minutos y seleccione 3. Inserte todos los elementos en la conexión actual.
Exponer los datos en la búsqueda
Para facilitar a los usuarios encontrar la información que tienen permiso para ver, cree verticales de búsqueda y tipos de resultados para personalizar los resultados de búsqueda en Microsoft SharePoint, Microsoft Office y Microsoft Search en Bing.
Creación de un vertical
Para crear y habilitar una búsqueda vertical en el nivel de organización, inicie sesión en el Centro de administración de Microsoft 365 mediante el rol de administrador global y haga lo siguiente:
- Vaya a Configuración Personalizaciones> de inteligencia >debúsqueda&.
- Vaya a Vertical y seleccione Agregar.
- Proporcione los detalles siguientes:
Asigne el nombre vertical: Piezas del dispositivo.
Origen de contenido: conector creado con la aplicación (inventario de piezas).
Agregar una consulta: deje en blanco.
Filtros: deje en blanco.
Creación de un tipo de resultado
Para crear un tipo de resultado:
- Vaya a Configuración Personalizaciones> de inteligencia >debúsqueda&.
- Vaya a la pestaña Tipo de resultado y seleccione Agregar.
- Proporcione los detalles siguientes:
Nombre: elemento del dispositivo
Origen de contenido: conector creado en la aplicación.
Reglas: Ninguna
Pegue el contenido de result-type.json en el cuadro de texto del diseñador de diseños.
Búsqueda de resultados
En este paso, buscará elementos en SharePoint.
Vaya al sitio raíz de SharePoint del inquilino.
Con el cuadro de búsqueda de la parte superior de la página, busque bisagra.
Cuando la búsqueda se complete con 0 resultados, seleccione la pestaña Piezas del dispositivo . Se muestran los resultados del conector.
Resumen
Ha completado correctamente el tutorial de conectores de Microsoft Graph de .NET Core: ha creado un conector personalizado y lo ha usado para impulsar Microsoft Search.
Pasos siguientes
Para obtener más información sobre todos los datos a los que puede acceder con el conector personalizado, consulte Introducción a los conectores de Microsoft Graph.
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.
Comentarios
Enviar y ver comentarios de