У меня есть небольшая проблема, которая на данный момент вызывает у меня серьезные проблемы, и я не могу найти никакого решения, обращаясь к документации или аналогичным вопросам, заданным здесь. У меня эта проблема возникает только при использовании EntraApp для аутентификации.
Проблема в том, что токен не обновляется после (или после) истечения его срока действия.
Вот что у меня есть (работает ровно 1 час):
Программа.cs
services.AddDbContext<DataverseDatabaseClient>(options =>
{
options.UseSqlServer($"" +
$"Server = {Environment.GetEnvironmentVariable("DataverseSettings:SqlServer")};" +
$"Database = {Environment.GetEnvironmentVariable("DataverseSettings:SqlDatabase")}");
});
DbContext.cs (с удалением OnModelCreating для визуального улучшения)
using MasterdataAPI.Configurations;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Client;
using MasterdataAPI.Models.DataverseTableModels;
namespace MasterdataAPI.EFCore
{
public class DvDbContext : DbContext
{
private readonly IMemoryCache _cache;
private readonly DataverseSettings _dataverseSettings;
public DbSet<QuoteSqlTableRowModel> Quote { get; set; }
private string Token => GetToken().Result ?? "";
public DvDbContext(DbContextOptions<DvDbContext> options, IMemoryCache cache, IOptions<DataverseSettings> dataverseSettings) : base(options)
{
_cache = cache;
_dataverseSettings = dataverseSettings.Value;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connString = $"" +
$"Server = {_dataverseSettings.SqlServer};" +
$"Database = {_dataverseSettings.SqlDatabase};";
var conn = new SqlConnection(connString);
conn.AccessToken = Token;
optionsBuilder.UseSqlServer(conn, providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
private async Task<string?> GetToken()
{
if (!_cache.TryGetValue("dataverseToken", out string? token))
{
var tokenResult = await Authenticate();
token = tokenResult.AccessToken;
_cache.Set("dataverseToken", tokenResult.AccessToken, tokenResult.ExpiresOn);
}
return token;
}
private async Task<AuthenticationResult> Authenticate()
{
var app = ConfidentialClientApplicationBuilder.Create(_dataverseSettings.ClientId)
.WithAuthority($"{_dataverseSettings.AuthenticationUrl}/{_dataverseSettings.TenantId}")
.WithClientSecret(_dataverseSettings.ClientSecret)
.Build();
var authResult = await app.AcquireTokenForClient(
new[] { $"{_dataverseSettings.Audience}/.default" })
.ExecuteAsync()
.ConfigureAwait(false);
return authResult;
}
}
}
.csproj
<Project Sdk = "Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove = "Models\D365FinOpsModels\**" />
<EmbeddedResource Remove = "Models\D365FinOpsModels\**" />
<None Remove = "Models\D365FinOpsModels\**" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include = "Microsoft.AspNetCore.App" />
<PackageReference Include = "EntityFramework" Version = "6.5.0" />
<PackageReference Include = "Microsoft.Azure.Functions.Worker" Version = "1.22.0" />
<PackageReference Include = "Microsoft.Azure.Functions.Worker.Extensions.Http" Version = "3.2.0" />
<PackageReference Include = "Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version = "1.3.1" />
<PackageReference Include = "Microsoft.Azure.Functions.Worker.Sdk" Version = "1.17.2" />
<PackageReference Include = "Microsoft.ApplicationInsights.WorkerService" Version = "2.22.0" />
<PackageReference Include = "Microsoft.Azure.Functions.Worker.ApplicationInsights" Version = "1.2.0" />
<PackageReference Include = "Microsoft.Data.SqlClient" Version = "5.2.1" />
<PackageReference Include = "Microsoft.EntityFrameworkCore" Version = "8.0.6" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.SqlServer" Version = "8.0.6" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.Tools" Version = "8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include = "Microsoft.Identity.Client" Version = "4.61.3" />
<PackageReference Include = "Newtonsoft.Json" Version = "13.0.3" />
</ItemGroup>
<ItemGroup>
<None Update = "host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update = "local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include = "System.Threading.ExecutionContext" Alias = "ExecutionContext" />
</ItemGroup>
</Project>
Я экспериментировал со строками подключения, найденными здесь, и особенно с этим: «Аутентификация = Принципал службы Active Directory». Это не позволяет мне установить свойство .Token для SqlConnection() (очевидно) и выдает ошибку, сообщающую, что он не может найти мои полномочия, и я также не могу найти способ отправить свой tenantId в этих документах.
Итак, с моей точки зрения, у меня есть два варианта, ни один из которых я не знаю, как действовать дальше:
Любая помощь или рефакторинг будут ОЧЕНЬ оценены.
Спасибо и не стесняйтесь редактировать вопрос, где это применимо.
@Харшита Готово!
Краткий ответ: получите токен в OnConfiguring.
В крайнем случае, когда ваш DbContext живет дольше, чем срок действия токена, откройте SqlConnection перед созданием DbContext, и он останется открытым в течение всего времени существования DbContext.
Вы имеете в виду непосредственно в методе inConfiguring(), а не через Token => GetToken в разделе свойств класса? Я попробую это! Спасибо!
Да, это особо не помогло. Срок действия соединения по-прежнему истекает с ошибкой «Microsoft.Data.SqlClient.SqlException (0x80131904): Ошибка входа в систему: срок действия соединения истек, повторно подключите окно запроса и повторите попытку».
Ответ на мою проблему на самом деле может быть связан с этой частью Program.cs, которая пролетела прямо у меня под носом:
services.AddSingleton<DataverseService>();
Я предполагаю, что синглтонная часть заставила мое приложение повторно использовать просроченный экземпляр контекста.
Изменение в AddScoped, по-видимому, решило проблему, поскольку токен извлекается из кеша для каждого запроса, если только срок его действия не истек, и вместо этого он будет обновлен.
services.AddScoped<DataverseService>();
Пожалуйста, поделитесь своим
.csproj
файлом.