Сборка вашего первого пользовательского соединителя Microsoft Graph
Соединители Microsoft Graph позволяют добавлять собственные данные в Microsoft Graph и использовать различные возможности Microsoft 365.
В этом приложении .NET Core показано, как с помощью API соединителей Microsoft Graph создать настраиваемый соединитель и использовать его для работы с поиском (Майкрософт). В этом руководстве используется пример данных (модуль) инвентаризации частей для организации по ремонту устройств Contoso.
Как работает пример?
В этом примере создается классическое приложение Для Windows, которое получает маркер из платформа удостоверений Майкрософт, и использует его для отправки запросов в API соединителей Microsoft Graph. API соединителей отправляет свой ответ после проверки доступа.
Предварительные требования
Установите Visual Studio 2019 с пакетом SDK для .NET Core 3.1 на компьютере разработки.
Убедитесь, что у вас есть личная учетная запись Майкрософт, рабочая или учебная учетная запись.
Установите Entity Framework Core Tools в качестве глобального средства с помощью следующей команды:
dotnet tool install --global dotnet-ef
Установите средство для обновления базы данных SQLite. Например, браузер базы данных для SQLite.
Скачайте файлApplianceParts.csv из репозитория примера соединителя поиска.
Совет
Лучший способ скачать файлы из GitHub — перейти на верхний уровень проекта. В зеленой кнопке Скачивание кода справа выберите Скачать ZIP- файл. ZIP-файл содержит содержимое репозитория.
Регистрация приложения на портале
После выполнения всех предварительных требований вы сможете зарегистрировать приложение в Центре администрирования Azure AD. Регистрация необходима для проверки подлинности приложения и его использования для вызовов API соединителей Microsoft Graph.
Перейдите в Центр администрирования Azure Active Directory и войдите с учетной записью администратора.
В левой области выберите Azure Active Directory и в разделе Управление выберите Регистрация приложений.
Выберите Новая регистрация.
Заполните форму Регистрация приложения со следующими значениями, а затем выберите Зарегистрировать.
Имя: Соединитель инвентаризации частей
Поддерживаемые типы учетных записей: учетные записи только в этом каталоге организации (только майкрософт — один клиент)
URI перенаправления: оставьте пустым
На странице Обзора соединителя инвентаризации частей скопируйте значения Идентификатор приложения (клиента) и Идентификатор каталога (клиента). В следующем разделе вам потребуется и то, и другое.
Выберите Разрешения API в разделе Управление.
Выберите Добавить разрешение, а затем выберите Microsoft Graph.
Выберите Разрешения приложения, а затем — разрешение ExternalItem.ReadWrite.All . Выберите Добавить разрешения.
Выберите Предоставить согласие администратора для {TENANT}, а затем нажмите кнопку Да при появлении запроса.
Выберите Секреты сертификатов & в разделе Управление, а затем выберите Новый секрет клиента.
Введите описание и выберите срок действия секрета, а затем нажмите кнопку Добавить.
Скопируйте и сохраните новый секрет; Он понадобится в следующем разделе.
Создание приложения
На этом шаге вы создадите консольное приложение .NET Core. После этого вы создадите новое подключение, зарегистрируете схему и синхронизируете элементы.
Создание консольного приложения .NET Core
Запустите Visual Studio 2019 и перейдите в раздел Файл>новый>проект.
Выберите шаблон Консольное приложение (.NET Core) и нажмите кнопку Далее.
Введите имя проекта: PartsInventoryConnector.
Установите флажок Разместить решение и проект в одном каталоге, а затем нажмите кнопку Создать.
Важно!
Перед переходом к следующему шагу скопируйте файл ApplianceParts.csv в корневую папку проекта.
Добавление пакетов NuGet
Чтобы добавить пакеты NuGet, сначала щелкните правой кнопкой мыши проект решение, а затем выберите Открыть в терминале.
Затем выполните следующие команды интерфейса командной строки (CLI) в командной строке разработчика.
dotnet add package CsvHelper --version 12.1.2
dotnet add package Microsoft.EntityFrameworkCore.Design --version 3.1.3
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 3.1.3
dotnet add package Microsoft.Extensions.Configuration.UserSecrets --version 3.1.3
dotnet add package Microsoft.Graph.Beta --version 0.17.0-preview
dotnet add package Microsoft.Identity.Client --version 4.13.0
Совет
В случае add package
сбоя команды проверка источник пакета проекта:
- Выберите проект в Обозреватель решений.
- Перейдите в раздел Инструменты> Диспетчерпакетов> NugetПараметры диспетчера пакетов.
- Проверьте источники пакетов и убедитесь, что nuget.org установлен в качестве источника пакета.
- Имя: nuget.org
- Источник: https://api.nuget.org/v3/index.json
Добавление проверки подлинности с помощью Azure AD
Эта проверка подлинности необходима для получения необходимого маркера доступа OAuth для вызова API соединителей.
Создайте каталог с именем Authentication в каталоге PartsInventoryConnector .
Создайте файл в каталоге проверки подлинности с именем ClientCredentialAuthProvider.cs, а затем поместите в этот файл следующий код:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Microsoft.Identity.Client; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; namespace PartsInventoryConnector.Authentication { public class ClientCredentialAuthProvider : IAuthenticationProvider { private IConfidentialClientApplication _msalClient; private int _maxRetries = 3; public ClientCredentialAuthProvider(string appId, string tenantId, string secret) { _msalClient = ConfidentialClientApplicationBuilder .Create(appId) .WithTenantId(tenantId) .WithClientSecret(secret) .Build(); } public async Task AuthenticateRequestAsync(HttpRequestMessage request) { int retryCount = 0; do { try { var result = await _msalClient .AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" }) .ExecuteAsync(); if (!string.IsNullOrEmpty(result.AccessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken); break; } } catch (MsalServiceException serviceException) { if (serviceException.ErrorCode == "temporarily_unavailable") { await Task.Delay(10000); } else { throw serviceException; } } catch (Exception exception) { throw exception; } retryCount++; } while (retryCount < _maxRetries); } } }
Добавление пользовательского интерфейса
Создайте каталог в каталоге PartsInventoryConnector с именем Console.
Создайте файл в каталоге Консоли с именем MenuChoice.cs, а затем поместите в этот файл следующий код:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. namespace PartsInventoryConnector.Console { public enum MenuChoice { Invalid = 0, CreateConnection, RegisterSchema, PushAllItems, Exit } }
Настройка модели данных
Создайте каталог в каталоге PartsInventoryConnector с именем Models.
Создайте файл в каталоге Models с именем AppliancePart.cs, а затем поместите в этот файл следующий код:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Newtonsoft.Json; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PartsInventoryConnector.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; } [JsonProperty("appliances@odata.type")] private const string AppliancesODataType = "Collection(String)"; public List<string> Appliances { get; set; } public Properties AsExternalItemProperties() { 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; } } }
Создайте файл в каталоге Models с именем ApplianceDbContext.cs, а затем поместите в этот файл следующий код:
using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; namespace PartsInventoryConnector.Models { public class ApplianceDbContext : DbContext { public DbSet<AppliancePart> Parts { get; set; } 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 => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject<List<string>>(v) ); // 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; } } }
Создайте каталог с именем Data в каталоге PartsInventoryConnector .
Создайте новый файл в каталоге Данных с именем CsvDataLoader.cs, а затем поместите в этот файл следующий код:
using CsvHelper; using CsvHelper.Configuration; using CsvHelper.TypeConversion; using PartsInventoryConnector.Models; using System.Collections.Generic; using System.IO; using System.Linq; 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)) { csv.Configuration.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(';'); 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>(); } } }
Написание вспомогательной службы Microsoft Graph
Создайте каталог с именем MicrosoftGraph в каталоге PartsInventoryConnector .
Создайте в каталоге MicrosoftGraph новый файл с именем CustomSerializer.cs, а затем поместите в него следующий код:
using Microsoft.Graph; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using System; using System.IO; namespace PartsInventoryConnector.MicrosoftGraph { // The Microsoft Graph SDK serializes enumerations in camelCase. // The Microsoft Graph service currently requires the PropertyType enum // to be PascalCase. This will override the Microsoft Graph serialization // If the Microsoft Graph service changes to accept camelCase this will no // longer be necessary. class CustomContractResolver : DefaultContractResolver { protected override JsonConverter ResolveContractConverter(Type objectType) { if (typeof(PropertyType).IsAssignableFrom(objectType)) { // This default converter uses PascalCase return new StringEnumConverter(); } return base.ResolveContractConverter(objectType); } } // In order to hook up the custom contract resolver for // PropertyType, we need to implement a custom serializer to // pass to the MicrosoftGraphServiceClient. public class CustomSerializer : ISerializer { private Serializer _microsoftGraphSerializer; private JsonSerializerSettings _jsonSerializerSettings; public CustomSerializer() { _microsoftGraphSerializer = new Serializer(); _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CustomContractResolver() }; } // For deserialize, just pass through to the default // Microsoft Graph SDK serializer public T DeserializeObject<T>(Stream stream) { return _microsoftGraphSerializer.DeserializeObject<T>(stream); } // For deserialize, just pass through to the default // Microsoft Graph SDK serializer public T DeserializeObject<T>(string inputString) { return _microsoftGraphSerializer.DeserializeObject<T>(inputString); } public string SerializeObject(object serializeableObject) { // If a Schema object is being serialized, do the conversion // ourselves if (serializeableObject is Schema) { var foo = JsonConvert.SerializeObject(serializeableObject, _jsonSerializerSettings); return foo; } // Otherwise, just pass through to the default Microsoft Graph SDK serializer return _microsoftGraphSerializer.SerializeObject(serializeableObject); } } }
Создайте новый файл в каталоге Microsoft Graph с именем MicrosoftGraphHelper.cs, а затем поместите в него следующий код. Этот код содержит методы, использующие MicrosoftGraphServiceClient для создания и отправки вызовов в службу Microsoft Graph и обработки ответа.
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Graph; using Newtonsoft.Json; using System.Net.Http; using System.Threading.Tasks; namespace PartsInventoryConnector.MicrosoftGraph { public class MicrosoftGraphHelper { private GraphServiceClient _microsoftGraphClient; public MicrosoftGraphHelper(IAuthenticationProvider authProvider) { // Configure a default HttpProvider with our // custom serializer to handle the PropertyType serialization var serializer = new CustomSerializer(); var httpProvider = new HttpProvider(serializer); // Initialize the Microsoft Graph client _microsoftGraphClient = new GraphServiceClient(authProvider, httpProvider); } } }
Инициализация вспомогательной службы Microsoft Graph
Откройте Файл Program.cs и замените все содержимое следующим кодом:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using PartsInventoryConnector.Authentication;
using PartsInventoryConnector.Console;
using PartsInventoryConnector.Data;
using PartsInventoryConnector.MicrosoftGraph;
using PartsInventoryConnector.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace PartsInventoryConnector
{
class Program
{
private static MicrosoftGraphHelper _microsoftGraphHelper;
private static ExternalConnection _currentConnection;
private static string _tenantId;
static async Task Main(string[] args)
{
try
{
// Load configuration from appsettings.json
var appConfig = LoadAppSettings();
if (appConfig == null)
{
return;
}
// Save tenant ID for setting ACL on items
_tenantId = appConfig["tenantId"];
// Initialize the auth provider
var authProvider = new ClientCredentialAuthProvider(
appConfig["appId"],
appConfig["tenantId"],
appConfig["appSecret"]
);
// Check if the database is empty
using (var db = new ApplianceDbContext())
{
if (db.Parts.IgnoreQueryFilters().Count() <= 0)
{
ImportCsvToDatabase(db, "ApplianceParts.csv");
}
}
_microsoftGraphHelper = new MicrosoftGraphHelper(authProvider);
do
{
var userChoice = DoMenuPrompt();
switch (userChoice)
{
case MenuChoice.CreateConnection:
await CreateConnectionAsync();
break;
case MenuChoice.RegisterSchema:
await RegisterSchemaAsync();
break;
case MenuChoice.PushAllItems:
await UpdateItemsFromDatabase();
break;
case MenuChoice.Exit:
// Exit the program
return;
case MenuChoice.Invalid:
default:
break;
}
} while (true);
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}
}
private static void ImportCsvToDatabase(ApplianceDbContext db, string partsFilePath)
{
var parts = CsvDataLoader.LoadPartsFromCsv(partsFilePath);
db.AddRange(parts);
db.SaveChanges();
}
private static MenuChoice DoMenuPrompt()
{
System.Console.WriteLine($"Current connection: {(_currentConnection == null ? "NONE" : _currentConnection.Name)}");
System.Console.WriteLine("Please choose one of the following options:");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.CreateConnection)}. Create a connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.RegisterSchema)}. Register schema for current connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.PushAllItems)}. Push ALL items to current connection");
System.Console.WriteLine($"{Convert.ToInt32(MenuChoice.Exit)}. Exit");
try
{
var choice = int.Parse(System.Console.ReadLine());
return (MenuChoice)choice;
}
catch (FormatException)
{
return MenuChoice.Invalid;
}
}
private static string PromptForInput(string prompt, bool valueRequired)
{
string response = null;
do
{
System.Console.WriteLine($"{prompt}:");
response = System.Console.ReadLine();
if (valueRequired && string.IsNullOrEmpty(response))
{
System.Console.WriteLine("You must provide a value");
}
} while (valueRequired && string.IsNullOrEmpty(response));
return response;
}
private static IConfigurationRoot LoadAppSettings()
{
var appConfig = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
// Check for required settings
if (string.IsNullOrEmpty(appConfig["appId"]) ||
string.IsNullOrEmpty(appConfig["appSecret"]) ||
string.IsNullOrEmpty(appConfig["tenantId"]))
{
return null;
}
return appConfig;
}
}
}
Создание подключения
В разделе MicrosoftGraph откройте файл MicrosoftGraphHelper.cs и добавьте следующий код после метода Конструктора :
public async Task<ExternalConnection> CreateConnectionAsync(string id, string name, string description) { var newConnection = new ExternalConnection { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, Id = id, Name = name, Description = description }; return await _microsoftGraphClient.External.Connections.Request().AddAsync(newConnection); }
Откройте файл Program.cs и добавьте следующий код после метода Main :
private static async Task CreateConnectionAsync() { var connectionId = PromptForInput("Enter a unique ID for the new connection", true); var connectionName = PromptForInput("Enter a name for the new connection", true); var connectionDescription = PromptForInput("Enter a description for the new connection", false); try { // Create the connection _currentConnection = await _microsoftGraphHelper.CreateConnectionAsync(connectionId, connectionName, connectionDescription); System.Console.WriteLine("New connection created"); System.Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(_currentConnection, Newtonsoft.Json.Formatting.Indented)); } catch (ServiceException serviceException) { System.Console.WriteLine(serviceException.Message); return; } }
Регистрация схемы
В разделе MicrosoftGraph откройте файл MicrosoftGraphHelper.cs и добавьте следующий код после метода Конструктора :
public async Task RegisterSchemaAsync(string connectionId, Schema schema) { // Need access to the HTTP response here since we are doing an // async request. The new schema object isn't returned, we need // the Location header from the response var asyncNewSchemaRequest = _microsoftGraphClient.External.Connections[connectionId].Schema .Request() .Header("Prefer", "respond-async") .GetHttpRequestMessage(); asyncNewSchemaRequest.Method = HttpMethod.Post; asyncNewSchemaRequest.Content = _microsoftGraphClient.HttpProvider.Serializer.SerializeAsJsonContent(schema); var response = await _microsoftGraphClient.HttpProvider.SendAsync(asyncNewSchemaRequest); if (response.IsSuccessStatusCode) { // Get the operation ID from the Location header var operationId = ExtractOperationId(response.Headers.Location); await CheckSchemaStatusAsync(connectionId, operationId); } else { throw new ServiceException( new Error { Code = response.StatusCode.ToString(), Message = "Registering schema failed" } ); } } private string ExtractOperationId(System.Uri uri) { int numSegments = uri.Segments.Length; return uri.Segments[numSegments - 1]; } public async Task CheckSchemaStatusAsync(string connectionId, string operationId) { do { var operation = await _microsoftGraphClient.External.Connections[connectionId] .Operations[operationId] .Request() .GetAsync(); if (operation.Status == ConnectionOperationStatus.Completed) { return; } else if (operation.Status == ConnectionOperationStatus.Failed) { throw new ServiceException( new Error { Code = operation.Error.ErrorCode, Message = operation.Error.Message } ); } await Task.Delay(3000); } while (true); }
Откройте файл Program.cs и добавьте следующий код после метода Main :
private static async Task RegisterSchemaAsync() { if (_currentConnection == null) { System.Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } System.Console.WriteLine("Registering schema, this may take a moment..."); try { // Register the schema var schema = new Schema { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, BaseType = "microsoft.graph.externalItem", Properties = new List<Property> { new Property { Name = "partNumber", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "name", Type = PropertyType.String, IsQueryable = true, IsSearchable = true, IsRetrievable = true }, new Property { Name = "description", Type = PropertyType.String, IsQueryable = false, IsSearchable = true, IsRetrievable = true }, new Property { Name = "price", Type = PropertyType.Double, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "inventory", Type = PropertyType.Int64, IsQueryable = true, IsSearchable = false, IsRetrievable = true }, new Property { Name = "appliances", Type = PropertyType.StringCollection, IsQueryable = true, IsSearchable = true, IsRetrievable = true } } }; await _microsoftGraphHelper.RegisterSchemaAsync(_currentConnection.Id, schema); System.Console.WriteLine("Schema registered"); } catch (ServiceException serviceException) { System.Console.WriteLine($"{serviceException.StatusCode} error registering schema:"); System.Console.WriteLine(serviceException.Message); return; } }
Синхронизация элементов
В разделе Microsoft Graph откройте файл MicrosoftGraphHelper.cs и добавьте следующий код после метода Конструктора :
public async Task AddOrUpdateItem(string connectionId, ExternalItem item) { // The SDK's auto-generated request builder uses POST here, // which isn't correct. For now, get the HTTP request and change it // to PUT manually. var putItemRequest = _microsoftGraphClient.External.Connections[connectionId] .Items[item.Id].Request().GetHttpRequestMessage(); putItemRequest.Method = HttpMethod.Put; putItemRequest.Content = _microsoftGraphClient.HttpProvider.Serializer.SerializeAsJsonContent(item); var response = await _microsoftGraphClient.HttpProvider.SendAsync(putItemRequest); if (!response.IsSuccessStatusCode) { throw new ServiceException( new Error { Code = response.StatusCode.ToString(), Message = "Error indexing item." } ); } }
Откройте файл Program.cs и добавьте следующий код после метода Main :
private static async Task UpdateItemsFromDatabase() { if (_currentConnection == null) { System.Console.WriteLine("No connection selected. Please create a new connection or select an existing connection."); return; } List<AppliancePart> partsToUpload = null; using (var db = new ApplianceDbContext()) { partsToUpload = db.Parts.ToList(); } System.Console.WriteLine($"Processing {partsToUpload.Count()} add/updates"); foreach (var part in partsToUpload) { var newItem = new ExternalItem { Id = part.PartNumber.ToString(), Content = new ExternalItemContent { // Need to set to null, service returns 400 // if @odata.type property is sent ODataType = null, Type = ExternalItemContentType.Text, Value = part.Description }, Acl = new List<Acl> { new Acl { AccessType = AccessType.Grant, Type = AclType.Everyone, Value = _tenantId, IdentitySource = "Azure Active Directory" } }, Properties = part.AsExternalItemProperties() }; try { System.Console.Write($"Uploading part number {part.PartNumber}..."); await _microsoftGraphHelper.AddOrUpdateItem(_currentConnection.Id, newItem); System.Console.WriteLine("DONE"); } catch (ServiceException serviceException) { System.Console.WriteLine("FAILED"); System.Console.WriteLine($"{serviceException.StatusCode} error adding or updating part {part.PartNumber}"); System.Console.WriteLine(serviceException.Message); } } }
Настройка приложения
Откройте интерфейс командной строки (CLI) в каталоге, где находится PartsInventoryConnector.csproj.
Выполните следующую команду, чтобы инициализировать секреты пользователя для проекта.
dotnet user-secrets init
Выполните следующие команды, чтобы сохранить идентификатор приложения, секрет приложения и идентификатор клиента в хранилище секретов пользователя.
dotnet user-secrets set appId "YOUR_APP_ID_HERE" dotnet user-secrets set appSecret "YOUR_APP_SECRET_HERE" dotnet user-secrets set tenantId "YOUR_TENANT_ID_HERE"
Создание базы данных из CSV
Откройте интерфейс командной строки (CLI) в каталоге, где находится PartsInventoryConnector.csproj.
Выполните следующие команды:
dotnet ef migrations add InitialCreate dotnet ef database update
Примечание.
Выполните следующие команды, если схема изменяется в CSV-файле и отражает эти изменения в базе данных SQLite.
dotnet ef database drop
dotnet ef database update
Запуск приложения
На этом шаге вы создадите и запустите пример. Этот пример кода создаст новое подключение, зарегистрирует схему, а затем отправляет элементы из файлаApplianceParts.csv в это соединение.
- Откройте интерфейс командной строки (CLI) в каталоге PartsInventoryConnector .
- Используйте команду
dotnet build
для сборки примера. - Используйте команду
dotnet run
для запуска примера. - Выберите 1. Создайте подключение. Введите уникальный идентификатор, имя и описание для этого соединения.
- Выберите 2. Зарегистрируйте схему для текущего параметра подключения, а затем дождитесь завершения операции.
- Выберите 3. Отправка всех элементов в текущее подключение.
Примечание.
Если шаг 5 приводит к ошибке, подождите несколько минут и выберите 3. Отправка всех элементов в текущее подключение.
Поверхности данных в поиске
Чтобы пользователям было проще находить информацию, на которую у них есть разрешение, создайте вертикали поиска и типы результатов, чтобы настроить результаты поиска в Microsoft SharePoint, Microsoft Office и Поиске Майкрософт в Bing.
Создание вертикали
Чтобы создать и включить вертикаль поиска на уровне организации, войдите в Центр администрирования Microsoft 365 с помощью роли глобального администратора и выполните следующие действия.
- Перейдите в раздел ПараметрыНастройки аналитики>>поиска&.
- Перейдите в раздел По вертикали и нажмите кнопку Добавить.
- Укажите следующие сведения.
Присвойти имя вертикали: Части устройства.
Источник содержимого: соединитель, созданный с помощью приложения (Инвентаризация частей).
Добавление запроса: оставьте пустым.
Фильтры: оставьте пустым.
Создание типа результата
Чтобы создать тип результата, выполните следующее:
- Перейдите в раздел ПараметрыНастройки аналитики>>поиска&.
- Перейдите на вкладку Тип результата и нажмите кнопку Добавить.
- Укажите следующие сведения.
Имя: Часть устройства
Источник содержимого: соединитель, созданный в приложении.
Правила: нет
Вставьте содержимое файла result-type.json в текстовое поле конструктора макетов.
Поиск результатов
На этом шаге вы будете искать части в SharePoint.
Перейдите на корневой сайт SharePoint для своего клиента.
С помощью поля поиска в верхней части страницы найдите шарню.
Когда поиск завершится с 0 результатов, перейдите на вкладку Части устройства . Отображаются результаты соединителя.
Сводка
Вы успешно завершили работу с руководством по соединителям Microsoft Graph для .NET Core: вы создали пользовательский соединитель и использовали его для работы с поиском (Майкрософт).
Дальнейшие действия
Дополнительные сведения обо всех данных, к которым можно получить доступ с помощью пользовательского соединителя, см. в статье Общие сведения о соединителях Microsoft Graph.
Возникла проблема с этим разделом? Если это так, отправьте нам отзыв, чтобы мы исправили этот раздел.
Обратная связь
Отправить и просмотреть отзыв по