Quando si lavora con Razor Pages in ASP.NET Core, si adotta un modello di rendering lato server che consente la generazione dinamica dell’interfaccia utente attraverso l’interazione con il codice server-side. Tutto inizia definendo alcune proprietà fondamentali nel file Index.cshtml.cs, come una proprietà Message che sarà utilizzata per visualizzare un messaggio nell’interfaccia utente.

Successivamente, si struttura il modello dati dell’applicazione: si crea una cartella Models nel progetto, in cui viene aggiunta una classe Product con proprietà Id, Name e Price. Questo rappresenta l’oggetto base che sarà manipolato e visualizzato nel frontend.

Nel file Index.cshtml.cs, si aggiungono poi ulteriori proprietà: una lista di prodotti (List<Product> Products) e una proprietà Color che conterrà informazioni stilistiche da applicare dinamicamente. Per popolare la lista iniziale, viene definito un metodo GenerateProduct, che genera una lista casuale di prodotti. Il costruttore della classe IndexModel viene quindi modificato per inizializzare Products e Message con valori predefiniti al caricamento della pagina.

Per permettere l’interazione con l’interfaccia, viene implementato un metodo OnPost, che riceve come parametro la quantità di prodotti da generare, permettendo così all’utente di richiedere dinamicamente una nuova lista attraverso un modulo HTML con metodo POST. A questa logica si aggiunge OnGetDefineColor, che imposta dinamicamente la proprietà Color in base all’id passato tramite richiesta GET. Il metodo sfrutta la convenzione del binding, attraverso la quale Razor Pages associa i parametri della query agli argomenti del metodo in base al nome.

Tutto questo avviene seguendo una convenzione ben definita: i metodi OnGet, OnPost, OnDelete, OnPut sono associati ai rispettivi verbi HTTP. Razor Pages interpreta automaticamente il nome del metodo e lo associa al tipo di richiesta, semplificando così l’integrazione tra logica server-side e comportamenti client-side.

Nel markup HTML, i parametri vengono passati utilizzando la direttiva asp-route, come in asp-route-id="1", che indica il valore da legare al parametro id del metodo chiamato. Razor Pages utilizza questo sistema per associare dinamicamente i dati dell’interfaccia con la logica server-side, mantenendo una separazione chiara e strutturata tra dati, logica e presentazione.

Una volta implementata questa struttura, l’applicazione è in grado di ricevere input dall’utente, elaborare le informazioni sul server e restituire dinamicamente una pagina HTML aggiornata. Questo modello di interazione è tipico del rendering lato server, dove ogni azione dell’utente genera una richiesta al server, che elabora e restituisce una nuova rappresentazione dell’interfaccia.

Questo approccio si avvicina molto al pattern MVC, su cui si basa ASP.NET Core MVC. In quest’altro modello, l’architettura è suddivisa in tre componenti fondamentali: il Model, che rappresenta lo stato e la logica dell’applicazione; il View, che gestisce la presentazione e l’interfaccia utente; e il Controller, che funge da coordinatore, ricevendo input dall’utente e orchestrando le risposte appropriate attraverso il modello e la vista.

Nel pattern MVC, il controller agisce in risposta agli input utente ricevuti dalla vista, manipolando il modello e restituendo una vista aggiornata. Il modello resta indipendente, privo di legami con la presentazione, permettendo così una maggiore manutenibilità, testabilità e chiarezza strutturale del codice.

L’organizzazione di un progetto ASP.NET Core MVC riflette questa struttura. La cartella Views è l’equivalente della cartella Pages di Razor Pages: contiene i file .cshtml organizzati in sottocartelle rappresentanti ciascuna vista. Tuttavia, non esistono file .cshtml.cs. La cartella Controllers contiene i controller, oggetti che implementano i metodi di risposta — detti “azioni” — che restituiscono viste, reindirizzamenti o dati. Infine, la cartella Models ospita le classi che definiscono la logica e la struttura dei dati dell’applicazione.

Sebbene Razor Pages e MVC condividano molte convenzioni e strumenti, la differenza sostanziale risiede nell’organizzazione del codice. Razor Pages tende ad accorpare logica e presentazione nella stessa unità (pagina + file code-behind), mentre MVC mantiene una separazione più netta, facendo del controller il fulcro della logica applicativa.

