英文:
C# Two GraphServiceClient singleton instances
问题
在我的应用程序(.NET Web API)中,我需要使用两组完全不同的凭据调用 Microsoft Graph API(位于不同租户中)。之前当我只使用一个凭据时,情况很简单 - 我只需要在 Program.cs
的 DI 部分进行设置:
var clientSecretCredential = new ClientSecretCredential(b2cSettings.TenantId, b2cSettings.ClientId, b2cSettings.ClientSecret, tokenCredentialOptions);
// 在应用程序的生命周期内只使用一个客户端实例
services.AddSingleton(sp =>
{
return new GraphServiceClient(clientSecretCredential, new[] { "https://graph.microsoft.com/.default" });
});
这使我可以将它注入到我需要的任何服务中,就像这样:
public GraphApiClient(
GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
显然,无法重复使用这个方法来处理第二组凭据 - 它只会选择最后注册的 GraphServiceClient
实例。
这似乎是一种常见的模式,但似乎不太适合工厂模式(我只希望在应用程序生命周期内有两个实例,每个实例都使用不同的凭据)。我还想知道服务 "client"(使用它的类)如何在运行时指定要检索哪个 GraphServiceClient
。
我想象中有一些类似以下方法的 "聚合" 类:
RegisterGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret);
// <- 这将在 Program.cs 中执行一次
RetrieveGraphApiClientWithCredentials(string tenantId, string clientId, string clientSecret);
// <- 这将在某些服务需要实际客户端实例时执行
我该如何解决这个问题?Microsoft 建议在应用程序的生命周期内使用单个实例。这种方法是否会与此相冲突?
提前感谢您的回答!
英文:
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!
答案1
得分: 1
以下是您提供的代码的中文翻译部分:
在我的使用情况下,我从/向两个租户读取/写入数据。
我已经注册了一个保存由租户ID访问的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];
// ...
}
}
请注意,这些翻译仅包括代码的主要部分,不包括注释或其他文本。如果您需要更多信息或有其他问题,请告诉我。
英文:
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];
...
}
}
答案2
得分: 0
设置一个接口,用于多个实现?
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;
}
}
}
英文:
set one interface with multiple implements?
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;
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论