C# Два одноэлементных экземпляра GraphServiceClient

В моем приложении (веб-API .NET) мне нужно вызвать Microsoft Graph API, используя два разных набора учетных данных (совершенно другой клиент). Когда я использовал только один, случай был простым — я просто сделал настройку в части DI Program.cs:

var clientSecretCredential = new ClientSecretCredential(b2cSettings.TenantId, b2cSettings.ClientId, b2cSettings.ClientSecret, tokenCredentialOptions);

// Use a single client instance for the lifetime of the application
services.AddSingleton(sp =>
{
    return new GraphServiceClient(clientSecretCredential, new[] { "https://graph.microsoft.com/.default" });
});

что позволило мне просто внедрить его в любой сервис, который мне нужен, например:

    public GraphApiClient(
        GraphServiceClient graphServiceClient)
    {
        _graphServiceClient = graphServiceClient;
    }

Очевидно, что нет возможности повторно использовать это для второго набора учетных данных — он просто выберет последний зарегистрированный экземпляр GraphServiceClient.

Это похоже на какой-то общий шаблон, но он не кажется хорошим кандидатом для фабрики (мне нужно только ДВА экземпляра за все время существования приложения — каждый с разными учетными данными). Мне также интересно, как служба «клиент» (использующий его класс) укажет, какие GraphServiceClient извлекать во время выполнения.

Я представляю какой-то "агрегатный" класс с такими методами, как:

RegisterGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret); // <- это будет сделано один раз, в Program.cs

RetrieveGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret); // <- это будет делаться всякий раз, когда какой-либо службе потребуется фактический экземпляр клиента

Как мне подойти к этой проблеме? Майкрософт рекомендует один экземпляр на весь срок службы приложения. Будет ли этот подход противоречить этому?

Заранее спасибо!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В моем случае я читал/записывал данные из/в двух арендаторов.

Я зарегистрировал как singleton класс, который содержит экземпляры GraphServiceClient, доступ к которым осуществляется по идентификатору арендатора.

public class GraphServiceClientProvider : IDisposable
{
    private bool disposedValue;

    private readonly IDictionary<string, GraphServiceClient> _clients;

    public GraphServiceClient this[string tenantId] => _clients[tenantId];

    public GraphServiceClientProvider(IDictionary<string, GraphServiceClient> clients)
    {
        _clients = clients;
    }       

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                foreach (var client in _clients.Values)
                {
                    client.Dispose();
                }
            }
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

в Program.cs

var tokenCredentialOptions = new ClientSecretCredentialOptions();
builder.Services.AddSingleton(x =>
{
    var scopes = new[] { "https://graph.microsoft.com/.default" };
    var dict = new Dictionary<string, GraphServiceClient>
    {
        { "tenantId1", new GraphServiceClient(new ClientSecretCredential("tenantId1", "clientId1", "clientSecret1", tokenCredentialOptions), scopes) },
        { "tenantId2", new GraphServiceClient(new ClientSecretCredential("tenantId2", "clientId2", "clientSecret2", tokenCredentialOptions), scopes) }
    };
    return new GraphServiceClientProvider(dict);
});
builder.Services.AddSingleton<IUsersService, UsersService>();

Пример использования

public class UserService : IUserService
{
    private readonly GraphServiceClientProvider _clientProvider;

    public UserService(GraphServiceClientProvider clientProvider)
    {
        _clientProvider = clientProvider;
    }

    public List<User> GetUsers(string tenantId)
    {
        var client = _clientProvider[tenantId];
        ...
    }
}

Мне удалось реализовать какую-то "фабрику", но в моем подходе я проверял, существует ли данный клиент с определенным (tenantId,clientId,clientSecret), а если нет - я добавлял его в словарь в памяти (просто как в вашем решении). Регистрация их в одном месте во время настройки приложения кажется гораздо лучшей идеей. Спасибо!

Kamil Turowski 18.05.2023 22:17

установить один интерфейс с несколькими инструментами?

builder.Services.AddTransient<ClientFlowService>();
builder.Services.AddTransient<ClientFlowService2>();
builder.Services.AddTransient<ClientServiceResolver>(serviceProvider => client =>
{
    return client switch
    {
        "client1" => serviceProvider.GetService<ClientFlowService>(),
        "client2" => serviceProvider.GetService<ClientFlowService2>(),
        _ => throw new NotImplementedException()
    };
});

IClientFlowService.cs :

using Microsoft.Graph;

namespace WebMvcException.Services
{
    public delegate IClientService ClientServiceResolver(string client);

    public interface IClientService
    {
        public GraphServiceClient getClient();
    }

    public interface IClientFlowService1 : IClientService { }
    public interface IClientFlowService2 : IClientService { }
}

ClientFlowService.cs

using Azure.Identity;
using Microsoft.Graph;

namespace WebMvcException.Services
{
    public class ClientFlowService : IClientFlowService1
    {
        public GraphServiceClient getClient()
        {
            var scopes = new[] { "https://graph.microsoft.com/.default" };
            var tenantId = "yyy";
            var clientId = "yyy";
            var clientSecret = "yyy";
            var clientSecretCredential = new ClientSecretCredential(
                            tenantId, clientId, clientSecret);
            var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
            return graphClient;
        }
    }

    public class ClientFlowService2 : IClientFlowService2
    {
        public GraphServiceClient getClient()
        {
            var scopes = new[] { "https://graph.microsoft.com/.default" };
            var tenantId = "xx";
            var clientId = "xx";
            var clientSecret = "xx";
            var clientSecretCredential = new ClientSecretCredential(
                            tenantId, clientId, clientSecret);
            var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
            return graphClient;
        }
    }
}

Различать клиента по имени класса - это как-то не правильно, и однозначно подвержено ошибкам, но все же - спасибо за Ваш ответ!

Kamil Turowski 18.05.2023 22:18

Другие вопросы по теме