È importante comprendere che entrambi gli approcci implementano un modello di rendering server-side e possono essere scelti in base alla complessità dell’applicazione, alla preferenza nella gestione delle responsabilità e all’esperienza del team. Razor Pages è più lineare per applicazioni semplici o CRUD-oriented, mentre MVC offre una maggiore flessibilità e controllo su progetti più articolati.

Come migliorare le prestazioni delle applicazioni ASP.NET Core 9 con compressione delle risposte e programmazione asincrona

Per ottimizzare le prestazioni delle applicazioni web, la compressione delle risposte HTTP rappresenta una tecnica essenziale, capace di ridurre drasticamente la dimensione dei dati trasferiti tra server e client, con evidenti benefici in termini di velocità di caricamento e risparmio di banda. In ASP.NET Core 9, questa funzionalità può essere abilitata con poche righe di codice, integrando il pacchetto NuGet Microsoft.AspNetCore.ResponseCompression. L’aggiunta di questo pacchetto al progetto permette di attivare i middleware necessari per comprimere automaticamente le risposte HTTP.

La configurazione avviene nel file Program.cs, dove è fondamentale aggiungere i servizi di compressione al contenitore di Dependency Injection tramite builder.Services.AddResponseCompression(). È possibile specificare provider di compressione come Gzip e Brotli, scegliendo anche il livello di compressione più adatto alle proprie esigenze, ad esempio il livello “Fastest” per un compromesso tra prestazioni e qualità di compressione. È cruciale inoltre abilitare la compressione anche per le richieste HTTPS, dato che la maggior parte delle applicazioni moderne si basa su connessioni sicure.

L’ordine in cui vengono inseriti i middleware nella pipeline di elaborazione delle richieste ha un ruolo determinante: la compressione deve essere applicata prima della memorizzazione nella cache della risposta (response caching). Ciò garantisce che le risposte memorizzate siano già compresse, evitando un inutile sovraccarico e ottimizzando il tempo di risposta per richieste future.

Oltre alla compressione, la gestione asincrona delle operazioni è un’altra pietra miliare per lo sviluppo di applicazioni scalabili e performanti. La programmazione asincrona consente di liberare i thread mentre si attende il completamento di operazioni I/O-bound, come l’accesso al database o chiamate di rete, evitando blocchi e migliorando la capacità di gestione simultanea di molteplici richieste. L’uso combinato delle parole chiave async e await in C# permette di scrivere codice asincrono in modo leggibile e mantenibile, integrandosi perfettamente con le API di ASP.NET Core e librerie come Entity Framework Core.

Per esempio, un metodo asincrono che recupera dati dal database utilizza await per sospendere l’esecuzione senza bloccare il thread fino a che la chiamata a ToListAsync() non ritorna i risultati. Questa strategia migliora sensibilmente la reattività dell’applicazione e la sua capacità di gestire carichi elevati, aspetto cruciale per servizi web ad alta intensità di traffico.

Va sottolineato che, nonostante ASP.NET Core 9 offra strumenti potenti e flessibili, è necessario progettare attentamente la pipeline delle richieste, rispettando le migliori pratiche come l’ordine corretto dei middleware, la scelta appropriata dei livelli di compressione e l’adozione della programmazione asincrona per ogni operazione potenzialmente bloccante. La sinergia di questi elementi determina un’applicazione web più efficiente, scalabile e capace di offrire una migliore esperienza utente.

Oltre alle implementazioni tecniche qui descritte, è fondamentale per lo sviluppatore comprendere l’importanza del bilanciamento tra compressione e carico di lavoro: un livello di compressione troppo elevato può impattare negativamente sulle risorse di CPU, mentre un livello troppo basso riduce i benefici in termini di traffico. Analogamente, la programmazione asincrona non deve essere vista come una soluzione universale, ma come uno strumento da usare in modo mirato, specie nelle operazioni di I/O intensive.

