In my application (.NET Web API), I need to call Microsoft Graph API using two different credentials set (completely different tenant).
When I was using only one, the case was simple - I just did a setup in DI part of 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" });
});
which allowed me to just inject it to whichever service I needed, like so:
public GraphApiClient(
GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
Obviously, there is no way to re-use that for second set of credentials - it will just pick the last registered GraphServiceClient
instance.
It smells like some common pattern, but it does not seem to be a good candidate for factory (I want only TWO instances across app lifetime - each with different credentials). I also wonder how would service "client" (class using it) specify which GraphServiceClient
to retrieve in runtime.
I imagine some "aggregate" class with methods like:
RegisterGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret);
// <- this would be done once, in Program.cs
RetrieveGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret);
// <- this would be done whenever some service would need actual client instance
How should I approach this problem? Microsoft advise single instance for the lifetime of the application. Will this approach collide with that?
Thank You in advance!
In my use case I was reading/writing data from/to two tenants.
I've registered as a singleton a class that holds GraphServiceClient
instances accessed by tenant id
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);
}
}
in 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>();
Example of usage
public class UserService : IUserService
{
private readonly GraphServiceClientProvider _clientProvider;
public UserService(GraphServiceClientProvider clientProvider)
{
_clientProvider = clientProvider;
}
public List<User> GetUsers(string tenantId)
{
var client = _clientProvider[tenantId];
...
}
}