Passa al contenuto principale

Configurazione tipizzata

ASP.NET Core offre un sistema di configurazione basato su IOptions<T> che trasforma le sezioni di appsettings.json in oggetti C# tipizzati. L'obiettivo è eliminare le stringhe magiche e rendere esplicite le dipendenze dalla configurazione.

Pattern base: IOptions<T>

// Options class
public class EmailOptions
{
public const string SectionName = "Email";

public string SmtpHost { get; init; } = string.Empty;
public int SmtpPort { get; init; } = 587;
public string SenderAddress { get; init; } = string.Empty;
public string SenderName { get; init; } = string.Empty;
}
// appsettings.json
{
"Email": {
"SmtpHost": "smtp.example.com",
"SmtpPort": 587,
"SenderAddress": "noreply@example.com",
"SenderName": "Example App"
}
}
// Program.cs — registrazione
builder.Services.Configure<EmailOptions>(
builder.Configuration.GetSection(EmailOptions.SectionName));
// Utilizzo nel servizio
public class EmailSender
{
private readonly EmailOptions _options;

public EmailSender(IOptions<EmailOptions> options)
{
_options = options.Value;
}
}

IOptionsMonitor<T> — configurazione hot-reload

IOptions<T> legge la configurazione una sola volta all'avvio. IOptionsMonitor<T> ricarica il valore ogni volta che appsettings.json cambia su disco:

public class FeatureSwitchService
{
private readonly IOptionsMonitor<FeatureSwitchOptions> _monitor;

public FeatureSwitchService(IOptionsMonitor<FeatureSwitchOptions> monitor)
{
_monitor = monitor;
}

public bool IsEnabled(string feature) =>
_monitor.CurrentValue.EnabledFeatures.Contains(feature);
}

IOptionsSnapshot<T> è una via di mezzo: ricalcola il valore per ogni request HTTP (scoped), ma non notifica in tempo reale come IOptionsMonitor<T>.

InterfacciaCiclo di vitaQuando ricaricare
IOptions<T>SingletonMai — valore fisso all'avvio
IOptionsSnapshot<T>ScopedAd ogni request
IOptionsMonitor<T>SingletonQuando il file cambia su disco

Validazione con DataAnnotations

using System.ComponentModel.DataAnnotations;

public class EmailOptions
{
public const string SectionName = "Email";

[Required]
public string SmtpHost { get; init; } = string.Empty;

[Range(1, 65535)]
public int SmtpPort { get; init; } = 587;

[Required, EmailAddress]
public string SenderAddress { get; init; } = string.Empty;
}
// Program.cs — validazione attivata esplicitamente
builder.Services
.AddOptions<EmailOptions>()
.BindConfiguration(EmailOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart(); // fallisce al boot se la configurazione non è valida

ValidateOnStart() fa fallire l'avvio dell'applicazione se le opzioni non passano la validazione — invece di fallire alla prima request. È il comportamento preferito: meglio un avvio che fallisce subito che un errore silenzioso in produzione.

Validazione custom con IValidateOptions

Per regole più complesse che coinvolgono più campi:

public class EmailOptionsValidator : IValidateOptions<EmailOptions>
{
public ValidateOptionsResult Validate(string? name, EmailOptions options)
{
var errors = new List<string>();

if (options.SmtpPort == 465 && !options.UseSsl)
errors.Add("La porta 465 richiede SSL abilitato.");

if (options.SenderAddress == options.ReplyToAddress)
errors.Add("SenderAddress e ReplyToAddress non possono coincidere.");

return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success;
}
}

// Registrazione
builder.Services.AddSingleton<IValidateOptions<EmailOptions>, EmailOptionsValidator>();

Configurazione per ambiente

Si usa il meccanismo di override di ASP.NET Core: appsettings.Production.json sovrascrive i valori di appsettings.json:

// appsettings.Development.json
{
"Email": {
"SmtpHost": "localhost",
"SmtpPort": 1025
}
}
// appsettings.Production.json
{
"Email": {
"SmtpHost": "smtp.sendgrid.net",
"SmtpPort": 587
}
}

I segreti (password, connection string, API key) non vanno mai in appsettings.json. Si usano variabili d'ambiente o un secret manager. Vedi regole/configurazione.

Organizzazione consigliata

Ogni sezione di configurazione ha la sua classe Options nella stessa cartella del servizio che la usa:

NomeSoluzione.Api/
├── Email/
│ ├── EmailSender.cs
│ └── EmailOptions.cs
├── Storage/
│ ├── BlobStorageService.cs
│ └── BlobStorageOptions.cs

La costante SectionName nella classe Options evita stringhe duplicate tra registrazione e appsettings.json.