Un ulteriore approfondimento riguarda il monitoraggio e il profiling delle applicazioni in produzione per valutare l’effettiva efficacia delle strategie di compressione e asincronia implementate, e per individuare eventuali colli di bottiglia o comportamenti inattesi che possono compromettere le prestazioni complessive.

Come e perché centralizzare la registrazione del middleware in ASP.NET Core 9?

In ASP.NET Core 9, il middleware rappresenta una componente fondamentale per la gestione del flusso delle richieste HTTP e per arricchire l’applicazione con funzionalità cross-cutting come la limitazione della velocità delle richieste, la gestione degli errori o la misurazione delle prestazioni. Sebbene esistano middleware incorporati con metodi di estensione già pronti, ogni middleware personalizzato deve essere registrato manualmente nel pipeline HTTP, tipicamente tramite metodi come UseMiddleware<>. Tuttavia, inserire più chiamate dirette a UseMiddleware<> o configurazioni disparate nel file principale Program.cs tende a generare codice difficilmente leggibile e complesso da mantenere.

Una buona pratica, adottata diffusamente, consiste nella creazione di metodi di estensione che centralizzino e astraggano la configurazione e la registrazione dei middleware. Questi metodi consentono di organizzare in modo chiaro e ordinato tutte le configurazioni necessarie in classi dedicate, favorendo la separazione delle responsabilità e migliorando la manutenzione dell’applicazione nel tempo.

Per esempio, un metodo di estensione dedicato all’aggiunta di politiche di rate limiting aggrega tutte le configurazioni relative in un unico punto, permettendo di definire diverse strategie come la limitazione a finestra fissa, la finestra mobile o il token bucket. Ogni politica è costruita specificando parametri chiave quali il numero massimo di richieste consentite, la finestra temporale di riferimento, l’ordine di gestione delle code e i limiti di coda, il tutto con la possibilità di identificare il client tramite l’indirizzo IP recuperato dagli header della richiesta. Parallelamente, un metodo di estensione per l’inserimento nel pipeline HTTP consente di aggiungere contemporaneamente middleware personalizzati e il middleware di limitazione della velocità, rendendo il file Program.cs estremamente compatto e leggibile.

L’approccio suggerito risponde anche a esigenze pratiche nell’ambito dello sviluppo collaborativo e del continuo aggiornamento dell’applicazione, poiché separare la logica di configurazione del middleware in estensioni specifiche consente di evitare duplicazioni di codice e facilita modifiche future o l’inclusione di nuove politiche di gestione delle richieste senza intaccare la struttura complessiva del progetto.

È inoltre importante comprendere che la centralizzazione tramite metodi di estensione si integra perfettamente con il modello di dipendenze e l’iniezione dei servizi di ASP.NET Core, permettendo di combinare con efficacia middleware di natura diversa, ad esempio per la gestione globale degli errori, il logging o la misurazione delle performance, senza appesantire il punto di ingresso dell’applicazione.

Infine, in un contesto di applicazioni moderne che devono garantire elevata affidabilità e scalabilità, conoscere e applicare queste best practice permette non solo di mantenere il codice pulito e modulare ma anche di creare architetture facilmente estendibili. La gestione centralizzata e astratta delle configurazioni middleware rappresenta quindi un passo fondamentale verso applicazioni robuste, dove la sicurezza e l’efficienza operativa vengono garantite anche grazie a un controllo granulare sul traffico e sul comportamento delle richieste.

Va considerato che, oltre alla pura registrazione e configurazione, l’implementazione efficace del middleware richiede una profonda comprensione del ciclo di vita delle richieste HTTP e della pipeline di ASP.NET Core. Sapere quando e come posizionare ciascun middleware nella pipeline influenza significativamente il comportamento dell’applicazione, specialmente in relazione all’ordine di esecuzione e alla gestione degli errori.

Inoltre, l’uso dei metodi di estensione facilita l’applicazione di principi SOLID, in particolare la separazione delle responsabilità e l’inversione delle dipendenze, migliorando l’organizzazione generale del codice e la sua testabilità. L’adozione di questa tecnica apre anche la strada a implementazioni future che possono integrare configurazioni dinamiche o basate su ambiente, aumentando l’adattabilità dell’applicazione a scenari diversi senza richiedere modifiche invasive.