C# 中的两个 GraphServiceClient 单例实例

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

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 =&gt;
{
    return new GraphServiceClient(clientSecretCredential, new[] { &quot;https://graph.microsoft.com/.default&quot; });
});

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&lt;string, GraphServiceClient&gt; _clients;

    public GraphServiceClient this[string tenantId] =&gt; _clients[tenantId];

    public GraphServiceClientProvider(IDictionary&lt;string, GraphServiceClient&gt; 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 =&gt;
{
    var scopes = new[] { &quot;https://graph.microsoft.com/.default&quot; };
    var dict = new Dictionary&lt;string, GraphServiceClient&gt;
    {
        { &quot;tenantId1&quot;, new GraphServiceClient(new ClientSecretCredential(&quot;tenantId1&quot;, &quot;clientId1&quot;, &quot;clientSecret1&quot;, tokenCredentialOptions), scopes) },
        { &quot;tenantId2&quot;, new GraphServiceClient(new ClientSecretCredential(&quot;tenantId2&quot;, &quot;clientId2&quot;, &quot;clientSecret2&quot;, tokenCredentialOptions), scopes) }
    };
    return new GraphServiceClientProvider(dict);
});
builder.Services.AddSingleton&lt;IUsersService, UsersService&gt;();

Example of usage

public class UserService : IUserService
{
    private readonly GraphServiceClientProvider _clientProvider;

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

    public List&lt;User&gt; GetUsers(string tenantId)
    {
        var client = _clientProvider[tenantId];
        ...
    }
}

答案2

得分: 0

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

builder.Services.AddTransient&lt;ClientFlowService&gt;();
builder.Services.AddTransient&lt;ClientFlowService2&gt;();
builder.Services.AddTransient&lt;ClientServiceResolver&gt;(serviceProvider =&gt; client =&gt;
{
    return client switch
    {
        &quot;client1&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService&gt;(),
        &quot;client2&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService2&gt;(),
        _ =&gt; 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[] { &quot;https://graph.microsoft.com/.default&quot; };
            var tenantId = &quot;yyy&quot;;
            var clientId = &quot;yyy&quot;;
            var clientSecret = &quot;yyy&quot;;
            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[] { &quot;https://graph.microsoft.com/.default&quot; };
            var tenantId = &quot;xx&quot;;
            var clientId = &quot;xx&quot;;
            var clientSecret = &quot;xx&quot;;
            var clientSecretCredential = new ClientSecretCredential(
                            tenantId, clientId, clientSecret);
            var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
            return graphClient;
        }
    }
}
英文:

set one interface with multiple implements?

builder.Services.AddTransient&lt;ClientFlowService&gt;();
builder.Services.AddTransient&lt;ClientFlowService2&gt;();
builder.Services.AddTransient&lt;ClientServiceResolver&gt;(serviceProvider =&gt; client =&gt;
{
    return client switch
    {
        &quot;client1&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService&gt;(),
        &quot;client2&quot; =&gt; serviceProvider.GetService&lt;ClientFlowService2&gt;(),
        _ =&gt; 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[] { &quot;https://graph.microsoft.com/.default&quot; };
            var tenantId = &quot;yyy&quot;;
            var clientId = &quot;yyy&quot;;
            var clientSecret = &quot;yyy&quot;;
            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[] { &quot;https://graph.microsoft.com/.default&quot; };
            var tenantId = &quot;xx&quot;;
            var clientId = &quot;xx&quot;;
            var clientSecret = &quot;xx&quot;;
            var clientSecretCredential = new ClientSecretCredential(
                            tenantId, clientId, clientSecret);
            var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
            return graphClient;
        }
    }
}

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:

确定