I have a webjob hosted within Azure which is scheduled to run continuous mode type. In this webjob I have some database operations which are managed using EntityFrameworkCore. I am also using userassigned ManagedIdentity to get the token and pass it to the AccessToken property of the connection. The token lifetime of the userassigned ManagedIdentity is 24 hours. For the first time the webjob runs successfully without any issues, but on the subsequent iterations post 24 hours it breaks , since the token by that time has expired.
Program.cs
public static void Main()
{
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddEnvironmentVariables();
IConfiguration configuration = builder.Build();
IHost host = new HostBuilder().ConfigureWebJobs(webJobConfiguration =>
{
webJobConfiguration.AddAzureStorage();
webJobConfiguration.AddAzureStorageCoreServices();
webJobConfiguration.AddTimers();
}).ConfigureServices(serviceCollection =>
{
serviceCollection.AddTransient<ImportFunctions>();
serviceCollection.AddSingleton<IConfiguration>(configuration);
serviceCollection.AddSingleton(new ConfigManager(configuration));
serviceCollection.AddTransient(typeof(IDBAuthTokenService), typeof(AzureSqlAuthTokenService));
serviceCollection.AddDbContext<AppDbContext>(options => options.UseSqlServer(GetConnectionString()));
}).Build();
using (host)
{
host.Run();
}
string GetConnectionString()
{
string connection = configuration["connectionString"];
bool readFromKeyVault = bool.Parse(configuration["ReadFromKeyVault"] ?? "false");
if (readFromKeyVault)
{
connection = GetKeyVaultClient().GetSecretValue("appconnectionstring");
}
return connection;
}
KeyVaultAccessClient GetKeyVaultClient()
{
string keyVaultURL = configuration["KeyVaultURL"];
string userAssignedClientId = configuration["MsiConfiguration:UserAssignedClientId"];
return new KeyVaultAccessClient(keyVaultURL, userAssignedClientId);
}
}
IDBAuthTokenService.cs
public interface IDBAuthTokenService
{
Task<string> GetTokenAsync();
}
AzureSqlAuthTokenService.cs
public class AzureSqlAuthTokenService : IDBAuthTokenService
{
public AzureSqlAuthTokenService()
{
}
public async Task<string> GetTokenAsync()
{
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions{ManagedIdentityClientId = ConfigManager.Get("UserAssignedClientId")});
var tokenRequestContext = new TokenRequestContext(new[]{ConfigManager.Get("AzureSQLResourceId")});
var token = await credential.GetTokenAsync(tokenRequestContext, default);
return token.Token;
}
}
AppDbContext.cs
public partial class AppDbContext : DbContext
{
public AppDbContext()
{
}
public AppDbContext(IDBAuthTokenService tokenService, DbContextOptions<AppDbContext> options) : base(options)
{
var connection = this.Database.GetDbConnection() as SqlConnection;
connection.AccessToken = tokenService.GetTokenAsync().Result;
}
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}
In this implementation I tried to leverage polly nuget package to implement the retry logic as mentioned below:
ImportFunctions.cs
public class ImportFunctions
{
private IEmailEID _emailEID;
private IConfiguration _config;
private readonly IDBAuthTokenService _tokenService;
private AsyncRetryPolicy retryPolicy;
public ImportFunctions(IEmailEID emailEID, IConfiguration config, IDBAuthTokenService tokenService)
{
_emailEID = emailEID;
_config = config;
_tokenService = tokenService;
#region RetryDetails
int MAX_RETRIES = 3;
retryPolicy = Policy.Handle<Exception>(ex => ex.Message.Trim().Length > 0).WaitAndRetryAsync(retryCount: MAX_RETRIES, sleepDurationProvider: (attemptCount) => TimeSpan.FromSeconds(attemptCount * 2), onRetry: (exception, sleepDuration, attemptNumber, context) =>
{
Console.WriteLine($"Exception details: {exception.Message}");
ReInitializeAppDbContext();
});
#endregion
}
public void DailyTrigger([TimerTrigger(typeof(DailyJobSchedule))] TimerInfo timerInfo)
{
try
{
if (!bool.Parse(_config["disableEmails"]))
{
//Code here runs every day
Console.WriteLine("Daily Job triggered at :" + DateTime.Now);
_emailEID.TestMethod1();
_emailEID.TestMethod2();
Console.WriteLine("Daily Job ended at :" + DateTime.Now);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception :" + ex.Message);
}
}
private void ReInitializeAppDbContext()
{
var contextOptions = new DbContextOptionsBuilder<AppDbContext>().UseSqlServer(GetConnectionString()).Options;
using var context = new AppDbContext(_tokenService, contextOptions);
}
}
Any database call that happens after 24 hours fails since the token has expired by that time and in that case I am trying to reintialize the dbcontext with a new token as mentioned in the method : ReInitializeAppDbContext()
But still I see the error : Login failed for user ''. Token is expired for all the executions post 24 hours duration.
Can anyone help me to resolve this issue?