Есть проблема Github по этому поводу , так что это определенно неясно. Проблема закрыта, потому что в настоящее время нет встроенной поддержки, другая проблема отслеживает эту .
Оригинальная проблема описывает умный обходной путь, хотя. Прежде всего, UseSqlBuilder
имеет перегрузку, которая принимает существующее соединение DbConnection. Это соединение можно настроить с помощью токена AAD. Если он закрыт, EF откроет и закроет его при необходимости. Можно написать:
services.AddDbContext<MyDBContext>(options => {
SqlConnection conn = new SqlConnection(Configuration["ConnectionString"]);
conn.AccessToken = (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/")
.Result;
options.UseSqlServer(conn);
});
Самое сложное - как избавиться от этого соединения.
Умное решение, опубликованное Брайаном Боллом, заключается в реализации интерфейса в DbContext и регистрации , что , в качестве службы, используемой контроллерами с заводской функцией. DbContext все еще регистрируется, используя его конкретный тип. Функция фабрики получает этот контекст и устанавливает токен AAD для его соединения:
services.AddDbContext<MyDbContext>(builder => builder.UseSqlServer(connectionString));
services.AddScoped<IMyDbContext>(serviceProvider => {
//Get the configured context
var dbContext = serviceProvider.GetRequiredService<MyDbContext>();
//And set the AAD token to its connection
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
if(connection == null) {/*either return dbContext or throw exception, depending on your requirements*/}
connection.AccessToken = //code used to acquire an access token;
return dbContext;
});
Таким образом, время жизни контекста все еще управляется EF Core. AddScoped<IMyDbContext>
действует как фильтр, который принимает этот контекст и устанавливает токен AAD
Следующая проблема - как записать это //code used to acquire an access token;
, чтобы оно не блокировалось.
Это не такая большая проблема, потому что, согласно документам :
Класс AzureServiceTokenProvider кэширует токен в памяти и извлекает его из Azure AD непосредственно перед истечением срока действия.
Этот код может быть извлечен в метод фабрики и даже вставлен как зависимость.
Перемещение сообщений ворот
Основная проблема заключается в том, что конструкторы не могут быть асинхронными , пока , поэтому инжектор конструктора не может извлекать токены асинхронно.
Что можно сделать, это зарегистрировать асинхронную фабрику или службу Func<>
, которая вызывается в асинхронных действиях контроллера вместо конструктора. Допустим,
//Let's inject configuration too
//Defaults stolen from AzureServiceTokenProvider's source
public class TokenConfig
{
public string ConnectionString {get;set;};
public string AzureAdInstance {get;set;} = "https://login.microsoftonline.com/";
public string TennantId{get;set;}
public string Resource {get;set;}
}
class DbContextWithAddProvider
{
readonly AzureServiceTokenProvider _provider;
readonly TokenConfig _config;
readonly IServiceProvider _svcProvider;
public DbContextWithAddProvider(IServiceProvider svcProvider, IOption<TokenConfig> config)
{
_config=config;
_provider=new AzureServiceTokenProvider(config.ConnectionString,config.AzureAdInstance);
_svcProvider=svcProvider;
}
public async Task<T> GetContextAsync<T>() where T:DbContext
{
var token=await _provider.GetAccessTokenAsync(_config.Resource,_config.TennantId);
var dbContext = _svcProvider.GetRequiredService<T>();
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
connection.AccessToken = token;
return dbContext;
}
}
Этот сервис должен быть зарегистрирован как одноэлементный, поскольку он не сохраняет никаких состояний, кроме кэшированного токена, который мы делаем хотим сохранить.
Теперь его можно внедрить в конструктор и вызвать в асинхронном действии:
class MyController:Controller
{
DbContextWithAddProvider _ctxProvider;
public MyController(DbContextWithAddProvider ctxProvider)
{
_ctxProvider=ctxProvider;
}
public async Task<IActionResult> Get()
{
var dbCtx=await _ctxProvider.GetContextAsync<MyDbContext>();
...
}
}