В моем приложении (веб-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); // <- это будет делаться всякий раз, когда какой-либо службе потребуется фактический экземпляр клиента
Как мне подойти к этой проблеме? Майкрософт рекомендует один экземпляр на весь срок службы приложения. Будет ли этот подход противоречить этому?
Заранее спасибо!





В моем случае я читал/записывал данные из/в двух арендаторов.
Я зарегистрировал как 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];
...
}
}
установить один интерфейс с несколькими инструментами?
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;
}
}
}
Различать клиента по имени класса - это как-то не правильно, и однозначно подвержено ошибкам, но все же - спасибо за Ваш ответ!
Мне удалось реализовать какую-то "фабрику", но в моем подходе я проверял, существует ли данный клиент с определенным (tenantId,clientId,clientSecret), а если нет - я добавлял его в словарь в памяти (просто как в вашем решении). Регистрация их в одном месте во время настройки приложения кажется гораздо лучшей идеей. Спасибо!