Nel contesto della gestione dei dati nelle applicazioni moderne, l'uso di ORM (Object-Relational Mapping) è diventato fondamentale. La tecnologia ORM consente di astrarre la gestione delle interazioni con il database, migliorando l'efficienza del codice e semplificando la logica di accesso ai dati. In questo capitolo, esploreremo in dettaglio come funziona Entity Framework Core (EF Core), come si integra con altri strumenti come Dapper, e le differenze tra ORM e Micro ORM.

EF Core è un ORM che facilita la gestione delle operazioni con il database senza richiedere l'uso esplicito di SQL. Con EF Core, la configurazione della connessione al database e la gestione delle tabelle vengono effettuate attraverso classi e metodi di alto livello, che eliminano la necessità di scrivere query SQL manuali. Ad esempio, la classe DbContext è il punto centrale per la gestione delle tabelle nel database. Ogni tabella che il contesto gestisce è definita come una proprietà di tipo DbSet, una collezione di entità di un certo tipo. La definizione di un'entità in C# mappa direttamente una tabella del database, permettendo di interagire con i dati come se fossero oggetti, senza dover scrivere codice SQL direttamente.

L’approccio di mappatura di EF Core si basa su convenzioni predefinite, in cui i nomi delle proprietà della classe vengono confrontati con i nomi delle colonne nella tabella del database. Queste convenzioni possono essere personalizzate facilmente con l’uso di attributi o configurazioni specifiche. Per esempio, il metodo GetAllAccountsAsync() di seguito recupera tutti i conti bancari dalla tabella Accounts senza che sia necessario scrivere query SQL esplicite:

csharp
public async Task<List<Account>> GetAllAccountsAsync() { return await _context.Accounts.ToListAsync(); }

Questo esempio dimostra come EF Core consente di ottenere i dati in modo semplice e intuitivo, con il codice che si concentra sull'accesso agli oggetti piuttosto che sulla gestione delle connessioni o la scrittura delle query. Tutte queste operazioni sono astratte da EF Core, che si occupa della traduzione del codice in SQL e della gestione delle interazioni con il database.

Uno dei vantaggi principali degli ORM è la riduzione del codice ripetitivo. Le operazioni SQL di selezione, inserimento e aggiornamento vengono generate automaticamente, consentendo agli sviluppatori di concentrarsi sulla logica dell'applicazione invece che sulla gestione diretta dei dati. Inoltre, l'uso di ORM migliora la produttività, poiché gli sviluppatori possono lavorare con oggetti, che è un approccio più naturale per chi è abituato alla programmazione orientata agli oggetti (OOP). Un altro beneficio importante è la manutenibilità, poiché l'ORM isola il codice applicativo dai dettagli specifici del database, rendendo più semplice il passaggio da un sistema di database all'altro o l'adozione di nuovi modelli di dati.

Tuttavia, nonostante i vantaggi, gli ORM presentano anche alcuni svantaggi, in particolare riguardo alle performance. Le query SQL generate da un ORM possono non essere le più efficienti in situazioni di carico elevato o quando il modello dei dati è complesso. In questi casi, gli sviluppatori esperti potrebbero scrivere manualmente query SQL più ottimizzate. Inoltre, poiché gli ORM astraggono molti dettagli del database, potrebbe risultare più difficile ottimizzare o risolvere problemi di performance, soprattutto per chi non ha una solida comprensione dei principi fondamentali dei database.

In risposta a queste preoccupazioni sulle performance, è emerso il concetto di Micro ORM. A differenza degli ORM tradizionali, che offrono una gestione avanzata delle relazioni tra oggetti e una serie di funzionalità automatiche, i Micro ORM come Dapper si concentrano principalmente sulle performance e sulla semplicità. I Micro ORM hanno un codice base molto più ridotto e poche dipendenze, il che li rende molto leggeri e veloci. Offrono un set di funzionalità di base per la mappatura degli oggetti e l'esecuzione di query, ma non gestiscono le relazioni tra oggetti o altre funzionalità avanzate degli ORM.

