Erstellen eines benutzerdefinierten Microsoft Graph-Connectors in C#

In diesem Artikel wird beschrieben, wie Sie das Microsoft Graph-Connector-SDK verwenden, um einen benutzerdefinierten Connector in C# zu erstellen.

Voraussetzungen

  1. Laden Sie das Setup für den Microsoft Graph-Connector-Agent herunter, installieren Sie sie, und schließen Sie sie ab.
  2. Installieren Sie Visual Studio 2019 oder höher mit dem .NET 7.0 SDK.
  3. Laden Sie die ApplianceParts.csv-Datei aus dem Beispielrepository für den benutzerdefinierten Connector herunter.

Installieren der Erweiterung

  1. Öffnen Sie Visual Studio, und wechseln Sie zu Erweiterungen>Erweiterungen verwalten.

  2. Search für die GraphConnectorsTemplate-Erweiterung, und laden Sie sie herunter.

  3. Schließen Sie Visual Studio, und starten Sie es neu, um die Vorlage zu installieren.

  4. Wechseln Sie zu Datei>Neues>Projekt , und suchen Sie nach GraphConnectorsTemplate. Wählen Sie die Vorlage und dann Weiter aus. Screenshot der Seite

  5. Geben Sie einen Namen für das Projekt an, und wählen Sie Weiter aus.

  6. Wählen Sie .NET Core 3.1 aus, nennen Sie den Connector CustomConnector, und wählen Sie Erstellen aus.

  7. Das benutzerdefinierte Connectorvorlagenprojekt wird jetzt erstellt.

    Screenshot der CustomConnector-Projektstruktur in Visual Studio

Erstellen des benutzerdefinierten Connectors

Führen Sie vor dem Erstellen des Connectors die folgenden Schritte aus, um NuGet-Pakete zu installieren und die zu verwendenden Datenmodelle zu erstellen.

Installieren der NuGet-Pakete

  1. Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie In Terminal öffnen aus.

  2. Führen Sie den folgenden Befehl aus.

    dotnet add package CsvHelper --version 27.2.1
    

Erstellen von Datenmodellen

  1. Erstellen Sie unter CustomConnector einen Ordner mit dem Namen Models und eine Datei mit dem Namen AppliancePart.cs unter dem Ordner.

  2. Fügen Sie den folgenden Code in AppliancePart.cs ein.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Text;
    
    namespace CustomConnector.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; }
            public List<string> Appliances { get; set; }
        }
    }
    
    

ConnectionManagementServiceImpl.cs aktualisieren

Sie implementieren drei Methoden in ConnectionManagementServiceImpl.cs.

ValidateAuthentication

Die ValidateAuthentication-Methode wird verwendet, um die anmeldeinformationen und die bereitgestellte Datenquellen-URL zu überprüfen. Sie müssen eine Verbindung mit der Datenquellen-URL mithilfe der angegebenen Anmeldeinformationen herstellen und eine erfolgreiche Verbindung zurückgeben, wenn die Verbindung erfolgreich ist oder ein Authentifizierungsfehler status, wenn die Verbindung fehlschlägt.

  1. Erstellen Sie unter CustomConnector einen Ordner mit dem Namen Daten , und erstellen Sie eine Datei CsvDataLoader.cs im Ordner.

  2. Kopieren Sie den folgenden Code in CsvDataLoader.cs:

    using CsvHelper;
    using CsvHelper.Configuration;
    using CsvHelper.TypeConversion;
    
    using CustomConnector.Models;
    
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    
    namespace CustomConnector.Data
    {
        public static class CsvDataLoader
        {
            public static void ReadRecordFromCsv(string filePath)
            {
                using (var reader = new StreamReader(filePath))
                using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                {
                    csv.Context.RegisterClassMap<AppliancePartMap>();
                    csv.Read();
                }
            }
        }
    
        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>();
            }
        }
    }
    
    

    Die ReadRecordFromCsv-Methode öffnet die CSV-Datei und liest den ersten Datensatz aus der Datei. Wir können diese Methode verwenden, um zu überprüfen, ob die angegebene Datenquellen-URL (Pfad der CSV-Datei) gültig ist. Dieser Connector verwendet anonyme Authentifizierung. Daher werden Anmeldeinformationen nicht überprüft. Wenn der Connector einen anderen Authentifizierungstyp verwendet, muss die Verbindung mit der Datenquelle mithilfe der zum Überprüfen der Authentifizierung angegebenen Anmeldeinformationen hergestellt werden.

  3. Fügen Sie die folgende using-Direktive in ConnectionManagementServiceImpl.cs hinzu.

    using CustomConnector.Data;
    
  4. Aktualisieren Sie die ValidateAuthentication-Methode in ConnectionManagementServiceImpl.cs mit dem folgenden Code, um die ReadRecordFromCsv-Methode der CsvDataLoader-Klasse aufzurufen.

    public override Task<ValidateAuthenticationResponse> ValidateAuthentication(ValidateAuthenticationRequest request, ServerCallContext context)
            {
                try
                {
                    Log.Information("Validating authentication");
                    CsvDataLoader.ReadRecordFromCsv(request.AuthenticationData.DatasourceUrl);
                    return this.BuildAuthValidationResponse(true);
                }
                catch (Exception ex)
                {
                    Log.Error(ex.ToString());
                    return this.BuildAuthValidationResponse(false, "Could not read the provided CSV file with the provided credentials");
                }
            }
    
    

