Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions SecurityService.Database/DbContexts/AuthenticationDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,69 @@
using SecurityService.BusinessLogic;
using Microsoft.Data.SqlClient;
using SecurityService.BusinessLogic;

namespace SecurityService.Database.DbContexts
{
using System;
using Duende.IdentityServer.EntityFramework.DbContexts;
using Duende.IdentityServer.EntityFramework.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Shared.General;
using System;
using System.Threading;
using System.Threading.Tasks;

public class AuthenticationDbContext : IdentityDbContext<ApplicationUser>
{
public AuthenticationDbContext(DbContextOptions options)
: base(options)
{
public class AuthenticationDbContext : IdentityDbContext<ApplicationUser> {
public AuthenticationDbContext(DbContextOptions options) : base(options) {
}

protected override void OnModelCreating(ModelBuilder builder)
{
protected override void OnModelCreating(ModelBuilder builder) {
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
public static class Extensions
{

public static async Task SetDbInSimpleMode(this DbContext context, CancellationToken cancellationToken)
{
var dbName = context.Database.GetDbConnection().Database;

var connection = context.Database.GetDbConnection();
if (connection.State != System.Data.ConnectionState.Open)
await connection.OpenAsync(cancellationToken);

// 1. Check current recovery model
await using var checkCommand = connection.CreateCommand();
checkCommand.CommandText = @"
SELECT recovery_model_desc
FROM sys.databases
WHERE name = @dbName;
";
var param = checkCommand.CreateParameter();
param.ParameterName = "@dbName";
param.Value = dbName;
checkCommand.Parameters.Add(param);

var result = await checkCommand.ExecuteScalarAsync(cancellationToken);
var currentRecoveryModel = result?.ToString();

if (currentRecoveryModel != "SIMPLE")
{
// 2. Alter database outside transaction
await using var alterCommand = connection.CreateCommand();
alterCommand.CommandText = $@"
ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [{dbName}] SET RECOVERY SIMPLE;
ALTER DATABASE [{dbName}] SET MULTI_USER;
";
// Execute outside EF transaction
await alterCommand.ExecuteNonQueryAsync(cancellationToken);
}
}
}


}
110 changes: 110 additions & 0 deletions SecurityService/HostedServices/DatabaseInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using Duende.IdentityServer.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using SecurityService.Database.DbContexts;
using Microsoft.Extensions.Logging;
using Shared.Logger;

public class DatabaseInitializer : IHostedService, IDisposable
{
private readonly IServiceProvider Services;
private readonly IHostApplicationLifetime Lifetime;
private readonly CancellationTokenSource InternalCts = new();
private Task? StartupTask;

public DatabaseInitializer(IServiceProvider services, IHostApplicationLifetime lifetime) {
Services = services;
this.Lifetime = lifetime;
}

public Task StartAsync(CancellationToken cancellationToken)
{
// start migrations/seeding on the threadpool so StartAsync returns quickly if you want.
StartupTask = Task.Run(() => RunMigrationsAsync(InternalCts.Token), CancellationToken.None);
return Task.CompletedTask;
}

private async Task RunMigrationsAsync(CancellationToken token)
{
try {

Logger.LogWarning("About to start DatabaseMigrations");

using IServiceScope scope = Services.CreateScope();
PersistedGrantDbContext persistedGrant = scope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>();
ConfigurationDbContext config = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
AuthenticationDbContext auth = scope.ServiceProvider.GetRequiredService<AuthenticationDbContext>();

if (persistedGrant.Database.IsRelational()) {
await persistedGrant.Database.MigrateAsync(token);
await persistedGrant.SetDbInSimpleMode(token);
}

if (config.Database.IsRelational()){
await config.Database.MigrateAsync(token);
await config.SetDbInSimpleMode(token);
}

if (auth.Database.IsRelational()) {
await auth.Database.MigrateAsync(token);
await auth.SetDbInSimpleMode(token);
}

Logger.LogWarning("Database Migrations Successful");
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Logger.LogInformation("DatabaseInitializer migration canceled.");
}
catch (Exception ex)
{
Logger.LogError("DatabaseInitializer migration failed.", ex);
// Request host shutdown:
try
{
Environment.ExitCode = 1;
Lifetime.StopApplication();
}
catch
{
// ignore any exception from StopApplication, we are already failing
}

// Rethrow to ensure host doesn't continue starting successfully.
throw;
}
}

public async Task StopAsync(CancellationToken cancellationToken)
{
// per-service shutdown window
TimeSpan perServiceTimeout = TimeSpan.FromSeconds(20);

// request cancel of internal work
await this.InternalCts.CancelAsync();

if (StartupTask == null)
return;

using var linked = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
Task timeout = Task.Delay(perServiceTimeout, linked.Token);
Task completed = await Task.WhenAny(StartupTask, timeout);

if (completed == timeout)
{
Logger.LogWarning($"DatabaseInitializer did not finish within {perServiceTimeout.TotalSeconds}s; continuing shutdown.");
// host will continue shutdown; hosted service StopAsync returned after per-service timeout.
}
else
{
await linked.CancelAsync(); // cancel the delay
await StartupTask; // propagate exceptions if any
}
}

public void Dispose() => InternalCts.Dispose();
}
58 changes: 32 additions & 26 deletions SecurityService/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using SecurityService.Endpoints;
using System.Threading;
using System.Threading.Tasks;
using SecurityService.Endpoints;

namespace SecurityService
{
Expand Down Expand Up @@ -156,7 +158,7 @@
app.UseSwaggerUI();

// this will do the initial DB population
this.InitializeDatabase(app);
//this.InitializeDatabase(app);

Check notice on line 161 in SecurityService/Startup.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

SecurityService/Startup.cs#L161

Remove this commented out code.
}

/// <summary>
Expand All @@ -172,37 +174,41 @@
services.IncludeRegistry<MediatorRegistry>();
services.IncludeRegistry<MiscRegistry>();

// Register the hosted service via the ServiceRegistry (Lamar)
services.AddHostedService<DatabaseInitializer>();

Startup.Container = new Container(services);
}

/// <summary>
/// Initializes the database.
/// </summary>
/// <param name="app">The application.</param>
private void InitializeDatabase(IApplicationBuilder app)
{
using(IServiceScope serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
PersistedGrantDbContext persistedGrantDbContext = serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>();
ConfigurationDbContext configurationDbContext = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
AuthenticationDbContext authenticationContext = serviceScope.ServiceProvider.GetRequiredService<AuthenticationDbContext>();

if (persistedGrantDbContext != null && persistedGrantDbContext.Database.IsRelational())
{
persistedGrantDbContext.Database.Migrate();
}

if (configurationDbContext != null && configurationDbContext.Database.IsRelational())
{
configurationDbContext.Database.Migrate();
}

if (authenticationContext != null && authenticationContext.Database.IsRelational())
{
authenticationContext.Database.Migrate();
}
}
}
//private void InitializeDatabase(IApplicationBuilder app)
//{

Check notice on line 188 in SecurityService/Startup.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

SecurityService/Startup.cs#L188

Remove this commented out code.
// using(IServiceScope serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
// {
// PersistedGrantDbContext persistedGrantDbContext = serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>();
// ConfigurationDbContext configurationDbContext = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
// AuthenticationDbContext authenticationContext = serviceScope.ServiceProvider.GetRequiredService<AuthenticationDbContext>();

// if (persistedGrantDbContext != null && persistedGrantDbContext.Database.IsRelational())
// {
// persistedGrantDbContext.Database.Migrate();
// //_ = persistedGrantDbContext.SetDbInSimpleMode(CancellationToken.None);
// }

// if (configurationDbContext != null && configurationDbContext.Database.IsRelational())
// {
// configurationDbContext.Database.Migrate();
// }

// if (authenticationContext != null && authenticationContext.Database.IsRelational())
// {
// authenticationContext.Database.Migrate();
// }
// }
//}

#endregion
}
Expand Down
Loading