Oltre appsettings.json — Guida pratica alla gestione della configurazione nelle applicazioni aziendali Windows (impostazioni per ambiente, segreti e percorsi di scrittura)
· Go Komura · CSharp, .NET, appsettings.json, IConfiguration, IOptions, Generic Host, Gestione della configurazione, Sviluppo Windows, Consulenza tecnica
«Voglio una stringa di connessione diversa per ambiente». «Voglio salvare le preferenze di visualizzazione di ogni utente». «Abbiamo finito per scrivere l’API key direttamente in appsettings.json». Nella gestione della configurazione di un’applicazione aziendale Windows, prima o poi tutti arrivano a inserire un appsettings.json e leggerlo tramite IConfiguration. Ma oltre quel punto — impostazioni per ambiente, posizione dei dati scrivibili, segreti e modifiche a runtime — c’è un’area che sorprendentemente spesso arriva in produzione senza essere stata realmente ordinata.
Questo articolo affronta la base IConfiguration e la stratificazione dei provider, le impostazioni per ambiente, la ricezione tipizzata tramite il pattern IOptions, la posizione delle impostazioni modificabili, i segreti, i limiti reali della modifica a runtime e infine una mappa di migrazione da app.config/Settings.settings, nell’ordine in cui queste decisioni creano difficoltà nella pratica.
1. La conclusione prima di tutto
Le decisioni di gestione della configurazione iniziano scegliendo una “posizione” per ogni “tipo di impostazione”. Ecco prima una tabella generale.
| Tipo di impostazione | Esempio | Prima scelta | Motivo |
|---|---|---|---|
| Valori predefiniti dell’applicazione | Livello di log predefinito, parametri UI | appsettings.json |
Distribuiti con l’output della build; valori comuni non dipendenti dall’ambiente |
| Impostazioni per ambiente | Stringhe di connessione e endpoint API diversi tra staging e produzione | appsettings.{Environment}.json + variabili d’ambiente |
Consente di usare direttamente l’ordine di override predefinito |
| Impostazioni per utente | Ultima cartella aperta, posizione della finestra, preferenze personali | File personalizzato sotto %LOCALAPPDATA% (o %APPDATA%) |
Serve una posizione in cui ciascun utente possa scrivere separatamente |
| Impostazioni per computer, condivise da tutti | Numero di porta COM di un dispositivo, indirizzo del server licenze | File personalizzato sotto %ProgramData% |
Impostato una volta dall’amministratore e condiviso tra utenti |
| Segreti | Password di stringhe di connessione, API key, token | File protetto da DPAPI (produzione), user-secrets (solo sviluppo) | Mai testo in chiaro; non riutilizzare in produzione un meccanismo solo di sviluppo |
| Valori che cambiano a runtime | Feature flag, variazione dinamica del livello di log | appsettings.json (reloadOnChange) + IOptionsMonitor |
Riservarlo ai soli valori che si vuole davvero applicare senza riavvio |
Con questa tabella come base, ecco i punti essenziali.
- La precedenza della configurazione segue una sola regola: vince il provider aggiunto più tardi. Per impostazione predefinita i provider vengono caricati nell’ordine
appsettings.json→appsettings.{Environment}.json→ user secrets (solo sviluppo) → variabili d’ambiente → argomenti della riga di comando; quando la stessa chiave appare in più fonti, il valore letto dopo sovrascrive quello precedente. Ricordare quest’ordine spiega la maggior parte dei casi «l’ho scritto nel JSON ma non ha effetto». 1 - Usando
Host.CreateApplicationBuilder, l’intera stratificazione è predisposta per impostazione predefinita: non occorre costruirla a mano. Anche app desktop WinForms/WPF ottengono gli stessi vantaggi adottando Generic Host. 23 - Cambiare ambiente con
DOTNET_ENVIRONMENT(oASPNETCORE_ENVIRONMENT). Nelle app basate suWebApplication,DOTNET_ENVIRONMENTha priorità e, se non è impostata nessuna delle due, il valore predefinito èProduction. Per applicazioni desktop e servizi Windows bisogna progettare in anticipo chi passa questa variabile al processo e come. 4 - Non ricevere le impostazioni come semplice DTO: riceverle in forma tipizzata tramite la famiglia
IOptions<T>. UsareIOptions<T>se basta calcolare il valore una volta all’avvio eIOptionsMonitor<T>se le modifiche devono essere riflesse senza riavvio. Usarli senza comprenderne la differenza porta a entrambi gli incidenti: valori che cambiano quando non dovrebbero e valori che non cambiano quando dovrebbero. 5 - Far fallire una configurazione errata all’avvio, non a runtime. Combinando
ValidateDataAnnotations()eValidateOnStart(), un errore di configurazione diventa un’eccezione immediata all’avvio anziché unNullReferenceExceptionalle due di notte in produzione. 6 - Non mettere mai segreti in chiaro in appsettings.json. In sviluppo usare user-secrets e in produzione DPAPI (
ProtectedData) o Credential Manager. La documentazione ufficiale dichiara esplicitamente che user-secrets non è cifrato ed è un meccanismo per il solo sviluppo. 78
2. Le fondamenta della configurazione .NET: IConfiguration e provider
Il sistema di configurazione .NET si basa sul caricamento stratificato di più configuration provider dietro l’aspetto di un singolo archivio chiave-valore chiamato IConfiguration. Il vantaggio è unificare fonti con caratteristiche molto diverse — JSON, variabili d’ambiente, argomenti da riga di comando, INI, XML, raccolte in memoria e altro — attraverso la stessa interfaccia IConfiguration. 9
I provider si sovrappongono secondo una semplice regola: vince quello aggiunto più tardi. Quando una chiave esiste in più provider, è effettivo il valore dell’ultimo. 1
Host.CreateApplicationBuilder(args) predispone i provider nel seguente ordine (i numeri più alti hanno maggiore priorità, cioè sono aggiunti dopo). 2
appsettings.jsonappsettings.{Environment}.json- Secret Manager (solo ambiente di sviluppo)
- Variabili d’ambiente
- Argomenti della riga di comando
Questo ordine predefinito è lo stesso per console app, Worker Service e Windows Forms. Ecco un esempio minimo.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Per impostazione predefinita, appsettings.json / appsettings.{Environment}.json / variabili d'ambiente /
// argomenti della riga di comando sono già caricati nel seguente ordine di priorità
string? connectionString = builder.Configuration.GetConnectionString("Main");
using IHost host = builder.Build();
await host.RunAsync();
Anche un’app desktop Windows Forms può usare lo stesso HostApplicationBuilder aggiungendo il pacchetto Microsoft.Extensions.Hosting. Come spiegato nell’articolo “Che cos’è Generic Host?”, Generic Host gestisce insieme configurazione, DI e logging; un’app desktop non deve rinunciare ai vantaggi del sistema di configurazione. Per modelli concreti in app con elaborazione in background, vedere anche “Generic Host + BackgroundService in un’app desktop”.
Le variabili d’ambiente hanno una sottigliezza. Le host settings — content root, nome dell’ambiente e così via — sono lette da variabili con prefisso DOTNET_, ma tale prefisso non viene usato per le app settings, cioè i normali valori letti tramite IConfiguration. Le variabili da leggere come app settings entrano nell’ordine predefinito senza prefisso tramite AddEnvironmentVariables(). 10 Se si desidera un prefisso personalizzato, aggiungerlo esplicitamente, ad esempio builder.Configuration.AddEnvironmentVariables(prefix: "MyApp_").
using Microsoft.Extensions.Configuration;
// Poiché viene aggiunto dopo i provider predefiniti, ha la priorità più alta
builder.Configuration.AddEnvironmentVariables(prefix: "MyApp_");
3. Impostazioni per ambiente: appsettings.{Environment}.json
Quando stringhe di connessione o endpoint variano per ambiente, usare file come appsettings.Development.json, appsettings.Staging.json e appsettings.Production.json. Il punto importante è che questi file sono trattati come diff che sovrascrivono il appsettings.json di base. Non è necessario riscrivere ogni valore: bastano quelli che cambiano per ambiente. 1
Il nome dell’ambiente è determinato dalla variabile DOTNET_ENVIRONMENT o ASPNETCORE_ENVIRONMENT. Con WebApplication, DOTNET_ENVIRONMENT ha priorità su ASPNETCORE_ENVIRONMENT e, se non è impostata nessuna delle due, il valore predefinito è Production. I nomi delle variabili d’ambiente in Windows non sono sensibili alle maiuscole, mentre in Linux lo sono; se è prevista la containerizzazione, è più sicuro mantenere una capitalizzazione coerente. 4
La difficoltà è che per app desktop e servizi Windows, «come passare la variabile d’ambiente» diventa un lavoro a sé. Meccanismi di sviluppo come launchSettings.json di ASP.NET Core sono usati solo localmente, quindi in produzione occorre un modo separato per cambiare ambiente. I tre approcci principali sono questi.
- Impostarla come variabile per l’intera macchina. Persistendola, ad esempio, con
setx DOTNET_ENVIRONMENT Production /M, viene ereditata da ogni processo in esecuzione sul computer. Non è adatta quando sulla stessa macchina devono convivere applicazioni di ambienti diversi. - Passarla al processo di avvio del servizio Windows. Un servizio residente viene avviato in un contesto diverso dalla sessione dell’utente interattivo, quindi le variabili con scope utente non vengono ereditate. Per account di esecuzione e separazione delle sessioni, vedere “Come creare e gestire servizi Windows”. Sono configurazioni pratiche una variabile di macchina oppure un sottile batch wrapper che esegue
SETprima di avviare il binario reale. - Con Task Scheduler è più affidabile passare un argomento della riga di comando. La scheda Action non offre una UI per impostare direttamente variabili d’ambiente; passare
--environment Productione leggerlo conAddCommandLine(args)riduce gli errori. Le particolarità dell’account di esecuzione e del tipo di accesso di Task Scheduler sono trattate in “Attività dell’Utilità di pianificazione che non partono o terminano con 0x1”.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
var options = new HostApplicationBuilderSettings
{
Args = args,
// Per i percorsi di esecuzione in cui non vengono passate variabili d'ambiente (ad esempio l'Utilità di pianificazione),
// consentire anche il cambio di ambiente dagli argomenti della riga di comando
};
HostApplicationBuilder builder = Host.CreateApplicationBuilder(options);
Console.WriteLine($"Ambiente corrente: {builder.Environment.EnvironmentName}");
4. Il pattern Options: ricevere le impostazioni come tipi
Leggere la configurazione con chiavi stringa come IConfiguration["Key:SubKey"] ha una debolezza: un refuso non viene rilevato e, quando l’annidamento è profondo, diventa difficile seguire il flusso. In pratica l’approccio base è l’options pattern: associare le impostazioni a un POCO e riceverle come tipo.
public sealed class ExternalApiOptions
{
public const string SectionName = "ExternalApi";
public required string BaseUrl { get; set; }
public required string ApiKey { get; set; }
public int TimeoutSeconds { get; set; } = 30;
}
Registrazione e binding sono i seguenti.
using Microsoft.Extensions.DependencyInjection;
builder.Services
.AddOptions<ExternalApiOptions>()
.Bind(builder.Configuration.GetSection(ExternalApiOptions.SectionName));
Esistono tre interfacce per ricevere questi valori, ciascuna con caratteristiche diverse. 5
| Interfaccia | Lifetime di registrazione | Riflette le modifiche di configurazione | Uso principale |
|---|---|---|---|
IOptions<T> |
Singleton | Non le riflette: calcolato una volta all’avvio | Impostazioni che non dovrebbero cambiare; opzione più semplice |
IOptionsSnapshot<T> |
Scoped | Ricalcolato a ogni ricostruzione dello scope | Contesti con uno scope chiaro, come una richiesta web |
IOptionsMonitor<T> |
Singleton | Ottiene sempre il valore più recente e fornisce notifiche (OnChange) |
Contesti come servizi residenti in cui il cambiamento deve essere rilevato subito |
Nelle app senza HTTP request scope — applicazioni desktop e servizi Windows — IOptionsSnapshot<T> si comporta di fatto come IOptions<T> finché non si crea esplicitamente lo scope. Se si desidera rilevare i cambiamenti, usare semplicemente IOptionsMonitor<T>: questa regola pratica elimina gran parte dell’incertezza.
I valori delle impostazioni — BaseUrl, ApiKey e timeout — vanno recuperati da IOptionsMonitor a ogni chiamata, ma l’HttpClient stesso va ottenuto da IHttpClientFactory e riutilizzato. Creare e disporre un HttpClient a ogni chiamata distrugge e ricrea ogni volta il pool interno di socket e connessioni, provocando esaurimento delle porte effimere con polling frequente o elaborazioni batch. La pratica standard è quindi: i valori possono cambiare, ma il riutilizzo delle connessioni resta a IHttpClientFactory.
using Microsoft.Extensions.Options;
public sealed class ExternalApiClient(
IHttpClientFactory httpClientFactory,
IOptionsMonitor<ExternalApiOptions> optionsMonitor)
{
public async Task<string> FetchAsync(CancellationToken cancellationToken)
{
// Recupera il valore più recente a ogni chiamata. Viene applicato se il file di configurazione è stato aggiornato
ExternalApiOptions current = optionsMonitor.CurrentValue;
// Ottenere l'HttpClient tramite la factory e riutilizzarne il pool di connessioni interno
HttpClient client = httpClientFactory.CreateClient(nameof(ExternalApiClient));
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(new Uri(current.BaseUrl), "status"));
request.Headers.Add("X-Api-Key", current.ApiKey);
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(current.TimeoutSeconds));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
using HttpResponseMessage response = await client.SendAsync(request, linkedCts.Token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken);
}
}
Sul lato chiamante registrare un client nominato, ad esempio builder.Services.AddHttpClient(nameof(ExternalApiClient));. I valori modificabili tramite configurazione, come BaseUrl e ApiKey, vengono aggiunti a HttpRequestMessage a ogni richiesta, mentre il costo di creare e disporre l’istanza HttpClient è lasciato alla factory: questa è la divisione delle responsabilità.
Quando un errore di configurazione si manifesta come eccezione a runtime, l’indagine si allunga. Combinando la validazione DataAnnotations con ValidateOnStart() è possibile progettare l’app in modo che una configurazione errata impedisca del tutto l’avvio. 6
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
public sealed class ExternalApiOptions
{
public const string SectionName = "ExternalApi";
[Required, Url]
public required string BaseUrl { get; set; }
[Required, MinLength(16)]
public required string ApiKey { get; set; }
[Range(1, 300)]
public int TimeoutSeconds { get; set; } = 30;
}
builder.Services
.AddOptions<ExternalApiOptions>()
.Bind(builder.Configuration.GetSection(ExternalApiOptions.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart(); // La convalida viene eseguita all'avvio dell'host (StartAsync/RunAsync), prima dell'avvio dei servizi ospitati
Senza ValidateOnStart(), la validazione viene rinviata al primo accesso effettivo all’option. La validazione stessa viene eseguita all’avvio dell’host (StartAsync/RunAsync), non subito dopo Build(); fare quindi attenzione ai test che chiamano solo Build() e mai RunAsync(), perché a quel punto la validazione non è ancora avvenuta. Per evitare il comportamento intermittente «l’app parte ma va in errore appena si apre la schermata che usa quell’impostazione», rendere ValidateOnStart() una regola per la configurazione delle app aziendali. 6
5. Dove collocare le impostazioni che l’app deve poter scrivere
appsettings.json è un posto per “valori predefiniti in sola lettura”; non è il posto per impostazioni che l’applicazione riscrive autonomamente. Molte app aziendali vengono installate in Program Files, dove gli utenti standard non hanno permessi di scrittura; ne deriva un’eccezione a runtime oppure la virtualizzazione del file system Windows fa divergere il contenuto visibile tra utenti.
Dividere le impostazioni scrivibili in base alla loro natura. È più sicuro ancorarle alle special folder ottenibili con Environment.GetFolderPath. 11
| Posizione | Come ottenerla | Uso |
|---|---|---|
%LOCALAPPDATA%\CompanyName\AppName |
Environment.SpecialFolder.LocalApplicationData |
Impostazione predefinita per dati e impostazioni per utente |
%APPDATA%\CompanyName\AppName (Roaming) |
Environment.SpecialFolder.ApplicationData |
Solo per impostazioni che devono seguire l’utente in un ambiente di roaming profile |
%ProgramData%\CompanyName\AppName |
Environment.SpecialFolder.CommonApplicationData |
Impostazioni per macchina condivise da tutti gli utenti; richiede progettazione degli ACL |
using System;
using System.IO;
using System.Text.Json;
public sealed class UserSettingsStore
{
private readonly string _filePath;
public UserSettingsStore()
{
string root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string dir = Path.Combine(root, "KomuraSoft", "MyApp");
Directory.CreateDirectory(dir);
_filePath = Path.Combine(dir, "user-settings.json");
}
public UserSettings Load()
{
if (!File.Exists(_filePath))
{
return new UserSettings();
}
string json = File.ReadAllText(_filePath);
return JsonSerializer.Deserialize<UserSettings>(json) ?? new UserSettings();
}
public void Save(UserSettings settings)
{
string json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
// Le scritture multiple nello stesso processo devono essere serializzate dal chiamante
File.WriteAllText(_filePath, json);
}
}
public sealed class UserSettings
{
public string? LastOpenedFolder { get; set; }
public int WindowWidth { get; set; } = 1024;
public int WindowHeight { get; set; } = 768;
}
Mescolare impostazioni per utente e per macchina in un solo file provoca incidenti come “l’impostazione dell’utente A crea un problema all’utente B” nei computer condivisi. Per la separazione per utente/per macchina e la struttura dei profili Windows, vedere “Come funzionano i profili utente Windows”; per una tabella generale di scelta della posizione, “Come scegliere dove archiviare i dati di un’app Windows”.
6. Segreti: stringhe di connessione e API key
Scrivere direttamente in appsettings.json la password di una stringa di connessione o un’API key è un rischio concreto anche se il file è in .gitignore. Il testo in chiaro può fuoriuscire distribuendo il file in una cartella condivisa, facendoselo inviare durante una richiesta di supporto o lasciando in un backup un vecchio file di impostazioni dimenticato.
In sviluppo, usare Secret Manager (dotnet user-secrets). È però un meccanismo pensato per l’esperienza di sviluppo e non è cifrato. I valori sono salvati come JSON in chiaro in %APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json, e la documentazione ufficiale dichiara esplicitamente che non va trattato come trusted store ed è solo per lo sviluppo. Mettere segreti di produzione in user-secrets e distribuirli è un errore. 7
dotnet user-secrets init
dotnet user-secrets set "ExternalApi:ApiKey" "valore-di-sviluppo"
In produzione, usare DPAPI (Data Protection API) fornita da Windows, che cifra il valore legandolo alle credenziali dell’utente o della macchina. Protect/Unprotect di System.Security.Cryptography.ProtectedData sono metodi wrapper; DataProtectionScope.CurrentUser significa che il valore può essere decifrato solo sulla stessa macchina e con lo stesso account. Progettare ricordando che DPAPI è disponibile solo in Windows e su altre piattaforme genera PlatformNotSupportedException. 8
using System.Security.Cryptography;
using System.Text;
public static class SecretProtector
{
// Aggiungere entropia che indichi lo scopo riduce il rischio di confonderla con dati protetti per altri scopi
private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("MyApp.ExternalApi.ApiKey");
public static string Protect(string plainText)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] protectedBytes = ProtectedData.Protect(plainBytes, Entropy, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(protectedBytes);
}
public static string Unprotect(string protectedBase64)
{
byte[] protectedBytes = Convert.FromBase64String(protectedBase64);
byte[] plainBytes = ProtectedData.Unprotect(protectedBytes, Entropy, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plainBytes);
}
}
I dettagli di implementazione — dove conservare il valore dopo la protezione DPAPI, quale scope scegliere tra CurrentUser e LocalMachine, e le insidie nelle app aziendali condivise da più utenti — sono approfonditi in “Conservare dati sensibili nelle app Windows: evitare impostazioni in chiaro con DPAPI”; consultarlo al momento dell’implementazione.
Il confine tra ciò che può restare in chiaro e ciò che non può è semplice. Valutare se la perdita del valore richiederebbe la rigenerazione di password o API key, l’indagine di accessi non autorizzati o una segnalazione a un’autorità di controllo. Il nome host o il numero di porta di una stringa di connessione di solito causano poco danno reale se divulgati; la password o il token di autenticazione inclusi sono invece sempre da proteggere. Costruendo una stringa di connessione separando “informazioni del server” e “credenziali” e applicando DPAPI solo alle seconde, si separa a livello di codice la parte relativamente innocua dalla parte da proteggere.
7. Modificare la configurazione a runtime: la realtà di reloadOnChange
La chiamata predefinita a AddJsonFile — quella eseguita internamente da Host.CreateApplicationBuilder — imposta reloadOnChange: true, quindi appsettings.json e appsettings.{Environment}.json vengono ricaricati automaticamente al rilevamento di una modifica. Internamente PhysicalFileProvider osserva le modifiche tramite FileSystemWatcher. 12
Il meccanismo presenta alcuni limiti pratici.
- Si aggiornano solo i valori letti tramite
IOptionsMonitor<T>(eIOptionsSnapshot<T>).IOptions<T>continua a mantenere il valore dell’avvio e resta quindi obsoleto anche dopo la riscrittura del file. La maggior parte dei casi «ho modificato direttamente il file ma non ha effetto» deriva dal confondere i due. FileSystemWatchernon sempre consegna in modo affidabile le notifiche di modifica su file system come container Docker o condivisioni di rete. In questi ambienti impostareDOTNET_USE_POLLING_FILE_WATCHERatruepassa al monitoraggio tramite polling a intervalli di 4 secondi; l’intervallo non è configurabile. 13 Per le particolarità generali diFileSystemWatcher— eventi persi, overflow del buffer e duplicati — vedere “Guida pratica a FileSystemWatcher”.- Una singola modifica del file può produrre più notifiche. Se l’app ripete un’operazione costosa a ogni notifica, occorre tenerlo presente: fare debounce di attivazioni ravvicinate o confrontare l’hash del contenuto e processare solo modifiche sostanziali.
Non è sempre necessario puntare a “applicare i cambi di configurazione senza riavvio”. Valori leggeri come livello di log o feature flag possono essere applicati subito con IOptionsMonitor, ma valori come la stringa di connessione al DB o la dimensione del thread pool, la cui modifica può confliggere immediatamente con risorse già in esecuzione, è più sicuro specificarli come “applicati al riavvio”. È più importante di reloadOnChange poter dire chiaramente al personale operativo “questa impostazione ha effetto al salvataggio” oppure “questa richiede un riavvio”.
8. Mappa di migrazione da app.config / Settings.settings
Quando si migra un’app dell’epoca .NET Framework a .NET, anche il meccanismo di configurazione va ricostruito. Ecco una corrispondenza tra i due. 14
| .NET Framework | .NET | Note |
|---|---|---|
<appSettings> in App.config / Web.config |
appsettings.json + IConfiguration |
La struttura gerarchica si esprime naturalmente con l’annidamento JSON |
ConfigurationManager.AppSettings["Key"] |
builder.Configuration["Key"] o IOptions<T> |
Da accesso con chiavi stringa ad accesso tipizzato |
ConfigurationManager.ConnectionStrings |
builder.Configuration.GetConnectionString("Name") |
La convenzione della sezione ConnectionStrings resta invariata |
Settings.settings (user scope) |
File JSON personalizzato sotto %LOCALAPPDATA% |
Non esiste un meccanismo autogenerato come ApplicationSettingsBase; serializzazione e salvataggio sono propri |
Settings.settings (application scope) |
appsettings.json |
Trattarlo come valori predefiniti in sola lettura |
Cifratura di <connectionStrings> (aspnet_regiis ecc.) |
DPAPI (ProtectedData) |
Il meccanismo di cifratura cambia interamente e deve essere ricostruito nella migrazione |
Durante la migrazione si incontrano spesso due trappole.
La prima è che l’aggiunta del pacchetto NuGet System.Configuration.ConfigurationManager permette al codice esistente che legge App.config di continuare a funzionare senza modifiche. È una scelta valida come primo stadio della migrazione, ma non bisogna fermarsi lì: pianificare lo spostamento a appsettings.json. Le librerie accessorie, come i logging provider, ormai assumono quasi universalmente appsettings.json, quindi mantenere il vecchio meccanismo solo per leggere App.config ha sempre meno ragioni. 14
La seconda riguarda le impostazioni user-scoped di Settings.settings. In .NET Framework una semplice chiamata a Properties.Settings.Default.Save() salvava automaticamente le impostazioni utente, ma .NET non fornisce un equivalente autogenerato pronto all’uso. Durante la migrazione preparare una propria storage class, come quella del capitolo 5.
La migrazione ha anche altri aspetti: compatibilità delle librerie dipendenti, presenza di COM interop, ripensamento della distribuzione e quadro complessivo dei controlli oltre la configurazione. Questa prospettiva di inventario è raccolta nella checklist pre-migrazione .NET Framework → .NET, che consigliamo di leggere all’inizio del progetto.
Riepilogo
La gestione della configurazione .NET nasce dalla combinazione di tre meccanismi: stratificazione dei provider tramite IConfiguration, ricezione type-safe tramite la famiglia IOptions e cambio dell’ambiente tramite variabili d’ambiente. Fino a questo punto la maggior parte delle app può usare lo stesso metodo. Le preoccupazioni specifiche delle applicazioni aziendali Windows si concentrano oltre: dove mettere le impostazioni scrivibili, come proteggere i segreti, quanto consentire i cambi a runtime e come dismettere gli asset della generazione app.config.
Andare oltre “è tutto scritto in appsettings.json” e separare posizione e gestione per ogni tipo di impostazione. Ci auguriamo che la tabella decisionale dell’articolo sia un punto di partenza. L’inventario della configurazione di un’app esistente o la definizione di una strategia di migrazione da app.config spesso non portano alla risposta migliore senza vedere i file delle impostazioni e l’ambiente di distribuzione reali; in caso di dubbio, contattateci.
Articoli correlati
- Che cos’è Generic Host?
- Generic Host + BackgroundService in un’app desktop
- Attività Task Scheduler che non partono o terminano con 0x1
- Come creare e gestire servizi Windows
- Come scegliere dove archiviare i dati di un’app Windows
- Come funzionano i profili utente Windows
- Conservare dati sensibili nelle app Windows: evitare impostazioni in chiaro con DPAPI
- Guida pratica a FileSystemWatcher
- Checklist pre-migrazione .NET Framework → .NET
Aree di consulenza correlate
KomuraSoft LLC offre consulenza tecnica sulla progettazione della gestione della configurazione per applicazioni aziendali Windows, sulla progettazione operativa delle impostazioni per ambiente e sulla strategia di migrazione da asset app.config esistenti.
Riferimenti
-
Microsoft Learn, Configuration in .NET - Alternative hosting approach. Sull’ordine di priorità dei provider assemblati per impostazione predefinita da
Host.CreateApplicationBuilder: argomenti della riga di comando → variabili d’ambiente → user secrets in sviluppo → appsettings.{Environment}.json → appsettings.json. ↩ ↩2 ↩3 -
Microsoft Learn, .NET Generic Host - Host builder settings. Sull’ordine predefinito della host configuration (variabili con prefisso
DOTNET_, argomenti) e della app configuration (appsettings.json, appsettings.{Environment}.json, Secret Manager, variabili d’ambiente, argomenti) caricate daHost.CreateApplicationBuilder. ↩ ↩2 -
Microsoft Learn, Use the .NET Generic Host in a Windows Forms app. Sui passaggi per integrare Generic Host in un’app Windows Forms e usare DI, configurazione e logging. ↩
-
Microsoft Learn, ASP.NET Core runtime environments - Environment variables that determine the runtime environment. Sulla relazione tra
DOTNET_ENVIRONMENTeASPNETCORE_ENVIRONMENT, la priorità del primo con WebApplication, il predefinito Production e la differenza di sensibilità al maiuscolo tra Windows e Linux. ↩ ↩2 -
Microsoft Learn, Options pattern in .NET - Options interfaces. Sulle differenze di lifetime, momento di riflessione delle modifiche e funzionalità supportate tra
IOptions<TOptions>,IOptionsSnapshot<TOptions>eIOptionsMonitor<TOptions>. ↩ ↩2 -
Microsoft Learn, Options pattern in .NET - Options validation. Sulla configurazione della validazione DataAnnotations tramite
ValidateDataAnnotations()e della validazione all’avvio tramiteValidateOnStart()oAddOptionsWithValidateOnStart. ↩ ↩2 ↩3 -
Microsoft Learn, Safe storage of app secrets in development in ASP.NET Core - Use the Secret Manager tool. Sul fatto che Secret Manager conserva i segreti come JSON in chiaro in
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json, senza cifrarli, e che è solo per lo sviluppo. ↩ ↩2 -
Microsoft Learn, ProtectedData Class. Su
ProtectedData.Protect/Unprotectcome wrapper di DPAPI, la differenza di scopeCurrentUser/LocalMachinee il comportamento solo Windows conPlatformNotSupportedExceptionsu altre piattaforme. ↩ ↩2 -
Microsoft Learn, Configuration providers in .NET. Sul meccanismo dei provider che unifica fonti differenti — JSON, variabili d’ambiente, riga di comando, INI, XML e altro — dietro
IConfiguration. ↩ -
Microsoft Learn, Configuration providers in .NET - Environment variable configuration provider. Su come la configurazione predefinita carica variabili
DOTNET_e argomenti nella host configuration, le normali variabili nella app configuration e come aggiungere un prefisso personalizzato. ↩ -
Microsoft Learn, Environment.GetFolderPath Method. Sul recupero dei percorsi special folder con
Environment.SpecialFoldereGetFolderPath. ↩ -
Microsoft Learn, Detect changes with change tokens in ASP.NET Core - Monitor for configuration changes. Sul parametro
reloadOnChangediAddJsonFilee sull’uso interno diPhysicalFileProvidereFileSystemWatcherper osservare il file delle impostazioni. ↩ -
Microsoft Learn, Options pattern in .NET - IOptionsMonitor. Sulla limitazione delle notifiche IOptionsMonitor ai provider basati su file system e sul passaggio al polling ogni 4 secondi con
DOTNET_USE_POLLING_FILE_WATCHERquando le notifiche non sono affidabili in container Docker o condivisioni di rete. ↩ -
Microsoft Learn, Modernize after upgrading to .NET from .NET Framework - App.config. Sui passaggi di migrazione da App.config a appsettings.json, sulla compatibilità temporanea con il pacchetto
System.Configuration.ConfigurationManagere sul pacchettoMicrosoft.Extensions.Configuration.Json. ↩ ↩2
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
Individuare con PerfView e dotnet-trace la causa di un rallentamento — introduzione pratica all'analisi delle prestazioni .NET
Quando un'app aziendale è lenta, satura la CPU o si blocca, quale strumento usare e cosa osservare? Questa guida spiega la divisione dei ...
Stampa e output PDF nelle applicazioni aziendali Windows — come scegliere tra System.Drawing.Printing, WPF e librerie di report
Organizza in una tabella decisionale la stampa WinForms con PrintDocument, la stampa WPF con FlowDocument/FixedDocument e le alternative ...
Introduzione al registro eventi di Windows e a ETW — usare per le app aziendali i meccanismi standard del sistema operativo
La vostra app aziendale Windows usa soltanto file di log? Registro eventi ed ETW sono registrazioni visibili a un livello diverso, per il...
Google Ads BtoB con un budget ridotto — come progettare e gestire una routine settimanale efficace con poche decine di migliaia di yen al mese
Guida pratica a Google Ads per aziende BtoB che vogliono iniziare con poche decine di migliaia di yen al mese. Illustra il funzionamento ...
Far comparire il sito nelle ricerche con il nome della zona — guida pratica alla SEO locale per PMI (pagine per area e Google Business Profile)
Per le PMI il cui sito non compare nelle ricerche «nome della zona + settore». L'articolo illustra l'ordine con cui migliorare la SEO loc...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Generic Host e architettura dell'applicazione
Generic Host, BackgroundService, DI, configurazione, registrazione e ciclo di vita.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Sviluppo di applicazioni Windows
La gestione della configurazione e la progettazione dei file delle impostazioni rientrano pienamente nella consulenza pratica per lo sviluppo di applicazioni Windows.
Consulenza tecnica e revisione del progetto
Decidere una strategia di migrazione da un app.config esistente è una valutazione che si presta alla consulenza tecnica con revisione della progettazione.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- Va bene mettere appsettings.json nella stessa cartella dell'exe?
- Sì, purché contenga valori predefiniti in sola lettura. Tuttavia, se quella cartella viene installata in Program Files, l'applicazione non può essere progettata per riscrivere appsettings.json. Gli utenti standard non hanno permessi di scrittura sotto Program Files: si avrà un'eccezione a runtime oppure la virtualizzazione del file system di Windows mostrerà silenziosamente contenuti diversi a utenti diversi. Separare ogni impostazione che deve essere modificabile in un file sotto %LOCALAPPDATA% o %ProgramData%.
- Devono avere priorità le variabili d'ambiente o appsettings.json?
- Non modificare la precedenza predefinita: mantenere l'ordine di override integrato appsettings.json → appsettings.{Environment}.json → variabili d'ambiente → argomenti della riga di comando. In caso di dubbio, valutare la natura del valore: i valori predefiniti che possono essere distribuiti nell'output della build appartengono ad appsettings.json, mentre quelli che variano per destinazione di distribuzione o contenitore appartengono alle variabili d'ambiente. Questa separazione mantiene chiare le responsabilità anche in seguito.
- Come devo suddividere le classi delle impostazioni?
- La regola di base è dividere le classi options per funzionalità o responsabilità — ConnectionOptions per le stringhe di connessione, ExternalApiOptions per l'integrazione con API esterne e così via — invece di riunire impostazioni non correlate in una sola classe. In questo modo si separano naturalmente anche le unità di validazione e ricaricamento per IOptionsSnapshot/IOptionsMonitor, e la validazione DataAnnotations resta autonoma per ciascuna classe.
- Devo migrare da file INI o dal Registro?
- Se si ricostruisce la gestione della configurazione da zero, consigliamo di standardizzare su appsettings.json + IConfiguration. .NET include anche un provider di configurazione INI, quindi è possibile continuare a leggere i file INI esistenti durante una migrazione graduale. Il Registro non rientra nell'ambito dei provider standard .NET; se esistono asset che ne dipendono, un approccio pratico è creare un bridge in sola lettura e spostare gradualmente i valori in appsettings.json.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.
Link pubblici