0

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)?

CMorgan
  • 645
  • 2
  • 11
  • 33

3 Answers3

3

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;
    }
}
Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
0

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>.

JohanP
  • 5,252
  • 2
  • 24
  • 34
0

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

Ziaullah Khan
  • 2,020
  • 17
  • 20