Il pattern Model-View-Controller (MVC) in ASP.NET Core si basa su una convenzione strutturale che semplifica la gestione delle richieste e l’interazione tra le componenti dell’applicazione. Questa convenzione si manifesta in particolare nella configurazione delle rotte, che definiscono quali controller e azioni saranno invocati in risposta a richieste HTTP specifiche. Nel file program.cs, prima dell’esecuzione di app.Run(), viene chiamato il metodo app.MapControllerRoute, il quale imposta la rotta predefinita con il pattern "{controller=Home}/{action=Index}/{id?}". Ciò significa che, qualora la richiesta non specifichi un controller o un’azione, il framework assegna automaticamente come default il controller Home e l’azione Index, mentre il parametro id è facoltativo.
Questa struttura sottolinea una differenza sostanziale rispetto alle Razor Pages, poiché MVC non opera su pagine statiche, ma su azioni. L’azione è una funzione pubblica all’interno della classe Controller, che agisce da orchestratore tra il modello (Model) e la vista (View). L’azione interpreta l’intento dell’utente, esegue la logica necessaria e restituisce una risposta, che può essere una View o un dato JSON.
Prendendo come esempio la classe PersonController, ereditata dalla classe base Controller, vediamo come le azioni siano metodi pubblici con nomi significativi: Index() restituisce la vista di default, GetPeople() restituisce un elenco di persone in formato JSON, Register(PersonModel personModel) elabora i dati inviati da un form e infine Result(string message) mostra un risultato specifico. La convenzione dei nomi è importante: il suffisso “Controller” identifica la classe come controller e la vista associata all’azione Index() viene cercata nella cartella /Views/Person/Index.cshtml, aderendo rigidamente alla struttura del progetto.
Il metodo View() è centrale in questo meccanismo: richiamandolo senza parametri, restituisce la vista che corrisponde al controller e all’azione correnti, ma può anche ricevere un modello dati da passare alla vista, permettendo così una stretta integrazione tra la logica di business e la presentazione.
In MVC, la comunicazione tra controller e vista è facilitata da metodi della classe base Controller, come Json(), che restituisce dati serializzati in formato JSON, e RedirectToAction(), che consente di reindirizzare l’utente verso un’altra azione, passando parametri come nel caso della registrazione di una persona.
L’architettura MVC offre quindi una grande flessibilità: il controller decide come trattare la richiesta, quale vista o dato restituire, mantenendo separati i compiti di gestione dati (Model), logica di flusso (Controller) e presentazione (View). Questo modello migliora la manutenibilità e facilita l’estensione dell’applicazione.
L’implementazione delle Views in ASP.NET MVC utilizza la sintassi Razor, che permette di mescolare codice C# e markup HTML in modo pulito. Nel file Index.cshtml, ad esempio, viene dichiarato un modello fortemente tipizzato tramite @model MyFirstMVCApp.Models.PersonModel. Questo consente l’uso di tag helper, come @Html.LabelFor e @Html.TextBoxFor, che generano automaticamente gli elementi HTML associati alle proprietà del modello, mantenendo il binding tra la vista e i dati.
È considerata buona pratica avere un’azione Index() e una vista Index.cshtml per ogni controller, così da evitare ambiguità nelle rotte e migliorare l’esperienza utente. Inoltre, la strutturazione dei file nella cartella /Views/ segue la convenzione che assegna ad ogni controller la propria sottocartella contenente le viste correlate.
Attraverso i tag helper, il form creato in Razor associa correttamente i dati degli input alle proprietà del modello, garantendo così che, quando l’utente invia il form (ad esempio con il pulsante “Register”), i dati siano trasmessi e processati dall’azione corretta del controller. Questo flusso, semplice ma efficace, è il cuore del pattern MVC e ne rappresenta uno dei principali punti di forza.
Oltre a quanto esposto, è fondamentale comprendere che la separazione tra controller e vista consente un’alta coesione nel codice e facilita test, debugging e riuso dei componenti. Il controller rimane agnostico rispetto alla modalità di presentazione, potendo restituire risposte diverse a seconda del contesto o delle richieste specifiche (ad esempio JSON per API, viste per pagine web). La conoscenza approfondita delle convenzioni e del funzionamento delle rotte è imprescindibile per sfruttare appieno la potenza di ASP.NET MVC, così come la corretta definizione e tipizzazione dei modelli che assicurano integrità e coerenza dei dati durante il ciclo di vita dell’applicazione.
Come si costruisce un'applicazione in tempo reale con SignalR?
L'integrazione di SignalR in un progetto consente la comunicazione in tempo reale tra client e server in modo semplice ma estremamente potente. Alla base del sistema c'è la classe Hub, una classe astratta fornita dal pacchetto Microsoft.AspNetCore.SignalR, che gestisce la connessione, la comunicazione e la sincronizzazione tra client. Ogni classe personalizzata che implementa una logica di comunicazione in SignalR deve ereditare da Hub, definendo al suo interno i metodi che verranno esposti ai client.
Nel nostro esempio, viene creata una classe chiamata TaskManagerHub, che espone due metodi asincroni: CreateTask e CompleteTask. Entrambi i metodi ricevono in input un oggetto della classe TaskModel, che rappresenta una semplice entità con tre proprietà: Id (un identificatore univoco generato automaticamente), Name (il nome del task) e IsCompleted (un booleano che indica se il task è stato completato o meno). La classe TaskModel è concepita in modo minimalista, sufficiente per rappresentare lo stato di un'attività e supportare la logica di interazione in tempo reale.
La comunicazione tra client e server utilizza, in generale, WebSockets come strategia di trasporto. I dati vengono trasmessi sotto forma di JSON testuale; il binario, spesso usato per contenuti multimediali, non è supportato nel contesto corrente, focalizzato su scambio di dati testuali.
Nel metodo CreateTask, l'oggetto taskModel viene prima salvato tramite _taskRepository.Save(taskModel), dove _taskRepository rappresenta un'interfaccia verso un livello di persistenza in memoria. Subito dopo, il server notifica tutti i client connessi al Hub attraverso Clients.All.SendAsync(...), invocando un metodo lato client il cui nome è specificato tramite una costante (ClientConstants.NOTIFY_TASK_MANAGER). Questo approccio è fondamentale per ridurre errori causati da nomi di metodo scritti come stringhe raw, facilitando la manutenzione e rendendo il codice più robusto.
Per rendere operativa l'applicazione lato server, è necessario configurare i servizi SignalR nel contenitore di dipendenze. All'interno del file Program.cs, si aggiunge la riga builder.Services.AddSignalR() prima di costruire l'applicazione con builder.Build(). Successivamente, si mappa l'endpoint del Hub con app.MapHub("/taskmanagerhub"), permettendo così ai client di connettersi al percorso /taskmanagerhub, come da convenzione REST.
Lato client, viene utilizzata una semplice pagina Razor (Index.cshtml) che contiene un form per aggiungere un nuovo task, e due elenchi per visualizzare i task completati e non completati. Anche se l’HTML è privo di direttive Razor specifiche, la logica client-server sarà interamente gestita da JavaScript, sfruttando l’SDK SignalR precedentemente installato. Il codice JavaScript (non mostrato qui) sarà responsabile per l’instaurazione della connessione, la gestione degli eventi e l’aggiornamento dinamico dell’interfaccia utente in risposta alle notifiche dal server.
È essenziale sottolineare che, sebbene l’applicazione usi Razor Pages per semplificare la dimostrazione, SignalR è perfettamente compatibile con architetture SPA (Single Page Application) basate su framework moderni come Angular, React o Vue.js. In questi scenari, il client e il server possono essere progetti completamente separati, uniti soltanto dalla connessione SignalR tramite WebSockets. Questo approccio, molto più scalabile e modulare, rappresenta la prassi nelle applicazioni moderne che richiedono interazione in tempo reale, come dashboard, chat, notifiche istantanee o sistemi collaborativi.
È importante comprendere a fondo il flusso dei dati e la gestione delle connessioni, perché SignalR è più di una semplice libreria di comunicazione: introduce una nuova architettura che spezza il paradigma tradizionale delle chiamate HTTP asincrone. Ogni client mantiene una connessione persistente con il server, e ogni evento può essere propagato istantaneamente a uno o più destinatari. Il controllo di questa granularità – chi riceve cosa, e quando – è ciò che determina l’efficacia e l’efficienza di un sistema basato su SignalR.
L’affidabilità della comunicazione, la gestione delle disconnessioni, l’autenticazione, e il bilanciamento del carico in ambienti distribuiti sono aspetti che diventano centrali in scenari più complessi. Tuttavia, il cuore di tutto rimane sempre la definizione chiara del contratto tra client e server e la buona progettazione delle classi Hub e dei modelli trasmessi.
Come integrare EF Core e Dapper nella gestione delle migrazioni e nell'interazione con il database
Quando si lavora con EF Core e Dapper, uno degli aspetti fondamentali da comprendere è la gestione delle migrazioni. Queste creano un insieme di classi nel progetto, e queste classi non dovrebbero essere modificate manualmente. Come mostrato nella Figura 5.9, l'aggiunta di migrazioni iniziali implica la generazione di tre file nella cartella Migrations dell'applicazione. Questi file contengono gli script di creazione delle risorse nel database. Ad esempio, nel file InitialDatabase.cs si osserva un frammento di codice che crea una tabella "Customers" nel database:
Ogni modifica al modello del dominio dell'applicazione richiede l'aggiunta di una nuova migrazione. In questo modo, si mantiene una cronologia delle modifiche, facilitando la manutenzione e l'evoluzione sia del database che dell'applicazione. Per applicare le migrazioni al database, anziché eseguire manualmente script SQL tramite strumenti come Azure Data Studio, si può utilizzare il comando CLI di EF Core dotnet ef database update. Questo comando aggiornerà il database in base alla versione mappata nell'applicazione, senza necessità di intervento manuale.
Quando si esegue il comando, la CLI si connette al server SQL e applica gli script per creare il database e le tabelle mappate nell'applicazione. Così facendo, ogni oggetto creato nel database, come mostrato nella Figura 5.10, sarà pronto per essere utilizzato dall'applicazione.
Con la configurazione della comunicazione con il database, il passo successivo è l'integrazione delle API per l'interazione con il database. Per esempio, è possibile definire alcune rotte nel file Program.cs per gestire le operazioni sulla tabella "Customers". Le rotte GET e POST permettono di recuperare, inserire e aggiornare i dati relativi ai clienti:
In queste rotte, si osserva l'uso del contesto BankingDbContext che viene risolto automaticamente tramite il sistema di iniezione delle dipendenze di .NET Core. In particolare, il metodo GET restituisce la lista di tutti i clienti dal database, mentre il metodo POST permette di aggiungere un nuovo cliente.
Uno degli aspetti più importanti nella gestione delle operazioni asincrone è l'uso dei metodi async e await. La programmazione asincrona consente di eseguire molteplici richieste simultaneamente senza bloccare i thread del server durante operazioni lunghe, come le query al database. Inoltre, l'uso di token di annullamento (cancellation tokens) consente di interrompere le operazioni in modo sicuro, garantendo che le risorse vengano liberate correttamente quando una richiesta viene annullata o scade.
Tuttavia, sebbene EF Core fornisca un potente strumento ORM, non è sempre la soluzione ideale per tutte le applicazioni, soprattutto quando si parla di prestazioni. EF Core, pur essendo altamente produttivo e facilitante la gestione delle entità, può risultare più lento rispetto ad altre soluzioni più leggere, come i micro-ORM. Un ottimo esempio di micro-ORM è Dapper, che si distingue per la sua velocità e semplicità nell'eseguire query SQL dirette.
Dapper offre numerosi vantaggi quando si desidera un approccio più performante, pur mantenendo la capacità di mappare entità del database in oggetti C#. In un'applicazione che già utilizza EF Core, Dapper può essere integrato facilmente per gestire operazioni ad alte prestazioni. Per farlo, basta aggiungere il pacchetto Dapper al progetto tramite il comando:
Successivamente, nel file Program.cs, è possibile definire nuove rotte che utilizzano Dapper per eseguire query SQL dirette, ottenendo una performance molto più elevata rispetto all'uso di un ORM completo:
Questa rotta consente di eseguire una query SQL per ottenere tutti i clienti ordinati per nome, sfruttando la velocità di Dapper per le operazioni di lettura e migliorando le performance complessive dell'applicazione.
EF Core e Dapper non sono tecnologie mutuamente esclusive, e l'uso combinato di entrambe può portare grandi benefici, in particolare quando si desidera un equilibrio tra la semplicità di gestione dei dati e l'efficienza nelle prestazioni.
In sintesi, la gestione delle migrazioni, l'interazione asincrona con il database e l'integrazione di tecnologie come EF Core e Dapper sono passi fondamentali per lo sviluppo di applicazioni moderne e performanti. La scelta dell'approccio giusto dipende dalle esigenze specifiche dell'applicazione, ma la combinazione di entrambi gli strumenti consente di ottimizzare sia la produttività che le prestazioni.
Come configurare ASP.NET Core Identity con Entity Framework e SQL Server
La configurazione corretta della classe DbContext all’interno del contenitore di iniezione delle dipendenze di ASP.NET Core è un passaggio fondamentale per l’implementazione di un sistema di autenticazione e autorizzazione sicuro. In questo contesto, abbiamo aggiunto la classe BankingDbContext al contenitore di iniezione delle dipendenze, configurando l’utilizzo di SQL Server, la cui stringa di connessione viene passata come parametro al metodo UseSqlServer. Tale stringa di connessione è ottenuta tramite le impostazioni dell’applicazione, che nel nostro caso possono essere trovate nel file appsettings.json. Con queste configurazioni in atto, ASP.NET Core Identity viene correttamente integrato nello strato di dati.
La struttura del database dbBanking è stata configurata in modo tale che contenga le tabelle necessarie per la gestione dell’identità. Al momento, la struttura di questo database è composta da quattro tabelle, di cui tre sono parte del contesto dell’applicazione: dbo.Accounts, dbo.Customers e dbo.Movements. La quarta tabella, dbo.EFMigrationsHistory, è utilizzata per la gestione delle modifiche apportate al database tramite le migrazioni.
Nel Capitolo 5, abbiamo visto come funziona il sistema di migrazioni in Entity Framework Core, e come queste consentano di effettuare modifiche dinamiche al database. Se desideri approfondire come funzionano le migrazioni in ASP.NET Core 9, puoi consultare la documentazione ufficiale di Microsoft.
Il database, infatti, conserva una cronologia delle entità che sono state create inizialmente per l’API bancario. Questa cronologia può essere visualizzata attraverso il codice nella struttura delle directory dell’applicazione, nella cartella Migrations. Le classi di migrazione vengono generate automaticamente dallo strumento della riga di comando di Entity Framework Core e non dovrebbero essere modificate manualmente.
Dopo aver apportato modifiche alla classe DbContext e aver aggiunto i modelli di ASP.NET Core Identity, è necessario aggiornare il database per applicare tali modifiche. Per farlo, è sufficiente creare una nuova migrazione. Per esempio, si può eseguire il comando dotnet ef migrations add IdentityModels nel terminale, nella directory radice dell’applicazione. Una volta generata la migrazione, è possibile applicarla al database eseguendo il comando dotnet ef database update. Questo comando esamina le migrazioni disponibili, analizza la cronologia delle migrazioni nella tabella dbo.EFMigrationsHistory e applica gli aggiornamenti necessari, creando le tabelle necessarie per il corretto funzionamento di ASP.NET Core Identity.
Con questo passaggio, tutte le configurazioni di base relative al modello di dati di ASP.NET Core Identity sono state aggiunte con successo. Tuttavia, per implementare completamente il sistema di autorizzazione e autenticazione, sono necessarie ulteriori configurazioni all’interno dell’applicazione.
La gestione dell’autenticazione e dell’autorizzazione in ASP.NET Core Identity viene realizzata attraverso il contenitore di iniezione delle dipendenze, che fornisce i servizi necessari per l’autenticazione e la generazione di token. Per abilitare queste funzionalità, è necessario modificare il file Program.cs dell’applicazione. La configurazione prevede l’aggiunta di alcuni servizi specifici per l’autenticazione, come ad esempio builder.Services.AddAuthentication().AddBearerToken() per configurare l’autenticazione tramite bearer token. Inoltre, è fondamentale aggiungere i servizi per l’autorizzazione con builder.Services.AddAuthorization(), e configurare l’accesso ai dati tramite Entity Framework Core con builder.Services.AddIdentityApiEndpoints().AddEntityFrameworkStores().
Una volta aggiunti questi servizi, bisogna mappare gli endpoint di Identity tramite app.MapIdentityApi(), aggiungere la gestione della pipeline di autenticazione con app.UseAuthentication() e l’autorizzazione con app.UseAuthorization().
Con queste modifiche, l’applicazione sarà in grado di gestire correttamente l’autenticazione e l’autorizzazione degli utenti, attraverso un sistema basato su ASP.NET Core Identity integrato con Entity Framework Core e SQL Server. È importante comprendere che la configurazione del sistema di autorizzazione non si limita alla semplice creazione delle tabelle: bisogna configurare correttamente la gestione dei permessi, la protezione delle risorse e la validazione dei token per garantire la sicurezza dell’intera applicazione.
Per una corretta gestione della sicurezza, è anche necessario implementare politiche di autorizzazione specifiche per ogni risorsa, in modo che solo gli utenti con i permessi adeguati possano accedere a determinate funzioni o dati sensibili. Queste politiche vanno configurate con attenzione per evitare vulnerabilità che potrebbero compromettere la sicurezza complessiva del sistema.
Come implementare il logging in ASP.NET Core: l'importanza della registrazione degli eventi e la gestione centralizzata dei log
Il logging è un aspetto fondamentale per il monitoraggio e il debug delle applicazioni. In un sistema complesso, è essenziale poter registrare e monitorare informazioni su come funziona l'applicazione. Questo processo fornisce indicazioni dettagliate sul comportamento dell'applicazione e facilita l'individuazione di errori, anomalie e problemi operativi. In ASP.NET Core, la gestione dei log è centralizzata e viene gestita tramite interfacce come ILogger e ILoggerFactory.
L'interfaccia ILogger è l'elemento chiave per registrare messaggi di log a diversi livelli di gravità. Ogni messaggio di log può essere classificato in uno dei seguenti livelli:
-
Trace: informazioni molto dettagliate, generalmente utilizzate solo per la diagnostica dei problemi.
-
Debug: informazioni utili per il processo di debug dell'applicazione.
-
Information: messaggi informativi che evidenziano il progresso dell'applicazione.
-
Warning: situazioni che potrebbero risultare dannose, ma non sono errori.
-
Error: errori che impediscono l'esecuzione di una funzionalità dell'applicazione.
-
Critical: errori critici che causano il completo fallimento dell'applicazione.
La flessibilità del logging in ASP.NET Core si riflette nella varietà di metodi offerti dall'interfaccia ILogger. I metodi principali sono:
-
Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter): il metodo di base per registrare i messaggi di log, che consente di specificare il livello del log, l'ID dell'evento, lo stato, le eccezioni e una funzione di formattazione.
-
Metodi di convenienza: come
LogTrace,LogDebug,LogInformation,LogWarning,LogError, eLogCritical, che permettono di registrare messaggi specifici in modo semplice e diretto.
In aggiunta, l'interfaccia ILogger supporta il concetto di "scope". Il metodo BeginScope(TState state) permette di raggruppare le operazioni loggate sotto un contesto logico comune. Questo è utile per correlare una serie di operazioni che fanno parte di un flusso di lavoro più ampio.
Per esempio, consideriamo il codice seguente che mostra l'utilizzo dell'interfaccia ILogger all'interno di una classe di servizio:
In questo esempio, i messaggi di log vengono generati utilizzando diversi metodi dell'interfaccia ILogger, come LogInformation e LogError. I log vengono associati alla classe MyService, il che consente di tracciare facilmente la provenienza dei messaggi. Questo approccio è un chiaro esempio di come il logging possa essere configurato in modo centralizzato e gestito in maniera sistematica all'interno di un'applicazione ASP.NET Core.
Un altro potente strumento per gestire i log è l'interfaccia ILoggerFactory. Questa interfaccia è responsabile della creazione delle istanze di ILogger e della configurazione dei provider di logging, che determinano come e dove i log vengono registrati. I principali metodi offerti da ILoggerFactory includono:
-
CreateLogger(string categoryName): crea un'istanza di
ILoggerper una categoria specificata, che solitamente corrisponde al nome della classe o del componente di applicazione associato al logger. -
AddProvider(ILoggerProvider provider): aggiunge un provider di logging alla factory. Questo permette di configurare dove i log verranno inviati, ad esempio alla console, a un file o a un servizio remoto come Azure Application Insights o Elasticsearch.
-
Dispose(): elimina la factory di logger e tutte le risorse ad essa associate.
Il vantaggio principale nell'utilizzare ILoggerFactory rispetto a ILogger è la capacità di creare categorie specifiche per i log, che consentono di organizzare meglio le informazioni e filtrarle in modo più efficiente. Ad esempio, utilizzando la factory, possiamo raggruppare i log di una classe sotto una categoria comune, facilitando la gestione e l'analisi delle informazioni.
Ecco un esempio di utilizzo di ILoggerFactory:
La differenza principale tra l'uso di `ILo
Come si garantisce la coerenza tra modelli numerici e teoria fondamentale nelle analisi strutturali non lineari?
Come Gestire una Presidenza Improvvisata: Il Ruolo del Consigliere Militare e le Sfide di Trump

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский