C# 中的两个 GraphServiceClient 单例实例

huangapple go评论105阅读模式
英文:

C# Two GraphServiceClient singleton instances

问题

在我的应用程序(.NET Web API)中,我需要使用两组完全不同的凭据调用 Microsoft Graph API(位于不同租户中)。之前当我只使用一个凭据时,情况很简单 - 我只需要在 Program.cs 的 DI 部分进行设置:

  1. var clientSecretCredential = new ClientSecretCredential(b2cSettings.TenantId, b2cSettings.ClientId, b2cSettings.ClientSecret, tokenCredentialOptions);
  2. // 在应用程序的生命周期内只使用一个客户端实例
  3. services.AddSingleton(sp =>
  4. {
  5. return new GraphServiceClient(clientSecretCredential, new[] { "https://graph.microsoft.com/.default" });
  6. });

这使我可以将它注入到我需要的任何服务中,就像这样:

  1. public GraphApiClient(
  2. GraphServiceClient graphServiceClient)
  3. {
  4. _graphServiceClient = graphServiceClient;
  5. }

显然,无法重复使用这个方法来处理第二组凭据 - 它只会选择最后注册的 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:

  1. var clientSecretCredential = new ClientSecretCredential(b2cSettings.TenantId, b2cSettings.ClientId, b2cSettings.ClientSecret, tokenCredentialOptions);
  2. // Use a single client instance for the lifetime of the application
  3. services.AddSingleton(sp =&gt;
  4. {
  5. return new GraphServiceClient(clientSecretCredential, new[] { &quot;https://graph.microsoft.com/.default&quot; });
  6. });

which allowed me to just inject it to whichever service I needed, like so:

  1. public GraphApiClient(
  2. GraphServiceClient graphServiceClient)
  3. {
  4. _graphServiceClient = graphServiceClient;
  5. }

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实例的单例类。

  1. public class GraphServiceClientProvider : IDisposable
  2. {
  3. private bool disposedValue;
  4. private readonly IDictionary<string, GraphServiceClient> _clients;
  5. public GraphServiceClient this[string tenantId] => _clients[tenantId];
  6. public GraphServiceClientProvider(IDictionary<string, GraphServiceClient> clients)
  7. {
  8. _clients = clients;
  9. }
  10. protected virtual void Dispose(bool disposing)
  11. {
  12. if (!disposedValue)
  13. {
  14. if (disposing)
  15. {
  16. foreach (var client in _clients.Values)
  17. {
  18. client.Dispose();
  19. }
  20. }
  21. disposedValue = true;
  22. }
  23. }
  24. public void Dispose()
  25. {
  26. Dispose(disposing: true);
  27. GC.SuppressFinalize(this);
  28. }
  29. }

Program.cs中:

  1. var tokenCredentialOptions = new ClientSecretCredentialOptions();
  2. builder.Services.AddSingleton(x =>
  3. {
  4. var scopes = new[] { "https://graph.microsoft.com/.default" };
  5. var dict = new Dictionary<string, GraphServiceClient>
  6. {
  7. { "tenantId1", new GraphServiceClient(new ClientSecretCredential("tenantId1", "clientId1", "clientSecret1", tokenCredentialOptions), scopes) },
  8. { "tenantId2", new GraphServiceClient(new ClientSecretCredential("tenantId2", "clientId2", "clientSecret2", tokenCredentialOptions), scopes) }
  9. };
  10. return new GraphServiceClientProvider(dict);
  11. });
  12. builder.Services.AddSingleton<IUsersService, UsersService>();

用法示例:

  1. public class UserService : IUserService
  2. {
  3. private readonly GraphServiceClientProvider _clientProvider;
  4. public UserService(GraphServiceClientProvider clientProvider)
  5. {
  6. _clientProvider = clientProvider;
  7. }
  8. public List<User> GetUsers(string tenantId)
  9. {
  10. var client = _clientProvider[tenantId];
  11. // ...
  12. }
  13. }

请注意,这些翻译仅包括代码的主要部分,不包括注释或其他文本。如果您需要更多信息或有其他问题,请告诉我。

英文:

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

  1. public class GraphServiceClientProvider : IDisposable
  2. {
  3. private bool disposedValue;
  4. private readonly IDictionary&lt;string, GraphServiceClient&gt; _clients;
  5. public GraphServiceClient this[string tenantId] =&gt; _clients[tenantId];
  6. public GraphServiceClientProvider(IDictionary&lt;string, GraphServiceClient&gt; clients)
  7. {
  8. _clients = clients;
  9. }
  10. protected virtual void Dispose(bool disposing)
  11. {
  12. if (!disposedValue)
  13. {
  14. if (disposing)
  15. {
  16. foreach (var client in _clients.Values)
  17. {
  18. client.Dispose();
  19. }
  20. }
  21. disposedValue = true;
  22. }
  23. }
  24. public void Dispose()
  25. {
  26. Dispose(disposing: true);
  27. GC.SuppressFinalize(this);
  28. }
  29. }

in Program.cs

  1. var tokenCredentialOptions = new ClientSecretCredentialOptions();
  2. builder.Services.AddSingleton(x =&gt;
  3. {
  4. var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
  5. var dict = new Dictionary&lt;string, GraphServiceClient&gt;
  6. {
  7. { &quot;tenantId1&quot;, new GraphServiceClient(new ClientSecretCredential(&quot;tenantId1&quot;, &quot;clientId1&quot;, &quot;clientSecret1&quot;, tokenCredentialOptions), scopes) },
  8. { &quot;tenantId2&quot;, new GraphServiceClient(new ClientSecretCredential(&quot;tenantId2&quot;, &quot;clientId2&quot;, &quot;clientSecret2&quot;, tokenCredentialOptions), scopes) }
  9. };
  10. return new GraphServiceClientProvider(dict);
  11. });
  12. builder.Services.AddSingleton&lt;IUsersService, UsersService&gt;();

Example of usage

  1. public class UserService : IUserService
  2. {
  3. private readonly GraphServiceClientProvider _clientProvider;
  4. public UserService(GraphServiceClientProvider clientProvider)
  5. {
  6. _clientProvider = clientProvider;
  7. }
  8. public List&lt;User&gt; GetUsers(string tenantId)
  9. {
  10. var client = _clientProvider[tenantId];
  11. ...
  12. }
  13. }

答案2

得分: 0

设置一个接口,用于多个实现?

  1. builder.Services.AddTransient&lt;ClientFlowService&gt;();
  2. builder.Services.AddTransient&lt;ClientFlowService2&gt;();
  3. builder.Services.AddTransient&lt;ClientServiceResolver&gt;(serviceProvider =&gt; client =&gt;
  4. {
  5. return client switch
  6. {
  7. &quot;client1&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService&gt;(),
  8. &quot;client2&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService2&gt;(),
  9. _ =&gt; throw new NotImplementedException()
  10. };
  11. });
  12. IClientFlowService.cs :
  13. using Microsoft.Graph;
  14. namespace WebMvcException.Services
  15. {
  16. public delegate IClientService ClientServiceResolver(string client);
  17. public interface IClientService
  18. {
  19. public GraphServiceClient getClient();
  20. }
  21. public interface IClientFlowService1 : IClientService { }
  22. public interface IClientFlowService2 : IClientService { }
  23. }
  24. ClientFlowService.cs
  25. using Azure.Identity;
  26. using Microsoft.Graph;
  27. namespace WebMvcException.Services
  28. {
  29. public class ClientFlowService : IClientFlowService1
  30. {
  31. public GraphServiceClient getClient()
  32. {
  33. var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
  34. var tenantId = &quot;yyy&quot;;
  35. var clientId = &quot;yyy&quot;;
  36. var clientSecret = &quot;yyy&quot;;
  37. var clientSecretCredential = new ClientSecretCredential(
  38. tenantId, clientId, clientSecret);
  39. var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
  40. return graphClient;
  41. }
  42. }
  43. public class ClientFlowService2 : IClientFlowService2
  44. {
  45. public GraphServiceClient getClient()
  46. {
  47. var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
  48. var tenantId = &quot;xx&quot;;
  49. var clientId = &quot;xx&quot;;
  50. var clientSecret = &quot;xx&quot;;
  51. var clientSecretCredential = new ClientSecretCredential(
  52. tenantId, clientId, clientSecret);
  53. var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
  54. return graphClient;
  55. }
  56. }
  57. }
英文:

set one interface with multiple implements?

  1. builder.Services.AddTransient&lt;ClientFlowService&gt;();
  2. builder.Services.AddTransient&lt;ClientFlowService2&gt;();
  3. builder.Services.AddTransient&lt;ClientServiceResolver&gt;(serviceProvider =&gt; client =&gt;
  4. {
  5. return client switch
  6. {
  7. &quot;client1&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService&gt;(),
  8. &quot;client2&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService2&gt;(),
  9. _ =&gt; throw new NotImplementedException()
  10. };
  11. });
  12. IClientFlowService.cs :
  13. using Microsoft.Graph;
  14. namespace WebMvcException.Services
  15. {
  16. public delegate IClientService ClientServiceResolver(string client);
  17. public interface IClientService
  18. {
  19. public GraphServiceClient getClient();
  20. }
  21. public interface IClientFlowService1 : IClientService { }
  22. public interface IClientFlowService2 : IClientService { }
  23. }
  24. ClientFlowService.cs
  25. using Azure.Identity;
  26. using Microsoft.Graph;
  27. namespace WebMvcException.Services
  28. {
  29. public class ClientFlowService : IClientFlowService1
  30. {
  31. public GraphServiceClient getClient()
  32. {
  33. var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
  34. var tenantId = &quot;yyy&quot;;
  35. var clientId = &quot;yyy&quot;;
  36. var clientSecret = &quot;yyy&quot;;
  37. var clientSecretCredential = new ClientSecretCredential(
  38. tenantId, clientId, clientSecret);
  39. var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
  40. return graphClient;
  41. }
  42. }
  43. public class ClientFlowService2 : IClientFlowService2
  44. {
  45. public GraphServiceClient getClient()
  46. {
  47. var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
  48. var tenantId = &quot;xx&quot;;
  49. var clientId = &quot;xx&quot;;
  50. var clientSecret = &quot;xx&quot;;
  51. var clientSecretCredential = new ClientSecretCredential(
  52. tenantId, clientId, clientSecret);
  53. var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
  54. return graphClient;
  55. }
  56. }
  57. }

huangapple
  • 本文由 发表于 2023年5月15日 02:55:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/76249199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定