Esempio per leggere le configurazioni AppSettings da un database
Questo esempio mostra come creare un extension method AddDatabaseConfiguration, per .NET 10, che crea un Custom Configuration Provider per legge i valori di configurazione da una tabella SQL Server.
Creazione Tabella
Per prima cosa creare una tabella AppSettings per salvare le configurazione:
T-SQL: AppSettings
CREATE TABLE AppSettings (
[Key] NVARCHAR(255) NOT NULL PRIMARY KEY,
[Value] NVARCHAR(MAX) NULL,
[IsEncrypted] BIT NOT NULL DEFAULT 0
)
Esempio tabellache corrisponde ad un appsettings.json come questo:
JSON: appsettings.json
{
"AppOptions": {
"MsSql": {
"ConnectionString": "Server=localhost;Database=TestDB;Integrated Security=true;TrustServerCertificate=True;"
},
/* i seguenti valori sono letti da DB */
"Enabled": true,
"SecretPassword": "UIN/qj2RJvZSxEfrgpFbbwVKPo4z74kBXX+2O4bNK8/FFhed8xw/asC4/sfRYU50"
},
...
}
Classi necessarie
Per gestire questa tabella si parte creando un ConfigurationProvider che fa l'override del metodo Load():
C#: MsSqlDatabaseConfigurationProvider
using Microsoft.Data.SqlClient;
public class MsSqlDatabaseConfigurationProvider(string connectionString) : ConfigurationProvider
{
public override void Load()
{
Dictionary<string, string?> data = new(StringComparer.OrdinalIgnoreCase);
using SqlConnection cnn = new(connectionString);
cnn.Open();
using SqlCommand cmd = new("SELECT [Key], [Value], [IsEncrypted] FROM AppSettings", cnn);
using SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
string key = dr.GetString(0);
string? value = dr.IsDBNull(1) ? null : dr.GetString(1);
bool isEncrypted = dr.IsDBNull(2) ? false : dr.GetBoolean(2);
if (isEncrypted && value != null)
{
// Decrypt the value if it is encrypted
string decryptedValue = CryptoHelper.Decrypt(value.Trim());
data[key] = decryptedValue;
}
else
{
data[key] = value;
}
}
Data = data;
}
}
Da notare che vengono gestiti anche valori stringa cryptati tramite il flag IsEncrypted e il metodo CryptoHelper.Decrypt(value.Trim()).
In seguito va creato un IConfigurationSource che usa il provider precedente:
C#: MsSqlDatabaseConfigurationSource
public class MsSqlDatabaseConfigurationSource(string connectionString) : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new MsSqlDatabaseConfigurationProvider(connectionString);
}
in ultimo l'extension method da usare in Program.cs:
C#: MsSqlDatabaseConfigurationExtension
public static class MsSqlDatabaseConfigurationExtension
{
/// <summary>
/// Adds a configuration provider that reads configuration values from a SQL Server database.
/// </summary>
/// <param name="builder"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IConfigurationBuilder AddDatabaseConfiguration(this IConfigurationBuilder builder, string connectionString)
=> builder.Add(new MsSqlDatabaseConfigurationSource(connectionString));
public static void CreateTableIfNotExists(string connectionString)
{
using SqlConnection cnn = new(connectionString);
cnn.Open();
using SqlCommand cmd = new("""
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='AppSettings' AND xtype='U')
CREATE TABLE AppSettings (
[Key] NVARCHAR(255) NOT NULL PRIMARY KEY,
[Value] NVARCHAR(MAX) NULL,
[IsEncrypted] BIT NOT NULL DEFAULT 0
)
""", cnn);
cmd.ExecuteNonQuery();
}
public static string EncryptValue(string value) => CryptoHelper.Encrypt(value);
}
Utilizzo
In una WebApplication, l'extension method AddDatabaseConfiguration va inserito subito dopo CreateBuilder() in Program.cs:
C#: AddDatabaseConfiguration
using WebApplication1.Extensions;
var builder = WebApplication.CreateBuilder(args);
// gestisco la configurazione su DB
string connectionString = builder.Configuration.GetValue<string>("AppOptions:MsSql:ConnectionString")
?? throw new InvalidOperationException("ConnectionString nella sezione AppOptions:MsSql mancante.");
builder.Configuration.AddDatabaseConfiguration(connectionString);
// se serve mappo la configurazione su una classe AppOptions per avere una configurazione tipizzata
AppOptions options = builder.Configuration.GetSection(AppOptions.SECTION_NAME).Get<AppOptions>()
?? throw new InvalidOperationException("Sezione AppSettings mancante.");
Console.WriteLine($"Pwd: {options.SecretPassword}");
...
ATTENZIONE: i valori letti da DatabaseSOVRASCRIVONO quelli dei provider di configurazione precedenti, come il file appsetting.json, variabili di ambiente o user secret.
Esempio di classe AppOptions:
C#: AppOptions
public class AppOptions
{
public static string SECTION_NAME = "AppOptions";
public bool Enabled { get; set; }
public string SecretPassword { get; set; } = string.Empty;
public MsSqlOptions? MsSql { get; set; }
}
public class MsSqlOptions
{
public string? ConnectionString { get; set; }
}
Creazione stringa criptata
La stringa criptata può essere generata da un testo in chiaro chiamando l'eseguibile della WebApplication in questo modo:
C#:
.\WebApplication1.exe encrypt "prova aaa"
che darà un output simile a questo
Text
STARTING COMMAND 'encrypt' with parameter 'prova aaa'...
Encrypted: 'UIN/qj2RJvZSxEfrgpFbbwVKPo4z74kBXX+2O4bNK8/FFhed8xw/asC4/sfRYU50'
STOPPING
questo comportamento lo si ottiene modificare leggermente il Program.cs:
C#
var builder = WebApplication.CreateBuilder(args);
if (args.Length > 1)
{
string cmd = args[0].ToLowerInvariant();
string? param = args[1];
Console.WriteLine($"STARTING COMMAND '{cmd}' with parameter '{param}'...");
if (cmd == "encrypt")
{
// solo per gestire la generazione della password chiamando l'exe
// WebApplicaion1.exe encrypt "testo da criptare"
Console.WriteLine($" Encrypted: '{MsSqlDatabaseConfigurationExtension.EncryptValue(param!)}'");
}
else if (cmd == "create" && param.Equals("table", StringComparison.OrdinalIgnoreCase))
{
// creazione della tabella AppSettings se non esiste, chiamando l'exe
// WebApplicaion1.exe create table
Console.WriteLine(" CREATING TABLE...");
MsSqlDatabaseConfigurationExtension.CreateTableIfNotExists(builder.Configuration.GetValue<string>("AppOptions:MsSql:ConnectionString")!);
}
else
{
Console.WriteLine($" Parameter '{cmd}' is not recognized\r\n");
}
Console.WriteLine("STOPPING");
return;
}
// il resto della configurazione delle WebApplication
Si può anche creare la tabella AppSettings con il comando:
PowerShell
.\WebApplication1.exe create table
Miglioramenti
Se serve, e se l'applicantion pool della WebApplication ha i permessi di creazione tabella, si può aggiungere un extension method per verificare l'esistenza della tabella e nel caso crearla.
La classe MsSqlDatabaseConfigurationExtension, con l'aggiunta del metodo EnsureAndAddDatabaseConfiguration, diventa:
C#: MsSqlDatabaseConfigurationExtension
public static class MsSqlDatabaseConfigurationExtension
{
/// <summary>
/// Ensures that the AppSettings table exists in the database and
/// adds a configuration provider that reads configuration values from a SQL Server database.
/// </summary>
/// <param name="builder"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IConfigurationBuilder EnsureAndAddDatabaseConfiguration(this IConfigurationBuilder builder, string connectionString)
{
CreateTableIfNotExists(connectionString);
return builder.Add(new MsSqlDatabaseConfigurationSource(connectionString));
}
// contenute esistente ...
}
// gestisco la configurazione su DB
string connectionString = builder.Configuration.GetValue<string>("AppOptions:MsSql:ConnectionString")
?? throw new InvalidOperationException("ConnectionString nella sezione AppOptions:MsSql mancante.");
builder.Configuration.EnsureAndAddDatabaseConfiguration(connectionString);