Dapper, ad esempio, è un Micro ORM ampiamente utilizzato nella comunità .NET. Anche se non offre le stesse caratteristiche avanzate di EF Core, come la gestione delle migrazioni automatiche o il tracking delle modifiche, Dapper permette di avere un controllo più diretto sulle query SQL. Questa caratteristica può essere particolarmente utile quando è necessario scrivere query altamente performanti o quando si lavora con modelli di dati complessi.

Micro ORM come Dapper non escludono l'uso di un ORM tradizionale, come EF Core. In effetti, in un’applicazione è possibile utilizzare entrambi gli strumenti a seconda delle necessità. L'uso combinato di un ORM tradizionale per operazioni di alto livello e un Micro ORM per query più specifiche e ottimizzate è una pratica comune, che consente di ottenere sia la semplicità di utilizzo che l'efficienza nelle performance.

Quando si decide se utilizzare EF Core, Dapper, o una combinazione di entrambi, è importante considerare la complessità della propria applicazione e le esigenze specifiche. EF Core è ideale per applicazioni che richiedono una gestione avanzata delle entità, mentre Dapper è perfetto per casi in cui la performance è critica e le query devono essere scritte in modo esplicito. Non esiste una soluzione universale: la scelta dipenderà sempre dal contesto dell'applicazione.

EF Core, nella sua ultima versione (8 al momento), è stato continuamente migliorato, aggiungendo nuove funzionalità e migliorando la gestione delle performance. Nonostante ciò, è sempre fondamentale utilizzare la tecnologia migliore per ogni caso specifico, tenendo conto dei requisiti dell’applicazione.

Perché ASP.NET Core 9 rappresenta una svolta nel mondo dello sviluppo software?

Microsoft ha intrapreso un percorso di profonda trasformazione della sua piattaforma di sviluppo, abbracciando il modello open source e concedendo così alla comunità di sviluppatori la possibilità di adottare un modello robusto, indipendente dal sistema operativo. Questa evoluzione ha portato alla nascita di ASP.NET Core, che ha sostituito la versione 4.x di ASP.NET, l’ultima ad essere limitata esclusivamente a Windows. Oggi, ASP.NET Core 9 si presenta come una piattaforma estremamente ricca, capace di soddisfare esigenze diversificate, supportata attivamente da una comunità open source globale.

ASP.NET Core 9 offre una serie di vantaggi fondamentali, quali la possibilità di sviluppare soluzioni UI web e API web, l’interoperabilità tra sistemi operativi diversi, l’adattabilità al cloud, prestazioni elevate, integrazioni con framework client moderni, e l’adozione delle migliori pratiche e standard di progettazione. È una piattaforma unificata che racchiude tutto ciò che serve per creare soluzioni avanzate, combinando tecnologie all’avanguardia e un ecosistema in continua evoluzione.

Dal punto di vista delle prestazioni, ASP.NET Core 9 introduce numerosi miglioramenti rispetto alla versione precedente, ASP.NET Core 7. Tra questi, si segnala una maggiore rapidità di esecuzione e avvio delle applicazioni, grazie a ottimizzazioni nel runtime e nel Garbage Collector. Le API minimaliste (Minimal API) si dimostrano significativamente più veloci e meno dispendiose in termini di memoria, mentre la compilazione nativa Ahead-of-Time (AOT) riduce l’ingombro su disco, accelera il caricamento e minimizza il consumo di memoria, caratteristiche particolarmente preziose per ambienti cloud-native.

L’integrazione con ML.NET 4.0 amplia le possibilità di applicare modelli di machine learning, mentre la tecnologia .NET Aspire semplifica lo sviluppo di applicazioni distribuite, osservabili e pronte per la produzione, astraggendo molte complessità tipiche del cloud. La presenza di .NET MAUI facilita la creazione di applicazioni multipiattaforma, incluse quelle per dispositivi mobili, grazie a un’integrazione fluida con Blazor.

