Créer votre premier connecteur Microsoft Graph personnalisé
Les connecteurs Microsoft Graph vous permettent d’ajouter vos propres données à Microsoft Graph et d’alimenter différentes expériences Microsoft 365.
Cette application .NET vous montre comment utiliser l’API de connecteurs Microsoft Graph pour créer un connecteur personnalisé et l’utiliser pour alimenter Recherche Microsoft. Ce tutoriel utilise un exemple d’inventaire des composants d’appliance de données pour l’organisation Contoso Appliance Repair.
Configuration requise
Sdk .NET installé sur votre ordinateur de développement.
Vous devez disposer d’un compte professionnel ou scolaire Microsoft avec le rôle Administrateur général. Si vous n’avez pas de locataire Microsoft 365, vous pouvez être éligible pour un client via le Programme pour les développeurs Microsoft 365 ; Pour plus d’informations, consultez la FAQ. Vous pouvez également vous inscrire à un essai gratuit de 1 mois ou acheter un plan Microsoft 365.
Installez Entity Framework Core Tools en tant qu’outil global à l’aide de la commande suivante :
dotnet tool install --global dotnet-ef
Installez un outil pour mettre à jour une base de données SQLite. Par exemple, le navigateur de base de données pour SQLite.
Téléchargez le fichier ApplianceParts.csv à partir de l’exemple de dépôt de connecteur de recherche.
Inscrire l’application sur le portail
Dans cet exercice, vous allez inscrire une nouvelle application dans l’ID Microsoft Entra pour activer l’authentification d’application uniquement. Les connecteurs Microsoft Graph utilisent l’authentification d’application uniquement pour accéder aux API du connecteur.
Inscrire l’application pour l’authentification d’application uniquement
Dans cette section, vous allez inscrire une application qui prend en charge l’authentification d’application uniquement à l’aide du flux d’informations d’identification du client.
Connectez-vous au Centre d'administration Microsoft 365.
Développez le menu >Identité, sélectionnez Applications>Inscriptions d’applications>Nouvelle inscription.
Entrez un nom pour votre application, par exemple .
Parts Inventory Connector
Définissez Types de comptes pris en chargesur Comptes dans cet annuaire organisationnel uniquement.
Laissez Redirect URI vide.
Sélectionner Inscription. Dans la page Vue d’ensemble de l’application, copiez la valeur de l’ID d’application (client) et de l’ID d’annuaire (locataire) et enregistrez-les. Vous aurez besoin de ces valeurs à l’étape suivante.
Sélectionnez Autorisations de l’API dans le volet de navigation gauche sous Gérer.
Supprimez l’autorisation User.Read par défaut sous Autorisations configurées en sélectionnant les points de suspension (...) dans sa ligne et en sélectionnant Supprimer l’autorisation.
Sélectionnez Ajouter une autorisation, puis Microsoft Graph.
Sélectionnez Autorisations d’application.
Sélectionnez ExternalConnection.ReadWrite.OwnedBy et ExternalItem.ReadWrite.OwnedBy, puis sélectionnez Ajouter des autorisations.
Sélectionnez Accorder le consentement de l’administrateur pour..., puis sélectionnez Oui pour fournir le consentement de l’administrateur pour l’autorisation sélectionnée.
Sélectionnez Certificats et secrets sous Gérer, puis sélectionnez Nouvelle clé secrète client.
Entrez une description, choisissez une durée, puis sélectionnez Ajouter.
Copiez le secret à partir de la colonne Valeur . Vous en aurez besoin dans les étapes suivantes.
Importante
Ce secret client n’apparaîtra plus jamais, aussi veillez à le copier maintenant.
Créez l’application
Commencez par créer un projet de console .NET à l’aide de l’interface CLI .NET.
Ouvrez votre interface de ligne de commande (CLI) dans un répertoire dans lequel vous souhaitez créer le projet. Exécutez la commande suivante :
dotnet new console -o PartsInventoryConnector
Une fois le projet créé, vérifiez qu’il fonctionne en remplaçant le répertoire actif par le répertoire PartsInventoryConnector et en exécutant la commande suivante dans votre interface CLI.
dotnet run
Si cela fonctionne, l’application doit générer
Hello, World!
.
Installer les dépendances
Avant de continuer, ajoutez des dépendances supplémentaires que vous utiliserez ultérieurement.
- Packages de configuration .NET pour lire la configuration de l’application à partir de appsettings.json.
- Bibliothèque de client Azure Identity pour .NET afin d’authentifier l’utilisateur et d’acquérir des jetons d’accès.
- Bibliothèque de client Microsoft Graph .NET pour effectuer des appels à Microsoft Graph.
- Packages Entity Framework pour accéder à une base de données locale.
- CsvHelper pour la lecture des fichiers CSV.
Exécutez les commandes suivantes dans votre interface CLI pour installer les dépendances.
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Azure.Identity
dotnet add package Microsoft.Graph
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package CsvHelper
Charger les paramètres de l’application
Dans cette section, vous ajoutez les détails de l’inscription de votre application au projet.
Ajoutez votre ID client, votre ID de locataire et votre clé secrète client au Gestionnaire de secrets .NET. Dans votre interface de ligne de commande, remplacez le répertoire par l’emplacement de PartsInventoryConnector.csproj et exécutez les commandes suivantes, en <remplaçant client-id> par votre ID client à partir de l’inscription de votre application, <tenant-id> par votre ID de locataire et <client-secret> par votre clé secrète client.
dotnet user-secrets init dotnet user-secrets set settings:clientId <client-id> dotnet user-secrets set settings:tenantId <tenant-id> dotnet user-secrets set settings:clientSecret <client-secret>
Créez un fichier dans le répertoire PartsInventoryConnector nommé Settings.cs et ajoutez le code suivant.
using Microsoft.Extensions.Configuration; namespace PartsInventoryConnector; public class Settings { public string? ClientId { get; set; } public string? ClientSecret { get; set; } public string? TenantId { get; set; } public static Settings LoadSettings() { // Load settings IConfiguration config = new ConfigurationBuilder() .AddUserSecrets<Program>() .Build(); return config.GetRequiredSection("Settings").Get<Settings>() ?? throw new Exception("Could not load app settings. See README for configuration instructions."); } }
Concevoir l’application
Dans cette section, vous créez un menu basé sur la console.
Ouvrez ./Program.cs et remplacez tout son contenu par le code suivant.
using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Graph; using Microsoft.Graph.Models.ExternalConnectors; using Microsoft.Graph.Models.ODataErrors; using PartsInventoryConnector; using PartsInventoryConnector.Data; using PartsInventoryConnector.Graph; Console.WriteLine("Parts Inventory Search Connector\n"); var settings = Settings.LoadSettings(); // Initialize Graph InitializeGraph(settings); ExternalConnection? currentConnection = null; int choice = -1; while (choice != 0) { Console.WriteLine($"Current connection: {(currentConnection == null ? "NONE" : currentConnection.Name)}\n"); Console.WriteLine("Please choose one of the following options:"); Console.WriteLine("0. Exit"); Console.WriteLine("1. Create a connection"); Console.WriteLine("2. Select an existing connection"); Console.WriteLine("3. Delete current connection"); Console.WriteLine("4. Register schema for current connection"); Console.WriteLine("5. View schema for current connection"); Console.WriteLine("6. Push updated items to current connection"); Console.WriteLine("7. Push ALL items to current connection"); Console.Write("Selection: "); try { choice = int.Parse(Console.ReadLine() ?? string.Empty); } catch (FormatException) { // Set to invalid value choice = -1; } switch(choice) { case 0: // Exit the program Console.WriteLine("Goodbye..."); break; case 1: currentConnection = await CreateConnectionAsync(); break; case 2: currentConnection = await SelectExistingConnectionAsync(); break; case 3: await DeleteCurrentConnectionAsync(currentConnection); currentConnection = null; break; case 4: await RegisterSchemaAsync(); break; case 5: await GetSchemaAsync(); break; case 6: await UpdateItemsFromDatabaseAsync(true, settings.TenantId); break; case 7: await UpdateItemsFromDatabaseAsync(false, settings.TenantId); break; default: Console.WriteLine("Invalid choice! Please try again."); break; } } static string? PromptForInput(string prompt, bool valueRequired) { string? response; do { Console.WriteLine($"{prompt}:"); response = Console.ReadLine(); if (valueRequired && string.IsNullOrEmpty(response)) { Console.WriteLine("You must provide a value"); } } while (valueRequired && string.IsNullOrEmpty(response)); return response; } static DateTime GetLastUploadTime() { if (File.Exists("lastuploadtime.bin")) { return DateTime.Parse( File.ReadAllText("lastuploadtime.bin")).ToUniversalTime(); } return DateTime.MinValue; } static void SaveLastUploadTime(DateTime uploadTime) { File.WriteAllText("lastuploadtime.bin", uploadTime.ToString("u")); }
Ajoutez les méthodes d’espace réservé suivantes à la fin du fichier. Vous les implémentez dans les étapes ultérieures.
void InitializeGraph(Settings settings) { // TODO } async Task<ExternalConnection?> CreateConnectionAsync() { // TODO throw new NotImplementedException(); } async Task<ExternalConnection?> SelectExistingConnectionAsync() { // TODO throw new NotImplementedException(); } async Task DeleteCurrentConnectionAsync(ExternalConnection? connection) { // TODO } async Task RegisterSchemaAsync() { // TODO } async Task GetSchemaAsync() { // TODO } async Task UpdateItemsFromDatabaseAsync(bool uploadModifiedOnly, string? tenantId) { // TODO }
Cela implémente un menu de base et lit le choix de l’utilisateur à partir de la ligne de commande.
Configurer Microsoft Graph
Dans cette section, vous allez configurer le client du Kit de développement logiciel (SDK) Microsoft Graph pour utiliser l’authentification d’application uniquement.
Créer une classe d’assistance
Créez un répertoire dans le répertoire PartsInventoryConnector nommé Graph.
Créez un fichier dans le répertoire Graph nommé GraphHelper.cs et ajoutez les instructions suivantes
using
.using Azure.Identity; using Microsoft.Graph; using Microsoft.Graph.Models.ExternalConnectors; using Microsoft.Kiota.Authentication.Azure;
Ajoutez un espace de noms et une définition de classe.
namespace PartsInventoryConnector.Graph; public static class GraphHelper { }
Ajoutez le code suivant à la
GraphHelper
classe , qui configure un avec l’authentificationGraphServiceClient
d’application uniquement.private static GraphServiceClient? graphClient; private static HttpClient? httpClient; public static void Initialize(Settings settings) { // Create a credential that uses the client credentials // authorization flow var credential = new ClientSecretCredential( settings.TenantId, settings.ClientId, settings.ClientSecret); // Create an HTTP client httpClient = GraphClientFactory.Create(); // Create an auth provider var authProvider = new AzureIdentityAuthenticationProvider( credential, scopes: new[] { "https://graph.microsoft.com/.default" }); // Create a Graph client using the credential graphClient = new GraphServiceClient(httpClient, authProvider); }
Remplacez la fonction vide
InitializeGraph
dans Program.cs par ce qui suit.void InitializeGraph(Settings settings) { try { GraphHelper.Initialize(settings); } catch (Exception ex) { Console.WriteLine($"Error initializing Graph: {ex.Message}"); } }
Créer la base de données
Dans cette section, vous définissez le modèle pour les enregistrements d’inventaire des composants de l’appliance et le contexte Entity Framework, puis vous utilisez l’outil dotnet ef
pour initialiser la base de données.
Définir le modèle
Créez un répertoire dans le répertoire PartsInventoryConnector nommé Data.
Créez un fichier dans le répertoire Data nommé AppliancePart.cs et ajoutez le code suivant.
using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using Microsoft.Graph.Models.ExternalConnectors; namespace PartsInventoryConnector.Data; public class AppliancePart { [JsonPropertyName("appliances@odata.type")] private const string AppliancesODataType = "Collection(String)"; [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; } public List<string>? Appliances { get; set; } public Properties AsExternalItemProperties() { _ = Name ?? throw new MemberAccessException("Name cannot be null"); _ = Description ?? throw new MemberAccessException("Description cannot be null"); _ = Appliances ?? throw new MemberAccessException("Appliances cannot be null"); 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; } }
Créez un fichier dans le répertoire Data nommé ApplianceDbContext.cs et ajoutez le code suivant.
using System.Text.Json; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace PartsInventoryConnector.Data; public class ApplianceDbContext : DbContext { public DbSet<AppliancePart> Parts => Set<AppliancePart>(); public void EnsureDatabase() { if (Database.EnsureCreated() || !Parts.Any()) { // File was just created (or is empty), // seed with data from CSV file var parts = CsvDataLoader.LoadPartsFromCsv("ApplianceParts.csv"); Parts.AddRange(parts); SaveChanges(); } } 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 => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), v => JsonSerializer.Deserialize<List<string>>(v, JsonSerializerOptions.Default) ); // 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; } }
Créez un fichier dans le répertoire Data nommé CsvDataLoader.cs et ajoutez le code suivant.
using System.Globalization; using CsvHelper; using CsvHelper.Configuration; using CsvHelper.TypeConversion; 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, CultureInfo.InvariantCulture); csv.Context.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(';') ?? Array.Empty<string>(); 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>(); } }
Initialiser la base de données
Ouvrez votre interface de ligne de commande (CLI) dans le répertoire où se trouve PartsInventoryConnector.csproj .
Exécutez les commandes suivantes :
dotnet ef migrations add InitialCreate dotnet ef database update
Remarque
Exécutez les commandes suivantes si un schéma change dans le fichier CSV et reflètez ces modifications dans la base de données SQLite.
dotnet ef database drop
dotnet ef database update
Gérer les connexions
Dans cette section, vous ajoutez des méthodes pour gérer les connexions externes.
Créer une connexion
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task<ExternalConnection?> CreateConnectionAsync(string id, string name, string? description) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); var newConnection = new ExternalConnection { Id = id, Name = name, Description = description, }; return await graphClient.External.Connections.PostAsync(newConnection); }
Remplacez la fonction
CreateConnectionAsync
d’espace réservé dans Program.cs par ce qui suit.async Task<ExternalConnection?> CreateConnectionAsync() { var connectionId = PromptForInput( "Enter a unique ID for the new connection (3-32 characters)", true) ?? "ConnectionId"; var connectionName = PromptForInput( "Enter a name for the new connection", true) ?? "ConnectionName"; var connectionDescription = PromptForInput( "Enter a description for the new connection", false); try { // Create the connection var connection = await GraphHelper.CreateConnectionAsync( connectionId, connectionName, connectionDescription); Console.WriteLine($"New connection created - Name: {connection?.Name}, Id: {connection?.Id}"); return connection; } catch (ODataError odataError) { Console.WriteLine($"Error creating connection: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); return null; } }
Sélectionner une connexion existante
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task<ExternalConnectionCollectionResponse?> GetExistingConnectionsAsync() { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); return await graphClient.External.Connections.GetAsync(); }
Remplacez la fonction
SelectExistingConnectionAsync
d’espace réservé dans Program.cs par ce qui suit.async Task<ExternalConnection?> SelectExistingConnectionAsync() { // TODO Console.WriteLine("Getting existing connections..."); try { var response = await GraphHelper.GetExistingConnectionsAsync(); var connections = response?.Value ?? new List<ExternalConnection>(); if (connections.Count <= 0) { Console.WriteLine("No connections exist. Please create a new connection"); return null; } // Display connections Console.WriteLine("Choose one of the following connections:"); var menuNumber = 1; foreach(var connection in connections) { Console.WriteLine($"{menuNumber++}. {connection.Name}"); } ExternalConnection? selection = null; do { try { Console.Write("Selection: "); var choice = int.Parse(Console.ReadLine() ?? string.Empty); if (choice > 0 && choice <= connections.Count) { selection = connections[choice - 1]; } else { Console.WriteLine("Invalid choice."); } } catch (FormatException) { Console.WriteLine("Invalid choice."); } } while (selection == null); return selection; } catch (ODataError odataError) { Console.WriteLine($"Error getting connections: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); return null; } }
Supprimer une connexion
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task DeleteConnectionAsync(string? connectionId) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); _ = connectionId ?? throw new ArgumentException("connectionId is required"); await graphClient.External.Connections[connectionId].DeleteAsync(); }
Remplacez la fonction
DeleteCurrentConnectionAsync
d’espace réservé dans Program.cs par ce qui suit.async Task DeleteCurrentConnectionAsync(ExternalConnection? connection) { if (connection == null) { Console.WriteLine( "No connection selected. Please create a new connection or select an existing connection."); return; } try { await GraphHelper.DeleteConnectionAsync(connection.Id); Console.WriteLine($"{connection.Name} deleted successfully."); } catch (ODataError odataError) { Console.WriteLine($"Error deleting connection: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); } }
Gérer le schéma
Dans cette section, vous allez ajouter des méthodes pour inscrire le schéma pour le connecteur.
Inscrire le schéma
Ajoutez les fonctions suivantes à la
GraphHelper
classe dans GraphHelper.cs.public static async Task RegisterSchemaAsync(string? connectionId, Schema schema) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); _ = httpClient ?? throw new MemberAccessException("httpClient is null"); _ = connectionId ?? throw new ArgumentException("connectionId is required"); // Use the Graph SDK's request builder to generate the request URL var requestInfo = graphClient.External .Connections[connectionId] .Schema .ToGetRequestInformation(); requestInfo.SetContentFromParsable(graphClient.RequestAdapter, "application/json", schema); // Convert the SDK request to an HttpRequestMessage var requestMessage = await graphClient.RequestAdapter .ConvertToNativeRequestAsync<HttpRequestMessage>(requestInfo); _ = requestMessage ?? throw new Exception("Could not create native HTTP request"); requestMessage.Method = HttpMethod.Post; requestMessage.Headers.Add("Prefer", "respond-async"); // Send the request var responseMessage = await httpClient.SendAsync(requestMessage) ?? throw new Exception("No response returned from API"); if (responseMessage.IsSuccessStatusCode) { // The operation ID is contained in the Location header returned // in the response var operationId = responseMessage.Headers.Location?.Segments.Last() ?? throw new Exception("Could not get operation ID from Location header"); await WaitForOperationToCompleteAsync(connectionId, operationId); } else { throw new ServiceException("Registering schema failed", responseMessage.Headers, (int)responseMessage.StatusCode); } } private static async Task WaitForOperationToCompleteAsync(string connectionId, string operationId) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); do { var operation = await graphClient.External .Connections[connectionId] .Operations[operationId] .GetAsync(); if (operation?.Status == ConnectionOperationStatus.Completed) { return; } else if (operation?.Status == ConnectionOperationStatus.Failed) { throw new ServiceException($"Schema operation failed: {operation?.Error?.Code} {operation?.Error?.Message}"); } // Wait 5 seconds and check again await Task.Delay(5000); } while (true); }
Remplacez la fonction
RegisterSchemaAsync
d’espace réservé dans Program.cs par ce qui suit.async Task RegisterSchemaAsync() { if (currentConnection == null) { Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } Console.WriteLine("Registering schema, this may take a moment..."); try { // Create the schema var schema = new Schema { BaseType = "microsoft.graph.externalItem", Properties = new List<Property> { new Property { Name = "partNumber", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true, IsRefinable = true }, new Property { Name = "name", Type = PropertyType.String, IsQueryable = true, IsSearchable = true, IsRetrievable = true, IsRefinable = false, Labels = new List<Label?>() { Label.Title }}, new Property { Name = "description", Type = PropertyType.String, IsQueryable = false, IsSearchable = true, IsRetrievable = true, IsRefinable = false }, new Property { Name = "price", Type = PropertyType.Double, IsQueryable = true, IsSearchable = false, IsRetrievable = true, IsRefinable = true }, new Property { Name = "inventory", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true, IsRefinable = true }, new Property { Name = "appliances", Type = PropertyType.StringCollection, IsQueryable = true, IsSearchable = true, IsRetrievable = true, IsRefinable = false } }, }; await GraphHelper.RegisterSchemaAsync(currentConnection.Id, schema); Console.WriteLine("Schema registered successfully"); } catch (ServiceException serviceException) { Console.WriteLine($"Error registering schema: {serviceException.ResponseStatusCode} {serviceException.Message}"); } catch (ODataError odataError) { Console.WriteLine($"Error registering schema: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); } }
Obtenir le schéma d’une connexion
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task<Schema?> GetSchemaAsync(string? connectionId) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); _ = connectionId ?? throw new ArgumentException("connectionId is null"); return await graphClient.External .Connections[connectionId] .Schema .GetAsync(); }
Remplacez la fonction
GetSchemaAsync
d’espace réservé dans Program.cs par ce qui suit.async Task GetSchemaAsync() { if (currentConnection == null) { Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } try { var schema = await GraphHelper.GetSchemaAsync(currentConnection.Id); Console.WriteLine(JsonSerializer.Serialize(schema)); } catch (ODataError odataError) { Console.WriteLine($"Error getting schema: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); } }
Gérer les articles
Dans cette section, vous allez ajouter des méthodes pour ajouter ou supprimer des éléments au connecteur.
Charger ou supprimer des éléments
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task AddOrUpdateItemAsync(string? connectionId, ExternalItem item) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); _ = connectionId ?? throw new ArgumentException("connectionId is null"); await graphClient.External .Connections[connectionId] .Items[item.Id] .PutAsync(item); }
Ajoutez la fonction suivante à la
GraphHelper
classe dans GraphHelper.cs.public static async Task DeleteItemAsync(string? connectionId, string? itemId) { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); _ = connectionId ?? throw new ArgumentException("connectionId is null"); _ = itemId ?? throw new ArgumentException("itemId is null"); await graphClient.External .Connections[connectionId] .Items[itemId] .DeleteAsync(); }
Remplacez la fonction
UpdateItemsFromDatabaseAsync
d’espace réservé dans Program.cs par ce qui suit.async Task UpdateItemsFromDatabaseAsync(bool uploadModifiedOnly, string? tenantId) { if (currentConnection == null) { Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } _ = tenantId ?? throw new ArgumentException("tenantId is null"); List<AppliancePart>? partsToUpload = null; List<AppliancePart>? partsToDelete = null; var newUploadTime = DateTime.UtcNow; var partsDb = new ApplianceDbContext(); partsDb.EnsureDatabase(); if (uploadModifiedOnly) { var lastUploadTime = GetLastUploadTime(); Console.WriteLine($"Uploading changes since last upload at {lastUploadTime.ToLocalTime()}"); partsToUpload = partsDb.Parts .Where(p => EF.Property<DateTime>(p, "LastUpdated") > lastUploadTime) .ToList(); partsToDelete = partsDb.Parts .IgnoreQueryFilters() .Where(p => EF.Property<bool>(p, "IsDeleted") && EF.Property<DateTime>(p, "LastUpdated") > lastUploadTime) .ToList(); } else { partsToUpload = partsDb.Parts.ToList(); partsToDelete = partsDb.Parts .IgnoreQueryFilters() .Where(p => EF.Property<bool>(p, "IsDeleted")) .ToList(); } Console.WriteLine($"Processing {partsToUpload.Count} add/updates, {partsToDelete.Count} deletes."); var success = true; foreach (var part in partsToUpload) { var newItem = new ExternalItem { Id = part.PartNumber.ToString(), Content = new ExternalItemContent { Type = ExternalItemContentType.Text, Value = part.Description }, Acl = new List<Acl> { new Acl { AccessType = AccessType.Grant, Type = AclType.Everyone, Value = tenantId, } }, Properties = part.AsExternalItemProperties(), }; try { Console.Write($"Uploading part number {part.PartNumber}..."); await GraphHelper.AddOrUpdateItemAsync(currentConnection.Id, newItem); Console.WriteLine("DONE"); } catch (ODataError odataError) { success = false; Console.WriteLine("FAILED"); Console.WriteLine($"Error: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); } } foreach (var part in partsToDelete) { try { Console.Write($"Deleting part number {part.PartNumber}..."); await GraphHelper.DeleteItemAsync(currentConnection.Id, part.PartNumber.ToString()); Console.WriteLine("DONE"); } catch (ODataError odataError) { success = false; Console.WriteLine("FAILED"); Console.WriteLine($"Error: {odataError.ResponseStatusCode}: {odataError.Error?.Code} {odataError.Error?.Message}"); } } // If no errors, update our last upload time if (success) { SaveLastUploadTime(newUploadTime); } }
Exécuter l’application
Dans cette étape, vous allez générer et exécuter l’exemple. Cet exemple de code crée une connexion, inscrit le schéma, puis envoie (push) des éléments du fichier ApplianceParts.csv dans cette connexion.
- Ouvrez votre interface de ligne de commande (CLI) dans le répertoire PartsInventoryConnector .
- Utilisez la commande
dotnet build
pour générer l’exemple. - Utilisez la commande
dotnet run
pour exécuter l’exemple. - Sélectionnez 1. Créez une connexion. Entrez un identificateur, un nom et une description uniques pour cette connexion.
- Sélectionnez 4. Inscrivez le schéma pour la connexion actuelle, puis attendez que l’opération se termine.
- Sélectionnez 7. Envoyer tous les éléments à la connexion actuelle.
Remarque
Si l’étape 5 génère une erreur, patientez quelques minutes, puis sélectionnez 7. Envoyer tous les éléments à la connexion actuelle.
Exposer les données dans la recherche
Dans cette étape, vous allez créer des secteurs verticaux de recherche et des types de résultats pour personnaliser les résultats de recherche dans Microsoft SharePoint, Microsoft Office et Recherche Microsoft dans Bing.
Créer un vertical
Connectez-vous au Centre d’administration Microsoft 365 à l’aide du rôle Administrateur général, puis procédez comme suit :
Accédez à Paramètres>Recherche & intelligence>Personnalisations.
Accédez à Verticals, puis sélectionnez Ajouter.
Entrez
Appliance Parts
dans le champ Nom , puis sélectionnez Suivant.Sélectionnez Connecteurs, puis le connecteur Inventaire des pièces . Sélectionnez Suivant.
Dans la page Ajouter une requête , laissez la requête vide. Sélectionnez Suivant.
Dans la page Filtres , sélectionnez Suivant.
Sélectionnez Ajouter un vertical.
Sélectionnez Activer vertical, puis Terminé.
Créer un type de résultat
Pour créer un type de résultat :
Accédez à Paramètres>Recherche & intelligence>Personnalisations.
Accédez à l’onglet Type de résultat , puis sélectionnez Ajouter.
Entrez
Appliance Part
dans le champ Nom , puis sélectionnez Suivant.Dans la page Source de contenu , sélectionnez Connecteur de composants. Sélectionnez Suivant.
Dans la page Règles , sélectionnez Suivant.
Dans la page Concevoir votre mise en page , collez le code JSON suivant, puis sélectionnez Suivant.
{ "type": "AdaptiveCard", "version": "1.3", "body": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "width": 6, "items": [ { "type": "TextBlock", "text": "__${name} (Part #${partNumber})__", "color": "accent", "size": "medium", "spacing": "none", "$when": "${name != \"\"}" }, { "type": "TextBlock", "text": "${description}", "wrap": true, "maxLines": 3, "$when": "${description != \"\"}" } ], "horizontalAlignment": "Center", "spacing": "none" }, { "type": "Column", "width": 2, "items": [ { "type": "FactSet", "facts": [ { "title": "Price", "value": "$${price}" }, { "title": "Current Inventory", "value": "${inventory} units" } ] } ], "spacing": "none", "horizontalAlignment": "right" } ] } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json" }
Sélectionnez Ajouter un type de résultat, puis Sélectionnez Terminé.
Rechercher des résultats
Dans cette étape, vous recherchez des composants dans SharePoint.
Accédez au site SharePoint racine de votre locataire.
À l’aide de la zone de recherche en haut de la page, recherchez charnière.
Une fois la recherche terminée avec 0 résultat, sélectionnez l’onglet Composants de l’appliance . Les résultats du connecteur sont affichés.
Félicitations !
Vous avez terminé avec succès le didacticiel sur les connecteurs Microsoft Graph .NET : vous avez créé un connecteur personnalisé et vous l’avez utilisé pour alimenter Recherche Microsoft.
Étapes suivantes
- Pour en savoir plus sur les connecteurs personnalisés, consultez Vue d’ensemble des connecteurs Microsoft Graph.
- Parcourez nos exemples de connecteurs.
- Explorez les exemples de connecteurs de la communauté.
Vous avez un défi avec cette section ? Si c'est le cas, faites-nous part de vos commentaires pour que nous puissions l'améliorer.