ValidateCustomConfiguration

Die ValidateCustomConfiguration-Methode wird verwendet, um alle anderen Parameter zu überprüfen, die für die Verbindung erforderlich sind. Für den Connector, den Sie erstellen, sind keine zusätzlichen Parameter erforderlich. Daher überprüft die -Methode, ob die zusätzlichen Parameter leer sind.

  1. Aktualisieren Sie die ValidateCustomConfiguration-Methode in ConnectionManagementServiceImpl.cs mit dem folgenden Code.

    public override Task<ValidateCustomConfigurationResponse> ValidateCustomConfiguration(ValidateCustomConfigurationRequest request, ServerCallContext context)
        {
            Log.Information("Validating custom configuration");
            ValidateCustomConfigurationResponse response;
    
            if (!string.IsNullOrWhiteSpace(request.CustomConfiguration.Configuration))
            {
                response = new ValidateCustomConfigurationResponse()
                {
                    Status = new OperationStatus()
                    {
                        Result = OperationResult.ValidationFailure,
                        StatusMessage = "No additional parameters are required for this connector"
                    },
                };
            }
            else
            {
                response = new ValidateCustomConfigurationResponse()
                {
                    Status = new OperationStatus()
                    {
                        Result = OperationResult.Success,
                    },
                };
            }
    
            return Task.FromResult(response);
        }
    
    

GetDataSourceSchema

Die GetDataSourceSchema-Methode wird verwendet, um das Schema für den Connector abzurufen.

  1. Fügen Sie die folgenden using-Direktiven in AppliancePart.cs hinzu.

    using Microsoft.Graph.Connectors.Contracts.Grpc;
    using static Microsoft.Graph.Connectors.Contracts.Grpc.SourcePropertyDefinition.Types;
    
    
  2. Fügen Sie die folgende GetSchema-Methode in der klasse AppliancePart.cs hinzu.

     public static DataSourceSchema GetSchema()
       {
           DataSourceSchema schema = new DataSourceSchema();
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(PartNumber),
                   Type = SourcePropertyType.Int64,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsQueryable | SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsQueryable | SearchAnnotations.IsRetrievable),
               });
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(Name),
                   Type = SourcePropertyType.String,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
               });
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(Price),
                   Type = SourcePropertyType.Double,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsRetrievable),
               });
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(Inventory),
                   Type = SourcePropertyType.Int64,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsQueryable | SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsQueryable | SearchAnnotations.IsRetrievable),
               });
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(Appliances),
                   Type = SourcePropertyType.StringCollection,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
               });
    
           schema.PropertyList.Add(
               new SourcePropertyDefinition
               {
                   Name = nameof(Description),
                   Type = SourcePropertyType.String,
                   DefaultSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
                   RequiredSearchAnnotations = (uint)(SearchAnnotations.IsSearchable | SearchAnnotations.IsRetrievable),
               });
    
           return schema;
       }
    
    
  3. Fügen Sie die folgende using-Direktive in ConnectionManagementServiceImpl.cs hinzu.

    using CustomConnector.Models;
    
  4. Aktualisieren Sie die GetDataSourceSchema-Methode in ConnectionManagementServiceImpl.cs mit dem folgenden Code.

    public override Task<GetDataSourceSchemaResponse> GetDataSourceSchema(GetDataSourceSchemaRequest request, ServerCallContext context)
        {
            Log.Information("Trying to fetch datasource schema");
    
            var opStatus = new OperationStatus()
            {
                Result = OperationResult.Success,
            };
    
            GetDataSourceSchemaResponse response = new GetDataSourceSchemaResponse()
            {
                DataSourceSchema = AppliancePart.GetSchema(),
                Status = opStatus,
            };
    
            return Task.FromResult(response);
        }
    
    

Update ConnectorCrawlerServiceImpl.cs

Diese Klasse verfügt über die Methoden, die von der Plattform während der Durchforstungen aufgerufen werden.