Entity Framework Core, uno dei pilastri del framework, continua a evolversi, estendendo il supporto a nuovi database come Azure Cosmos DB e migliorando la compatibilità con AOT. Parallelamente, ASP.NET Core 9 apporta avanzamenti sostanziali in ambiti come Blazor, SignalR, autenticazione, autorizzazione e l’adesione agli standard OpenAPI, nonché una riconsiderazione dell’uso di librerie di terze parti come Swagger, per favorire soluzioni più leggere e meno dipendenti da specifiche tecnologie.

Confrontando la nuova piattaforma .NET con il suo predecessore, .NET Framework, emergono differenze sostanziali: mentre quest’ultimo è stato concepito come un ecosistema chiuso e dipendente da Windows, .NET si configura come una piattaforma modulare, multipiattaforma e aperta, sostenuta da una comunità vivace e dalla .NET Foundation, che ne garantisce l’indipendenza e il continuo sviluppo. Questo modello ha consentito una maggiore rapidità nel rilascio di nuove versioni e un ciclo di aggiornamenti più agile e sicuro.

La comprensione di questo ecosistema e del ciclo di vita delle versioni è cruciale per evitare incompatibilità o problemi nelle applicazioni esistenti. Le versioni vengono infatti rilasciate con cadenza annuale, alternate tra supporto a breve termine (STS) e a lungo termine (LTS), con aggiornamenti mensili dedicati a correzioni e patch di sicurezza. Questo approccio consente di mantenere elevati standard qualitativi senza sacrificare la stabilità.

ASP.NET Core 9 si distingue infine per la sua versatilità nello sviluppo su sistemi operativi diversi, tra cui Windows, Linux e macOS, garantendo una libertà di scelta e una facilità d’uso non comuni nelle piattaforme tradizionali.

È importante che chi si avvicina a questa piattaforma comprenda non solo le sue caratteristiche tecniche, ma anche il contesto evolutivo che ha portato a questo modello aperto e modulare. La collaborazione continua con la comunità open source, la rapidità nel rilascio di aggiornamenti e la crescente integrazione con tecnologie cloud e di intelligenza artificiale sono elementi che rendono ASP.NET Core 9 una scelta strategica per sviluppatori orientati al futuro. La padronanza di questi concetti permette di sfruttare al massimo le potenzialità della piattaforma, garantendo soluzioni moderne, scalabili e facilmente manutenibili.

Come integrare Azure App Configuration in un'applicazione ASP.NET Core: un approccio dinamico

L'integrazione delle configurazioni dinamiche è una delle pratiche fondamentali nella costruzione di applicazioni moderne e scalabili. In particolare, l'uso di Azure App Configuration in combinazione con ASP.NET Core permette di centralizzare e gestire facilmente le configurazioni, semplificando così il processo di sviluppo e la manutenzione delle applicazioni.

Per iniziare, si crea un'applicazione MVC utilizzando il comando dotnet new mvc -n DynamicConfiguration -o .. Questo comando genera una struttura di base per un'applicazione MVC, con il nome "DynamicConfiguration" e collocata nella directory corrente. Una volta creata l'applicazione, il passo successivo è prepararla per l'integrazione con Azure App Configuration.

All'interno della directory del progetto, si apre Visual Studio Code con il comando code ., quindi si crea una cartella chiamata Options e al suo interno si definisce un file denominato GlobalOptions.cs. In questo file, viene creata una classe GlobalOptions con una sola proprietà, Title, che rappresenta un valore che sarà recuperato tramite Azure App Configuration:

csharp
namespace DynamicConfiguration.Options;
public class GlobalOptions { public string Title { get; set; } }

Questa classe contiene una proprietà Title, che sarà il punto di riferimento per le configurazioni dinamiche. Quando l'applicazione viene avviata, ASP.NET Core gestirà il caricamento delle configurazioni attraverso vari provider, come i file appsettings.json o le variabili d'ambiente. Con l'uso del pattern Options, la separazione delle responsabilità, la manutenibilità, la flessibilità e l'estensibilità sono garantite.

