Nel mio ultimo progetto ho visto alcuni colleghi impostare un’applicazione console senza configurare Dependency Injection (DI). Penso che oggi questa configurazione non stia sfruttando la funzionalità incorporata in .net core che può darci flessibilità e separazione delle responsibilità.
Ad un certo punto abbiamo avuto difficoltà a reperire informazioni di debug (era un cron-job), quindi ho pensato di aggiungere Azure Application Insight. Quindi ho pensato “Sì, è un lavoro di 5 minuti”. Dopo poche ore ho rinunciato, troppe modifiche e in pratica era necessario un refactoring completo.
Ma ciò che mi ha davvero sorpreso è stata la scarsa flessibilità di tale configurazione.
Penso che questo scenario possa accadere molte volte, specialmente se non hai tempo da investire in una soluzione migliore, a causa delle consegne imminenti.
Quindi ho passato un po’ di tempo a configurare un template con le funzionalità più utili che penso potremmo aver bisogno in un’applicazione console, come un cron-job o un processore di eventi.
Il codice per l’app console creata utilizzando .NET Core 5 con supporto alle best practice di Microsoft DI è disponibile in questo repository github.
Le funzionalità disponibili sono:
- Multiple environments,
- Classi con le configurazioni con il
Options pattern
, - Appsettings,
- Usersecrets,
- Azure KeyVault,
- Azure Application Insight,
- TelemetryClient,
- HostedService,
- HttpClientFactory,
Configurazione
Ambienti
È possibile configurare più ambienti (sviluppo, test, produzione, ecc.) in modi diversi.
Quindi è possibile eseguire con una configurazione di ambiente specifica, ad esempio:
Project
>Properties
>Debug
>Environment variables
> Name:DOTNET_ENVIRONMENT
, Value:Development
dotnet run --environment=Development
Properties
>launchSettings.json
Quindi puoi creare diversi file appsettings.ENV.json
, come appsettings.development.json
.
NB. context.HostingEnvironment.IsDevelopment()
richiede un ambiente chiamato Development
(e non solo Dev
)
I file di configurazione possono contenere solo oggetti parziali e possono essere sovrascritti, seguendo il seguente ordine:
appsettings.json
appsettings.ENV.json
secrets.json
- Azure KeyVault
Le configurazioni personalizzate (Options) devono essere inserite in oggetti e non aggiunte direttamente alla classe AppConfig.
In questo modo è possibile iniettare queste classi durante DI.
Segreti utente
È possibile memorizzare una configurazione “sensibile” nei file secrets.json
(che si può aggiungere facilmente con Visual Studio) e in questa configurazione è obbligatorio.
Azure KeyVault
Configura nella tua sottoscrizione un Azure KeyVault e copia l’uri di KeyVault nel file di configurazione:
KeyVaultUri: "https://your-key-vault-name.vault.azure.net/"
Azure Application Insight
Configura nella tua sottoscrizione un Azure Application Insight e copia l’InstrumentationKey
(un GUID) nel file di configurazione:
InstrumentationKey: "your application insight guid"
.
Configureremo anche un TelemetryClient
, ma tieni presente che Flush()
non è un’operazione sincrona e non garantisce che i log vengano inviati al backplane di Azure. Ciò significa che dobbiamo aggiungere una Sleep come suggerito dai documenti, in attesa di una correzione adeguata nel pacchetto AI.
NB. Non c’è un tempo ufficiale di Sleep suggerito, quindi modifica questo valore in base alle tue esigenze.
Configurare il logging
Il livello di logging può essere configurato nei file di configurazione.
Fare il dump della configurazione finale
È possibile, solo per scopi di debug, eseguire il dump dell’albero di configurazione.
NB. non utilizzare in produzione, né esporre pubblicamente, poiché è un rischio per la sicurezza.
Dependency Injection (DI)
L’obiettivo di questa demo/template è mostrare come sia possibile supportare DI per ogni dipendenza. Un esempio è IHttpClientFactory
che consente di creare HttpClients
in modo sicuro, aggiunge molte funzionalità (come “named instances”), è molto flessibile e può essere configurato in molti modi.
HostedService
Il punto di partenza della console è un HostedService
, un Singleton che il framework gestirà per noi e si occuperà di chiamare StartAsync
e StopAsync
nel momento giusto del ciclo di vita dell’applicazione, consentendo anche arresti “puliti”.
All’interno di ConsoleHostedService è possibile utilizzare servizi con un lifescope diverso, come un Transient
o un servizio Scoped
(es. EntityFramework
), utilizzando IServiceScopeFactory
.
Spero sia da aiuto. Fatemi sapere cosa ne pensate!