I am using EF Core to connect to a Azure SQL Database deployed to Azure App Services. I am using an access token (obtained via the Managed Identities) to connect to Azure SQ
While the approach is generally correct in the sense that there is no other way than having to write custom code that sets the AccessToken of the connection, there is a couple of issues in your implementation that could be avoided by using a DbConnectionInterceptor as I will describe below. Those two issues are:
.Result to block while waiting for the access token.A better alternative is to use interceptors, which EF Core supports. You will start with a DbContext like this:
public class MyCustomDbContextFactory : IMyCustomDbContextFactory
{
private readonly string _connectionString;
private readonly AzureAuthenticationInterceptor _azureAuthenticationInterceptor;
public MyCustomDbContextFactory(DbContextFactoryOptions options, AzureAuthenticationInterceptor azureAuthenticationInterceptor)
{
_connectionString = options.ConnectionString;
_azureAuthenticationInterceptor = azureAuthenticationInterceptor;
}
public MyCustomDbContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder
.UseSqlServer(_connectionString)
.AddInterceptors(_azureAuthenticationInterceptor);
return new MyCustomDbContext(optionsBuilder.Options);
}
}
And this is the interceptor implementation:
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
private const string AzureDatabaseResourceIdentifier = "https://database.windows.net";
private readonly AzureServiceTokenProvider _azureServiceTokenProvider;
public AzureAuthenticationInterceptor(AzureServiceTokenProvider azureServiceTokenProvider) : base()
{
_azureServiceTokenProvider = azureServiceTokenProvider;
}
public override async Task ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = await GetAccessToken();
}
return result;
}
public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = GetAccessToken().Result;
}
return result;
}
private Task GetAccessToken() => _azureServiceTokenProvider.GetAccessTokenAsync(AzureDatabaseResourceIdentifier);
}
And this is how to configure your services:
services.AddSingleton(new DbContextFactoryOptions(connection_string));
services.AddSingleton(new AzureAuthenticationInterceptor(new AzureServiceTokenProvider()));
And finally, this is how to instantiate DbContext objects in your repository:
public async Task> GetAll()
{
using var context = _notificationsDbContextFactory.Create(); // Injected in ctor
var dbos = await context.MyCustomEntity.ToListAsync();
return ... // something;
}