Successivamente, si apportano delle modifiche alla classe HomeController per integrare le configurazioni dinamiche. La classe HomeController viene aggiornata in modo che venga iniettata la configurazione tramite il costruttore utilizzando l'interfaccia IOptionsSnapshot<GlobalOptions>. Questo permette di ottenere configurazioni dinamicamente, consentendo modifiche in tempo reale senza la necessità di riavviare l'applicazione.

csharp
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly GlobalOptions _globalOptions;
public HomeController(ILogger<HomeController> logger, IOptionsSnapshot<GlobalOptions> globalOptions)
{ _logger = logger; _globalOptions = globalOptions.Value; }
public IActionResult Index() { ViewData["Title"] = _globalOptions.Title; return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }

In questa versione aggiornata del controller, è stato aggiunto un nuovo campo di tipo GlobalOptions, e la configurazione viene iniettata nel costruttore. La proprietà Title viene poi utilizzata nella vista tramite ViewData["Title"].

Nel file della vista Views/Home/Index.cshtml, il valore di Title viene visualizzato nel corpo della pagina:

html
Welcome @ViewData["Title"] Learn about building Web apps with ASP.NET Core.

A questo punto, l'applicazione è pronta per interagire con Azure App Configuration. Il primo passo consiste nell'aggiungere il pacchetto NuGet necessario per l'integrazione con Azure App Configuration:

bash
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore

Una volta aggiunto il pacchetto, sarà necessario ottenere la stringa di connessione dall'Azure Portal. Questa stringa si trova nella sezione di configurazione delle risorse e deve essere copiata per l'utilizzo successivo.

Per evitare di memorizzare informazioni sensibili nel codice o nel controllo di versione, è buona pratica utilizzare dotnet user-secrets per gestire la stringa di connessione. Con il seguente comando, la stringa di connessione viene memorizzata in modo sicuro:

bash
dotnet user-secrets init dotnet user-secrets set ConnectionStrings:AppConfig "<your_connection_string>"

Successivamente, nel file Program.cs, vengono aggiunti i servizi necessari per la connessione ad Azure App Configuration e per il caricamento delle configurazioni dinamiche. Il codice aggiornato per il file Program.cs è il seguente:

