Erstellen Ihres ersten benutzerdefinierten Microsoft Graph-Connectors
Mit Microsoft Graph-Connectors können Sie Eigene Daten zu Microsoft Graph hinzufügen und verschiedene Microsoft 365-Umgebungen unterstützen.
Diese .NET-Anwendung zeigt Ihnen, wie Sie die Microsoft Graph-Connectors-API verwenden, um einen benutzerdefinierten Connector zu erstellen und ihn für Microsoft Search zu verwenden. In diesem Tutorial wird ein Beispielbestand für Teile der Datenappliance für die Contoso Appliance Repair-Organisation verwendet.
Voraussetzungen
Das auf Ihrem Entwicklungscomputer installierte .NET SDK .
Sie sollten über ein Microsoft-Geschäfts-, Schul- oder Unikonto mit der Rolle "Globaler Administrator" verfügen. Wenn Sie nicht über einen Microsoft 365-Mandanten verfügen, können Sie sich über das Microsoft 365-Entwicklerprogramm für einen mandantenfähigen Mandanten qualifizieren. Weitere Informationen finden Sie in den häufig gestellten Fragen. Alternativ können Sie sich für eine kostenlose 1-monatige Testversion registrieren oder einen Microsoft 365-Plan erwerben.
Installieren Sie die Entity Framework Core Tools mit dem folgenden Befehl als globales Tool:
dotnet tool install --global dotnet-ef
Installieren Sie ein Tool zum Aktualisieren einer SQLite-Datenbank. Beispiel: DB-Browser für SQLite.
Laden Sie die ApplianceParts.csv-Datei aus dem Beispielrepository für den Suchconnector herunter.
Registrieren der App im Portal
In dieser Übung registrieren Sie eine neue Anwendung in Microsoft Entra ID, um die reine App-Authentifizierung zu aktivieren. Microsoft Graph-Connectors verwenden die reine App-Authentifizierung, um auf die Connector-APIs zuzugreifen.
Registrieren der Anwendung für die reine App-Authentifizierung
In diesem Abschnitt registrieren Sie eine Anwendung, die die reine App-Authentifizierung mithilfe des Flusses für Clientanmeldeinformationen unterstützt.
Melden Sie sich beim Microsoft Entra Admin Centeran.
Erweitern Sie das Menü >Identität und wählen Sie Anwendungen>App-Registrierungen>Neue Registrierung aus.
Geben Sie einen Namen für Ihre Anwendung ein,
Parts Inventory Connector
z. B. .Legen Sie Unterstützte Kontotypen auf Nur Konten in diesem Organisationsverzeichnis fest.
Lassen Sie URI umleiten leer.
Wählen Sie Registrieren aus. Kopieren Sie auf der Seite Übersicht der Anwendung den Wert der Anwendungs-ID (Client-ID) und der Verzeichnis-ID (Mandanten-ID), und speichern Sie sie. Diese Werte benötigen Sie im nächsten Schritt.
Wählen Sie API-Berechtigungen unter Verwalten aus.
Entfernen Sie die Standardberechtigung User.Read unter Konfigurierte Berechtigungen , indem Sie die Auslassungspunkte (...) in der Zeile auswählen und Berechtigung entfernen auswählen.
Wählen Sie Berechtigung hinzufügen und dann Microsoft Graph aus.
Wählen Sie Anwendungsberechtigungen aus.
Wählen Sie ExternalConnection.ReadWrite.OwnedBy und ExternalItem.ReadWrite.OwnedBy und dann Berechtigungen hinzufügen aus.
Wählen Sie Administratoreinwilligung erteilen für... und dann Ja aus, um die Administratoreinwilligung für die ausgewählte Berechtigung bereitzustellen.
Wählen Sie zertifikate und geheimnisse unter Verwalten und dann Neuer geheimer Clientschlüssel aus.
Geben Sie eine Beschreibung ein, wählen Sie eine Dauer aus, und wählen Sie Hinzufügen aus.
Kopieren Sie das Geheimnis aus der Spalte Wert . Sie benötigen es in den nächsten Schritten.
Wichtig
Dieser geheime Clientschlüssel wird nicht noch einmal angezeigt, stellen Sie daher sicher, dass Sie ihn jetzt kopieren.
Erstellen der Anwendung
Erstellen Sie zunächst ein neues .NET-Konsolenprojekt mithilfe der .NET CLI.
Öffnen Sie Ihre Befehlszeilenschnittstelle (CLI) in einem Verzeichnis, in dem Sie das Projekt erstellen möchten. Führen Sie den folgenden Befehl aus.
dotnet new console -o PartsInventoryConnector
Nachdem das Projekt erstellt wurde, überprüfen Sie, ob es funktioniert, indem Sie das aktuelle Verzeichnis in das Verzeichnis PartsInventoryConnector ändern und den folgenden Befehl in Ihrer CLI ausführen.
dotnet run
Wenn es funktioniert, sollte die App ausgeben
Hello, World!
.
Installieren von Abhängigkeiten
Bevor Sie mit dem Vorgang fortfahren, fügen Sie einige zusätzliche Abhängigkeiten hinzu, die Sie später verwenden.
- .NET-Konfigurationspakete zum Lesen der Anwendungskonfiguration aus appsettings.json.
- Azure Identity-Clientbibliothek für .NET zum Authentifizieren des Benutzers und Abrufen von Zugriffstoken.
- Microsoft Graph .NET-Clientbibliothek , um Aufrufe an Microsoft Graph zu tätigen.
- Entity Framework-Pakete für den Zugriff auf eine lokale Datenbank.
- CsvHelper zum Lesen von CSV-Dateien.
Führen Sie die folgenden Befehle in Ihrer CLI aus, um die Abhängigkeiten zu installieren.
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
Laden von Anwendungseinstellungen
In diesem Abschnitt fügen Sie dem Projekt die Details Ihrer App-Registrierung hinzu.
Fügen Sie Die Client-ID, die Mandanten-ID und den geheimen Clientschlüssel zum .NET-Geheimnis-Manager hinzu. Ändern Sie in Ihrer Befehlszeilenschnittstelle das Verzeichnis in den Speicherort partsInventoryConnector.csproj, und führen Sie die folgenden Befehle aus. Ersetzen <Sie dabei client-id durch Ihre Client-ID> aus Ihrer App-Registrierung,< tenant-id> durch Ihre Mandanten-ID und <client-secret> durch Ihren geheimen Clientschlüssel.
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>
Erstellen Sie eine Datei im Verzeichnis PartsInventoryConnector mit dem Namen Settings.cs , und fügen Sie den folgenden Code hinzu.
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."); } }
Entwerfen der App
In diesem Abschnitt erstellen Sie ein konsolenbasiertes Menü.
Öffnen Sie ./Program.cs , und ersetzen Sie den gesamten Inhalt durch den folgenden Code.
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")); }
Fügen Sie am Ende der Datei die folgenden Platzhaltermethoden hinzu. Sie implementieren sie in späteren Schritten.
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 }
Dadurch wird ein einfaches Menü implementiert und die Auswahl des Benutzers aus der Befehlszeile gelesen.
Konfigurieren von Microsoft Graph
In diesem Abschnitt konfigurieren Sie den Microsoft Graph SDK-Client für die Verwendung der reinen App-Authentifizierung.
Erstellen einer Hilfsklasse
Erstellen Sie ein neues Verzeichnis im Verzeichnis PartsInventoryConnector mit dem Namen Graph.
Erstellen Sie im Graph-Verzeichnis eine Datei mit dem Namen GraphHelper.cs , und fügen Sie die folgenden
using
Anweisungen hinzu.using Azure.Identity; using Microsoft.Graph; using Microsoft.Graph.Models.ExternalConnectors; using Microsoft.Kiota.Authentication.Azure;
Fügen Sie einen Namespace und eine Klassendefinition hinzu.
namespace PartsInventoryConnector.Graph; public static class GraphHelper { }
Fügen Sie der -Klasse den
GraphHelper
folgenden Code hinzu, der eineGraphServiceClient
mit nur app-Authentifizierung konfiguriert.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); }
Ersetzen Sie die leere
InitializeGraph
Funktion in Program.cs durch Folgendes.void InitializeGraph(Settings settings) { try { GraphHelper.Initialize(settings); } catch (Exception ex) { Console.WriteLine($"Error initializing Graph: {ex.Message}"); } }
Erstellen der Datenbank
In diesem Abschnitt definieren Sie das Modell für die Geräteteilbestandsdatensätze und den Entity Framework-Kontext und verwenden das dotnet ef
Tool zum Initialisieren der Datenbank.
Definieren des Modells
Erstellen Sie im Verzeichnis PartsInventoryConnector ein neues Verzeichnis mit dem Namen Data.
Erstellen Sie im Verzeichnis Data eine Datei mit dem Namen AppliancePart.cs , und fügen Sie den folgenden Code hinzu.
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; } }
Erstellen Sie im Verzeichnis Data eine Datei mit dem Namen ApplianceDbContext.cs , und fügen Sie den folgenden Code hinzu.
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; } }
Erstellen Sie im Verzeichnis Data eine Datei mit dem Namen CsvDataLoader.cs , und fügen Sie den folgenden Code hinzu.
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>(); } }
Initialisieren der Datenbank
Öffnen Sie Ihre Befehlszeilenschnittstelle (CLI) in dem Verzeichnis, in dem sich PartsInventoryConnector.csproj befindet.
Führen Sie die folgenden Befehle aus:
dotnet ef migrations add InitialCreate dotnet ef database update
Hinweis
Führen Sie die folgenden Befehle aus, wenn sich ein Schema in der CSV-Datei ändert, und spiegeln Sie diese Änderungen in der SQLite-Datenbank wider.
dotnet ef database drop
dotnet ef database update
Verwalten von Verbindungen
In diesem Abschnitt fügen Sie Methoden zum Verwalten externer Verbindungen hinzu.
Erstellen einer Verbindung
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.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); }
Ersetzen Sie die Platzhalterfunktion
CreateConnectionAsync
in Program.cs durch Folgendes.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; } }
Auswählen einer vorhandenen Verbindung
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.public static async Task<ExternalConnectionCollectionResponse?> GetExistingConnectionsAsync() { _ = graphClient ?? throw new MemberAccessException("graphClient is null"); return await graphClient.External.Connections.GetAsync(); }
Ersetzen Sie die Platzhalterfunktion
SelectExistingConnectionAsync
in Program.cs durch Folgendes.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; } }
Löschen einer Verbindung
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.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(); }
Ersetzen Sie die Platzhalterfunktion
DeleteCurrentConnectionAsync
in Program.cs durch Folgendes.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}"); } }
Schema verwalten
In diesem Abschnitt fügen Sie Methoden zum Registrieren des Schemas für den Connector hinzu.
Registrieren des Schemas
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgenden Funktionen hinzu.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); }
Ersetzen Sie die Platzhalterfunktion
RegisterSchemaAsync
in Program.cs durch Folgendes.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}"); } }
Abrufen des Schemas für eine Verbindung
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.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(); }
Ersetzen Sie die Platzhalterfunktion
GetSchemaAsync
in Program.cs durch Folgendes.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}"); } }
Verwalten von Elementen
In diesem Abschnitt fügen Sie Methoden zum Hinzufügen oder Löschen von Elementen zum Connector hinzu.
Hochladen oder Löschen von Elementen
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.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); }
Fügen Sie der -Klasse in GraphHelper.cs die
GraphHelper
folgende Funktion hinzu.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(); }
Ersetzen Sie die Platzhalterfunktion
UpdateItemsFromDatabaseAsync
in Program.cs durch Folgendes.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); } }
Ausführen der Anwendung
In diesem Schritt erstellen Sie das Beispiel und führen es aus. In diesem Codebeispiel wird eine neue Verbindung erstellt, das Schema registriert und dann Elemente aus der ApplianceParts.csv Datei in diese Verbindung gepusht.
- Öffnen Sie Ihre Befehlszeilenschnittstelle (CLI) im Verzeichnis PartsInventoryConnector .
- Verwenden Sie den Befehl
dotnet build
, um das Beispiel zu erstellen. - Verwenden Sie den Befehl
dotnet run
, um das Beispiel auszuführen. - Wählen Sie 1 aus. Erstellen Sie eine Verbindung. Geben Sie einen eindeutigen Bezeichner, Einen Namen und eine Beschreibung für diese Verbindung ein.
- Wählen Sie 4 aus. Registrieren Sie das Schema für die aktuelle Verbindung, und warten Sie dann, bis der Vorgang abgeschlossen ist.
- Wählen Sie 7 aus. PushEN SIE ALLE Elemente an die aktuelle Verbindung.
Hinweis
Wenn Schritt 5 zu einem Fehler führt, warten Sie einige Minuten, und wählen Sie dann 7 aus. PushEN SIE ALLE Elemente an die aktuelle Verbindung.
Anzeigen der Daten in der Suche
In diesem Schritt erstellen Sie Suchsparten und Ergebnistypen, um die Suchergebnisse in Microsoft SharePoint, Microsoft Office und Microsoft Search in Bing anzupassen.
Erstellen einer vertikalen
Melden Sie sich mit der Rolle "Globaler Administrator" beim Microsoft 365 Admin Center an, und gehen Sie wie folgt vor:
Wechseln Sie zu Einstellungen>Suche & Intelligenz>Anpassungen.
Wechseln Sie zu Vertikalen, und wählen Sie dann Hinzufügen aus.
Geben Sie
Appliance Parts
in das Feld Name ein, und wählen Sie Weiter aus.Wählen Sie Connectors und dann den Connector Teilebestand aus. Wählen Sie Weiter aus.
Lassen Sie auf der Seite Abfrage hinzufügen die Abfrage leer. Wählen Sie Weiter aus.
Wählen Sie auf der Seite Filter die Option Weiter aus.
Wählen Sie Vertikal hinzufügen aus.
Wählen Sie Vertikal aktivieren und dann Fertig aus.
Erstellen eines Ergebnistyps
So erstellen Sie einen Ergebnistyp:
Wechseln Sie zu Einstellungen>Suche & Intelligenz>Anpassungen.
Wechseln Sie zur Registerkarte Ergebnistyp , und wählen Sie dann Hinzufügen aus.
Geben Sie
Appliance Part
in das Feld Name ein, und wählen Sie Weiter aus.Wählen Sie auf der Seite Inhaltsquelledie Option Teileconnector aus. Wählen Sie Weiter aus.
Wählen Sie auf der Seite Regeln die Option Weiter aus.
Fügen Sie auf der Seite Layout entwerfen den folgenden JSON-Code ein, und wählen Sie dann Weiter aus.
{ "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" }
Wählen Sie Ergebnistyp hinzufügen und dann Fertig aus.
Suchen nach Ergebnissen
In diesem Schritt suchen Sie nach Teilen in SharePoint.
Wechseln Sie zur SharePoint-Stammwebsite für Ihren Mandanten.
Suchen Sie über das Suchfeld oben auf der Seite nach Scharnier.
Wenn die Suche mit 0 Ergebnissen abgeschlossen ist, wählen Sie die Registerkarte Applianceparts aus. Die Ergebnisse des Connectors werden angezeigt.
Herzlichen Glückwunsch!
Sie haben das Tutorial für .NET-Microsoft Graph-Connectors erfolgreich abgeschlossen: Sie haben einen benutzerdefinierten Connector erstellt und ihn für Microsoft Search verwendet.
Nächste Schritte
- Weitere Informationen zu benutzerdefinierten Connectors finden Sie unter Übersicht über Microsoft Graph-Connectors.
- Durchsuchen Sie unsere Beispielconnectors.
- Erkunden Sie Beispielconnectors aus der Community.
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.