Die GetCrawlStream-Methode wird während der vollständigen oder regelmäßigen vollständigen Durchforstungen aufgerufen.

  1. Fügen Sie die folgende using-Direktive in AppliancePart.cs hinzu.

    using System.Globalization;
    
  2. Fügen Sie die folgenden Methoden in AppliancePart.cs hinzu, um den AppliancePart-Datensatz in CrawlItem zu konvertieren.

    public CrawlItem ToCrawlItem()
        {
            return new CrawlItem
            {
                ItemType = CrawlItem.Types.ItemType.ContentItem,
                ItemId = this.PartNumber.ToString(CultureInfo.InvariantCulture),
                ContentItem = this.GetContentItem(),
            };
        }
    
        private ContentItem GetContentItem()
        {
            return new ContentItem
            {
                AccessList = this.GetAccessControlList(),
                PropertyValues = this.GetSourcePropertyValueMap()
            };
        }
    
        private AccessControlList GetAccessControlList()
        {
            AccessControlList accessControlList = new AccessControlList();
            accessControlList.Entries.Add(this.GetAllowEveryoneAccessControlEntry());
            return accessControlList;
        }
    
        private AccessControlEntry GetAllowEveryoneAccessControlEntry()
        {
            return new AccessControlEntry
            {
                AccessType = AccessControlEntry.Types.AclAccessType.Grant,
                Principal = new Principal
                {
                    Type = Principal.Types.PrincipalType.Everyone,
                    IdentitySource = Principal.Types.IdentitySource.AzureActiveDirectory,
                    IdentityType = Principal.Types.IdentityType.AadId,
                    Value = "EVERYONE",
                }
            };
        }
    
        private SourcePropertyValueMap GetSourcePropertyValueMap()
        {
            SourcePropertyValueMap sourcePropertyValueMap = new SourcePropertyValueMap();
    
            sourcePropertyValueMap.Values.Add(
                nameof(this.PartNumber),
                new GenericType
                {
                    IntValue = this.PartNumber,
                });
    
            sourcePropertyValueMap.Values.Add(
                nameof(this.Name),
                new GenericType
                {
                    StringValue = this.Name,
                });
    
            sourcePropertyValueMap.Values.Add(
                nameof(this.Price),
                new GenericType
                {
                    DoubleValue = this.Price,
                });
    
            sourcePropertyValueMap.Values.Add(
                nameof(this.Inventory),
                new GenericType
                {
                    IntValue = this.Inventory,
                });
    
            var appliancesPropertyValue = new StringCollectionType();
            foreach(var property in this.Appliances)
            {
                appliancesPropertyValue.Values.Add(property);
            }
            sourcePropertyValueMap.Values.Add(
                nameof(this.Appliances),
                new GenericType
                {
                    StringCollectionValue = appliancesPropertyValue,
                });
    
            sourcePropertyValueMap.Values.Add(
                nameof(this.Description),
                new GenericType
                {
                    StringValue = Description,
                });
    
            return sourcePropertyValueMap;
        }
    
    
  3. Fügen Sie die folgende using-Direktive in CsvDataLoader.cs hinzu.

    using Microsoft.Graph.Connectors.Contracts.Grpc;
    
  4. Fügen Sie die folgende Methode in CsvDataLoader.cs hinzu.

    public static IEnumerable<CrawlItem> GetCrawlItemsFromCsv(string filePath)
        {
            using (var reader = new StreamReader(filePath))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                csv.Context.RegisterClassMap<AppliancePartMap>();
    
                // The GetRecords<T> method will return an IEnumerable<T> that will yield records. This means that only one record is returned at a time as you iterate the records.
                foreach (var record in csv.GetRecords<AppliancePart>())
                {
                    yield return record.ToCrawlItem();
                }
            }
        }
    
    
  5. Fügen Sie die folgende using-Direktive in ConnectorCrawlerServiceImpl.cs hinzu.

    using CustomConnector.Data;
    
  6. Fügen Sie die folgende Methode in ConnectorCrawlerServiceImpl.cs hinzu.

    private CrawlStreamBit GetCrawlStreamBit(CrawlItem crawlItem)
        {
            return new CrawlStreamBit
            {
                Status = new OperationStatus
                {
                    Result = OperationResult.Success,
                },
                CrawlItem = crawlItem,
                CrawlProgressMarker = new CrawlCheckpoint
                {
                    CustomMarkerData = crawlItem.ItemId,
                },
            };
        }
    
    
  7. Aktualisieren Sie die GetCrawlStream-Methode wie folgt.

    public override async Task GetCrawlStream(GetCrawlStreamRequest request, IServerStreamWriter<CrawlStreamBit> responseStream, ServerCallContext context)
        {
            try
            {
                Log.Information("GetCrawlStream Entry");
                var crawlItems = CsvDataLoader.GetCrawlItemsFromCsv(request.AuthenticationData.DatasourceUrl);
                foreach (var crawlItem in crawlItems)
                {
                    CrawlStreamBit crawlStreamBit = this.GetCrawlStreamBit(crawlItem);
                    await responseStream.WriteAsync(crawlStreamBit).ConfigureAwait(false);
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex.ToString());
                CrawlStreamBit crawlStreamBit = new CrawlStreamBit
                {
                    Status = new OperationStatus
                    {
                        Result = OperationResult.DatasourceError,
                        StatusMessage = "Fetching items from datasource failed",
                        RetryInfo = new RetryDetails
                        {
                            Type = RetryDetails.Types.RetryType.Standard,
                        },
                    },
                };
                await responseStream.WriteAsync(crawlStreamBit).ConfigureAwait(false);
            }
    
        }
    
    

Nun wird der Connector erstellt, und Sie können das Projekt erstellen und ausführen.

Nächste Schritte