简体   繁体   中英

Dependency injection problem - Initializing remote service connection

In my.Net Core 3.0 app I want to use the Microsoft Graph Nuget library. I have created a connection class that authenticates my application using [MSAL][1] and then creates the connection and returns this. My idea was to inject this connection object in the constructor using Dependency Injection . However, since the method that creates the connection is async, I seem to have a problem how to use it in the constructor.

My Connect Class

 public class AuthorizeGraphApi: IAuthorizeGraphApi
    {
        private readonly IConfiguration _config;

        public AuthorizeGraphApi(IConfiguration config)
        {
            _config = config;
        }

        public async Task<GraphServiceClient> ConnectToAAD()
        {
            string accessToken = await GetAccessTokenFromAuthorityAsync();
            var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
                requestMessage
                    .Headers
                    .Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                return Task.FromResult(0);
            }));
            return graphServiceClient;
        }

        private async Task<string> GetAccessTokenFromAuthorityAsync()
        {
            // clientid, authUri, etc removed for this example.
            IConfidentialClientApplication _conn;
            _conn = ConfidentialClientApplicationBuilder.Create(clientId)
                .WithClientSecret(clientSecret)
                .WithAuthority(new Uri(authUri))
                .Build();
           string[] scopes = new string[] { $"api://{clientId}/.default" };

           AuthenticationResult result = null;
           // AcquireTokenForClient only has async method.
           result = await _conn.AcquireTokenForClient(scopes)
              .ExecuteAsync();

           return result.AccessToken;
        }
    }

My Graph Service to send requests

public class AzureIntuneService
{
    private readonly IAuthorizeGraphApi _graphClient;
    public AzureIntuneService(IAuthorizeGraphApi client)
    {
        //Gives: cannot implicitely convert to Threading.Tasks.Task.... error
        _graphClient = client.ConnectToAAD();
    }

    public async Task<IList<string>> GetAADInformationAsync()
    {
        // then here, use the graphClient object for the request...
        var payload = await _graphClient.Groups.Request().GetAsync();
        return payload
    }
}

I register the above classess in my startup as follows:

services.AddScoped<IAuthorizeGraphApi, AuthorizeGraphApi>();

The idea was that this way, I don't need to call the _graphClient in each method. How can I inject the connection object in a correct way? Or what are the best practices regarding this (injecting connection objects)?

One way would be to store a reference to the Task and make sure any public methods that use the connection are async :

public class AzureIntuneService
{
    private readonly Task<GraphServiceClient> _graphClientTask;
    public AzureIntuneService(IAuthorizeGraphApi client)
    {
        _graphClientTask = client.ConnectToAAD();
    }

    public async Task<IList<string>> GetAADInformationAsync()
    {
        var client = await _graphClientTask; // Get the client when connected
        var payload = await client.Groups.Request().GetAsync();
        return payload;
    }
}

Constructors aren't async and should never be used to initialize anything async . The only way to workaround it is to do sync-over-async by doing a .Result which is always a problem.

In your case, the GraphServiceClient that takes in DelegateAuthenticationProvider , accepts an AuthenticateRequestAsyncDelegate . This allows you to have an async delegate to construct the client.

So now you can do

new DelegateAuthenticationProvider(async requestMessage =>  
    {
        string accessToken = await GetAccessTokenFromAuthorityAsync(); 
        //rest of code here
    } 
)

and this allows you to change your ConnectToAAD signature to just return a GraphServiceClient and not a Task<GraphServiceClient> .

When you need async data you have to look away from the regular constructor and create a factory method (private static function). Something like below:

public sealed class MyClass
{
  private MyData asyncData;
  private MyClass() { ... }

  private async Task<MyClass> InitializeAsync()
  {
    asyncData = await GetDataAsync();
    return this;
  }

  public static Task<MyClass> CreateAsync()
  {
    var ret = new MyClass();
    return ret.InitializeAsync();
  }
}

public static async Task UseMyClassAsync()
{
  MyClass instance = await MyClass.CreateAsync();
  ...
}

More here: https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM