C でカスタム Microsoft Graph コネクタを構築する#

この記事では、Microsoft Graph コネクタ SDK を使用して C# でカスタム コネクタを構築する方法について説明します。

前提条件

  1. Microsoft Graph コネクタ エージェントのセットアップをダウンロード、インストール、完了します。
  2. .NET 7.0 SDK を使用して Visual Studio 2019 以降をインストールします。
  3. カスタム コネクタ サンプル リポジトリから ApplianceParts.csv ファイルをダウンロードします。

拡張機能をインストールする

  1. Visual Studio を開き、[拡張機能の管理]> に移動します

  2. GraphConnectorsTemplate 拡張機能のSearchしてダウンロードします。

  3. Visual Studio を閉じて再起動してテンプレートをインストールします。

  4. [ファイル>] [新しい>プロジェクト] に移動し、GraphConnectorsTemplate を検索します。 テンプレートを選択し、[ 次へ] を選択します。 Visual Studio の [テンプレートからプロジェクトを作成する] ページのスクリーンショット

  5. プロジェクトの名前を指定し、[ 次へ] を選択します。

  6. [.NET Core 3.1] を選択し、コネクタに CustomConnector という名前を付け、[ 作成] を選択します。

  7. カスタム コネクタ テンプレート プロジェクトが作成されました。

    Visual Studio の CustomConnector プロジェクト構造のスクリーンショット

カスタム コネクタを作成する

コネクタをビルドする前に、次の手順を使用して NuGet パッケージをインストールし、使用するデータ モデルを作成します。

NuGet パッケージをインストールする

  1. プロジェクトを右クリックし、[ ターミナルで開く] を選択します。

  2. 次のコマンドを実行します。

    dotnet add package CsvHelper --version 27.2.1
    

データ モデルを作成する

  1. CustomConnector の下に Models という名前のフォルダーを作成し、フォルダーの下に AppliancePart.cs という名前のファイルを作成します。

  2. 次のコードをAppliancePart.csに貼り付けます。

    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

ConnectionManagementServiceImpl.csには 3 つのメソッドを実装します。

ValidateAuthentication

ValidateAuthentication メソッドは、指定された資格情報とデータ ソース URL を検証するために使用されます。 指定された資格情報を使用してデータ ソース URL に接続し、接続が成功した場合は成功を返し、接続が失敗した場合は認証エラー状態を返す必要があります。

  1. CustomConnector の下に Data という名前のフォルダーを作成し、フォルダーにCsvDataLoader.csファイルを作成します。

  2. 次のコードを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>();
            }
        }
    }
    
    

    ReadRecordFromCsv メソッドは CSV ファイルを開き、ファイルから最初のレコードを読み取ります。 このメソッドを使用して、指定されたデータ ソース URL (CSV ファイルのパス) が有効であることを検証できます。 このコネクタは匿名認証を使用しています。そのため、資格情報は検証されません。 コネクタが他の認証の種類を使用する場合は、認証を検証するために指定された資格情報を使用してデータ ソースへの接続を行う必要があります。

  3. ConnectionManagementServiceImpl.csに次の using ディレクティブを追加します。

    using CustomConnector.Data;
    
  4. ConnectionManagementServiceImpl.csの ValidateAuthentication メソッドを次のコードで更新して、CsvDataLoader クラスの ReadRecordFromCsv メソッドを呼び出します。

    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

ValidateCustomConfiguration メソッドは、接続に必要なその他のパラメーターを検証するために使用されます。 ビルドするコネクタには、追加のパラメーターは必要ありません。したがって、メソッドは余分なパラメーターが空であることを検証します。

  1. ConnectionManagementServiceImpl.csの ValidateCustomConfiguration メソッドを次のコードで更新します。

    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

GetDataSourceSchema メソッドは、コネクタのスキーマをフェッチするために使用されます。

  1. AppliancePart.csに次の using ディレクティブを追加します。

    using Microsoft.Graph.Connectors.Contracts.Grpc;
    using static Microsoft.Graph.Connectors.Contracts.Grpc.SourcePropertyDefinition.Types;
    
    
  2. AppliancePart.cs クラスに次の GetSchema メソッドを追加します。

     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. ConnectionManagementServiceImpl.csに次の using ディレクティブを追加します。

    using CustomConnector.Models;
    
  4. ConnectionManagementServiceImpl.csの GetDataSourceSchema メソッドを次のコードで更新します。

    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);
        }
    
    

更新ConnectorCrawlerServiceImpl.cs

このクラスには、クロール中にプラットフォームによって呼び出されるメソッドがあります。

GetCrawlStream メソッドは、フル クロールまたは定期的なフル クロール中に呼び出されます。

  1. AppliancePart.csに次の using ディレクティブを追加します。

    using System.Globalization;
    
  2. AppliancePart レコードを CrawlItem に変換するには、AppliancePart.csに次のメソッドを追加します。

    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. CsvDataLoader.csに次の using ディレクティブを追加します。

    using Microsoft.Graph.Connectors.Contracts.Grpc;
    
  4. CsvDataLoader.csに次のメソッドを追加します。

    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. ConnectorCrawlerServiceImpl.csに次の using ディレクティブを追加します。

    using CustomConnector.Data;
    
  6. ConnectorCrawlerServiceImpl.csに次のメソッドを追加します。

    private CrawlStreamBit GetCrawlStreamBit(CrawlItem crawlItem)
        {
            return new CrawlStreamBit
            {
                Status = new OperationStatus
                {
                    Result = OperationResult.Success,
                },
                CrawlItem = crawlItem,
                CrawlProgressMarker = new CrawlCheckpoint
                {
                    CustomMarkerData = crawlItem.ItemId,
                },
            };
        }
    
    
  7. GetCrawlStream メソッドを次のように更新します。

    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);
            }
    
        }
    
    

これでコネクタが作成され、プロジェクトをビルドして実行できます。

次の手順