Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo descreve como usar o SDK dos conectores do Microsoft 365 Copilot (anteriormente conectores do Microsoft Graph) para criar um conector personalizado em C#.
Pré-requisitos
- Baixe, instale e conclua a configuração do agente do conector do Microsoft Graph.
- Instale o Visual Studio 2019 ou posterior com o SDK do .NET 7.0.
- Baixe o arquivo ApplianceParts.csv do repositório de exemplo do conector personalizado.
Instale a extensão
Abra o Visual Studio e vá para Extensões>:Gerenciar extensões.
Procure a extensão GraphConnectorsTemplate e baixe-a.
Feche e reinicie o Visual Studio para instalar o modelo.
Vá para Arquivo>Novo>Projeto e procure por GraphConnectorsTemplate. Selecione o modelo e escolha Avançar.
Forneça um nome para o projeto e escolha Avançar.
Escolha o .NET Core 3.1, nomeie o conector como CustomConnector e escolha Criar.
O projeto de modelo de conector personalizado foi criado.
Criar o conector personalizado
Antes de criar o conector, use as etapas a seguir para instalar pacotes NuGet e criar os modelos de dados que serão usados.
Instalar pacotes NuGet
Clique com o botão direito do mouse no projeto e escolha Abrir no terminal.
Execute o seguinte comando:
dotnet add package CsvHelper --version 27.2.1
Criar modelos de dados
Crie uma pasta chamada Modelos em CustomConnector e crie um arquivo chamado AppliancePart.cs na pasta.
Cole o seguinte código em 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; } } }
Atualizar ConnectionManagementServiceImpl.cs
Você implementará três métodos em ConnectionManagementServiceImpl.cs.
ValidateAuthentication
O método ValidateAuthentication é usado para validar as credenciais e a URL da fonte de dados fornecida. Você precisa se conectar à URL da fonte de dados usando as credenciais fornecidas e retornar sucesso se a conexão for bem-sucedida ou o status de falha de autenticação se a conexão falhar.
Crie uma pasta chamada Dados em CustomConnector e crie um CsvDataLoader.cs de arquivo na pasta.
Copie o seguinte código para 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>(); } } }O método ReadRecordFromCsv abre o arquivo CSV e lê o primeiro registro do arquivo. Podemos usar esse método para validar se a URL da fonte de dados fornecida (caminho do arquivo CSV) é válida. Esse conector está usando autenticação anônima; Portanto, as credenciais não são validadas. Se o conector usar qualquer outro tipo de autenticação, a conexão com a fonte de dados deverá ser feita usando as credenciais fornecidas para validar a autenticação.
Adicione a seguinte diretiva using no ConnectionManagementServiceImpl.cs.
using CustomConnector.Data;Atualize o método ValidateAuthentication no ConnectionManagementServiceImpl.cs com o código a seguir para chamar o método ReadRecordFromCsv da classe CsvDataLoader.
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
O método ValidateCustomConfiguration é usado para validar quaisquer outros parâmetros necessários para a conexão. O conector que você está criando não requer nenhum parâmetro extra; Portanto, o método valida se os parâmetros extras estão vazios.
Atualize o método ValidateCustomConfiguration no ConnectionManagementServiceImpl.cs com o código a seguir.
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
O método GetDataSourceSchema é usado para buscar o esquema para o conector.
Adicione as seguintes diretivas using no AppliancePart.cs.
using Microsoft.Graph.Connectors.Contracts.Grpc; using static Microsoft.Graph.Connectors.Contracts.Grpc.SourcePropertyDefinition.Types;Adicione o método GetSchema a seguir na classe AppliancePart.cs.
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; }Observação
- A propriedade RequiredSearchAnnotations marca as anotações de propriedade como obrigatórias e inalteráveis durante a configuração do conector. O exemplo anterior define todas as propriedades como pesquisáveis e recuperáveis obrigatoriamente; no entanto, você pode optar por não definir RequiredSearchAnnotations em uma ou mais propriedades.
- A propriedade DefaultSearchAnnotations marca as anotações de propriedade como padrão, mas elas podem ser alteradas durante a instalação do conector.
Adicione a seguinte diretiva using no ConnectionManagementServiceImpl.cs.
using CustomConnector.Models;Atualize o método GetDataSourceSchema no ConnectionManagementServiceImpl.cs com o código a seguir.
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); }
Atualizar ConnectorCrawlerServiceImpl.cs
Essa classe tem os métodos chamados pela plataforma durante os rastreamentos.
O método GetCrawlStream é chamado durante os rastreamentos completos completos ou periódicos.
Adicione a seguinte diretiva using no AppliancePart.cs.
using System.Globalization;Adicione os métodos a seguir no AppliancePart.cs para converter o registro AppliancePart em CrawlItem.
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; }Adicione a seguinte diretiva using no CsvDataLoader.cs.
using Microsoft.Graph.Connectors.Contracts.Grpc;Adicione o método a seguir em 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 returns an IEnumerable<T> that yields 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(); } } }Adicione a seguinte diretiva using em ConnectorCrawlerServiceImpl.cs.
using CustomConnector.Data;Adicione o método a seguir em ConnectorCrawlerServiceImpl.cs.
private CrawlStreamBit GetCrawlStreamBit(CrawlItem crawlItem) { return new CrawlStreamBit { Status = new OperationStatus { Result = OperationResult.Success, }, CrawlItem = crawlItem, CrawlProgressMarker = new CrawlCheckpoint { CustomMarkerData = crawlItem.ItemId, }, }; }Atualize o método GetCrawlStream para o seguinte.
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); } }
Agora o conector é criado e você pode construir e executar o projeto.