csharp
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
var builder = WebApplication.CreateBuilder(args); builder.Services.AddAzureAppConfiguration(); var connectionString = builder.Configuration.GetConnectionString("AppConfig"); builder.Configuration.AddAzureAppConfiguration(options => { options.Connect(connectionString) .Select("DynamicConfiguration:*", LabelFilter.Null) .ConfigureRefresh(refreshOptions => { refreshOptions.Register("DynamicConfiguration:Sentinel", refreshAll: true); }); }); builder.Services.Configure<GlobalOptions>(builder.Configuration.GetSection("DynamicConfiguration:GlobalOptions")); builder.Services.AddControllersWithViews(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseAzureAppConfiguration(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();

In questo codice, il metodo AddAzureAppConfiguration registra i servizi necessari per l'integrazione, mentre GetConnectionString recupera la stringa di connessione da un sistema sicuro. La configurazione viene poi aggiornata dinamicamente utilizzando il metodo ConfigureRefresh.

L'approccio mostrato evidenzia come Azure App Configuration possa semplificare e centralizzare la gestione delle configurazioni in un'applicazione ASP.NET Core, migliorando la manutenzione e la scalabilità. La possibilità di aggiornare le configurazioni in tempo reale, senza la necessità di riavviare l'applicazione, è particolarmente utile in scenari di applicazioni distribuite o in ambienti di produzione.

In conclusione, l'integrazione di Azure App Configuration con ASP.NET Core tramite il pattern Options offre una soluzione robusta e flessibile per la gestione delle configurazioni dinamiche. Tuttavia, è importante comprendere che l'uso di configurazioni centralizzate porta anche la necessità di una gestione accurata dei permessi e della sicurezza delle informazioni sensibili. La connessione alle configurazioni tramite segreti, piuttosto che direttamente nel codice, è una pratica fondamentale per garantire la protezione dei dati sensibili e ridurre i rischi di esposizione in ambienti di produzione.

Come si crea una pipeline CI/CD automatizzata con GitHub Actions?

La creazione di una pipeline CI/CD rappresenta un passo cruciale nel flusso DevOps per la consegna continua del valore. Utilizzando GitHub Actions, possiamo definire un workflow che automatizzi l’intero ciclo di build, test e deploy delle nostre applicazioni. L’interfaccia di GitHub offre un editor integrato, in cui è possibile scrivere direttamente la configurazione del flusso di lavoro in un file YAML. Questo file viene tipicamente salvato nel percorso .github/workflows/ con un nome descrittivo, ad esempio cicd-pipeline.yml.

All’interno del file, il blocco iniziale on: specifica l’evento che innesca l'esecuzione del workflow. Un esempio comune è l’evento push sulla branch main, il che significa che ogni modifica inviata a questa branch attiverà la pipeline.

La sezione jobs: rappresenta l’insieme dei processi che verranno eseguiti. In uno scenario semplice, un unico job chiamato build-and-deploy può essere sufficiente. Questo job gira in ambiente ubuntu-latest e contiene una serie di steps: che definiscono le azioni da compiere.

Il primo step utilizza l’azione nativa actions/[email protected], fondamentale per clonare il repository all’interno del runner, poiché quest’ultimo viene creato dinamicamente e non possiede accesso al codice sorgente di default.

Segue poi uno step che costruisce e pubblica un'immagine Docker. Il comando docker build utilizza uno username segreto salvato nei secrets di GitHub per taggare l'immagine. Il comando successivo effettua l’autenticazione su Docker Hub in modo sicuro, utilizzando --password-stdin per evitare che le credenziali vengano esposte nei log. Infine, con docker push, l’immagine viene inviata a Docker Hub.

È essenziale definire correttamente la directory di lavoro tramite working-directory, puntando alla cartella dove si trova il Dockerfile, altrimenti i comandi Docker falliranno.

Un dettaglio tecnico importante riguarda la sintassi YAML: la struttura gerarchica si basa sull’identazione con spazi. Un errore nella spaziatura invalida l’intero file. L’uso coerente di due spazi per ogni livello è una prassi consolidata.

Una volta completato il file, è sufficiente effettuare il commit. GitHub rileverà automaticamente la presenza di un nuovo workflow e ne avvierà l’esecuzione. È possibile monitorare l’avanzamento del processo attraverso il tab “Actions” del repository. Se tutto è configurato correttamente, al termine del workflow l’immagine sarà disponibile su Docker Hub.

Per testare l’immagine localmente, si può eseguire il comando docker run specificando le porte e le variabili d’ambiente necessarie. Questo permette di avviare l’applicazione sulla propria macchina, verificandone il corretto funzionamento senza necessità di ulteriori deploy.

Il vantaggio chiave di GitHub Actions risiede nell’automazione completa del processo di integrazione e distribuzione continua. Qualsiasi nuova modifica inviata al repository produce automaticamente una nuova immagine aggiornata. Questa rapidità e consistenza nella delivery favorisce un miglioramento continuo del prodotto, mantenendo alta la qualità e riducendo il time-to-market.

È fondamentale inoltre che gli sviluppatori acquisiscano familiarità con la logica dei job e degli step all’interno dei workflow. Comprendere come separare logicamente le fasi del processo consente una gestione più robusta degli errori, una maggiore riusabilità del codice e una tracciabilità più chiara del ciclo di vita applicativo.

Al di là della configurazione tecnica, è importante sviluppare una mentalità orientata al flusso continuo del valore. La pipeline non è solo un meccanismo di deploy, ma uno strumento per abilitare l’agilità organizzativa, ridurre i colli di bottiglia e promuovere la cultura DevOps.

Per ottenere il massimo da questo approccio, bisogna considerare l’intero ecosistema in cui l’applicazione vive: l’uso dei secrets, la gestione delle versioni, l’osservabilità del sistema, la sicurezza dell’ambiente di esecuzione e la configurazione dinamica in base agli ambienti.

L’integrazione tra GitHub Actions, Docker, e ambienti cloud crea le basi per un’architettura moderna e resiliente. Questa sinergia consente di costruire sistemi che reagiscono velocemente ai cambiamenti, si adattano al carico e mantengono elevata la qualità del servizio. In questo contesto, ogni push al repository rappresenta non solo una modifica del codice, ma una nuova iterazione del prodotto pronta per essere testata, distribuita e utilizzata.

Come migliorare la sicurezza e l'efficienza in un'applicazione ASP.NET Core

ASP.NET Core è una piattaforma robusta per la creazione di applicazioni web moderne, che integra numerosi strumenti e metodologie per garantire performance elevate e sicurezza avanzata. Un elemento cruciale per la gestione delle applicazioni sicure è l'implementazione dell'Identity Management. L'Identity di ASP.NET Core offre una gestione centralizzata degli utenti, inclusi i meccanismi per autenticazione, autorizzazione e gestione dei ruoli. Utilizzando un'architettura di tipo middleware, è possibile proteggere l'accesso a specifiche risorse tramite l'uso di token di accesso (access tokens) o altre forme di autenticazione, come i JSON Web Tokens (JWT).

Nel contesto di un'applicazione ASP.NET Core, la gestione dei dati degli utenti avviene attraverso un layer di accesso ai dati (Data Access Layer), che interagisce con il database mediante Entity Framework (EF) Core. La configurazione di un contesto di database adeguato (DbContext) è essenziale per la gestione corretta dei dati degli utenti e delle loro credenziali, che vengono memorizzate in un'apposita "identity store". Per migliorare la sicurezza, è fondamentale che ogni comunicazione, soprattutto durante la trasmissione dei dati sensibili, sia protetta tramite l'uso di HTTPS, il protocollo sicuro per la trasmissione dei dati su internet.

La protezione dei dati utente non si limita alla sola protezione della comunicazione: l'autenticazione a più fattori (MFA) è una prassi sempre più diffusa per garantire un livello di sicurezza superiore. L'implementazione di meccanismi di sicurezza come l'uso di access token per le API, oltre a garantire l'autenticità dell'utente, impedisce accessi non autorizzati alle risorse critiche, proteggendo l'applicazione da attacchi di tipo cross-site request forgery (CSRF) e cross-site scripting (XSS).

Un altro aspetto importante è l'ottimizzazione delle performance delle applicazioni, che possono beneficiare dell'integrazione con tecnologie come Blazor e SignalR. Blazor permette di creare interfacce utente interattive direttamente in C#, eliminando la necessità di scrivere codice JavaScript. SignalR, d'altra parte, consente una comunicazione in tempo reale tra client e server, particolarmente utile per applicazioni di messaggistica o aggiornamenti dinamici delle interfacce.

Il rafforzamento della sicurezza non si limita alla protezione contro attacchi diretti, ma include anche la gestione delle risorse attraverso un uso efficiente delle risorse di sistema. Tecniche come il caching e la compressione dei dati (ad esempio, tramite Brotli o Gzip) possono ridurre significativamente i tempi di risposta dell'applicazione, migliorando l'esperienza utente e la scalabilità dell'applicazione.

Infine, la gestione dei dati attraverso un database ben progettato è un altro punto cruciale. L'uso di un'architettura a microservizi e container, supportata da strumenti come Docker e Kubernetes, offre una notevole flessibilità e scalabilità. Containerizzare l'applicazione permette di distribuire in modo efficace le risorse e ottimizzare la gestione dei carichi di lavoro, soprattutto in ambienti cloud.

Concludendo, è fondamentale che i sviluppatori comprendano che un'applicazione sicura ed efficiente non si limita all'adozione di tecnologie avanzate, ma richiede anche una configurazione accurata dei sistemi di autenticazione, la protezione delle comunicazioni e la gestione ottimizzata delle risorse. Implementare correttamente questi strumenti permette di garantire una base solida per lo sviluppo di applicazioni robuste, sicure e performanti.