Minimal API o controller in ASP.NET Core con .NET 10: quale scegliere?

Il team di una software house di Bergamo apre un nuovo progetto per un cliente del settore logistico. Venti endpoint il primo mese, ottanta al sesto, forse trecento fra due anni. Il capo progetto chiede una cosa semplice: Minimal API o controller? Lo sviluppatore senior risponde "controller, si è sempre fatto così". Quello arrivato da poco ha visto un video in cui un'API completa sta in dodici righe e vuole le Minimal API. La discussione dura tre riunioni e si chiude con una scelta fatta a maggioranza, che dopo sei mesi nessuno ricorda più perché.
Questa guida serve a chiudere la discussione in una riunione sola. In ASP.NET Core con .NET 10 le Minimal API e i controller producono la stessa API con le stesse prestazioni reali: la scelta riguarda l'organizzazione del codice, la squadra che lo manterrà e il prodotto che deve nascere. Qui sotto trovi che cosa cambia sotto il cofano, lo stesso endpoint scritto nei due stili, il peso vero delle prestazioni, come si tiene in ordine un'API minimal quando cresce, dove stanno validazione e filtri, come si testa, come si migra e una regola per decidere.
Se l'accesso ai dati è il tuo prossimo dubbio, leggi anche Dapper o Entity Framework Core: le due scelte si incrociano più di quanto sembri.
Minimal API o controller: quale scegliere per un progetto nuovo con .NET 10?
Per un progetto nuovo con .NET 10 le Minimal API sono la scelta predefinita quando l'API ha logica semplice e una squadra piccola; i controller restano la scelta migliore quando i team sono grandi, i filtri sono molti o il codice esistente è già costruito su di essi. Le prestazioni non decidono: decidono l'organizzazione e le abitudini di chi lavora.
Una Minimal API è un modo di dichiarare gli endpoint HTTP direttamente nel punto di avvio dell'applicazione, con una funzione per ogni rotta. Un controller è una classe che raggruppa più azioni, ciascuna legata a un verbo e a un percorso tramite attributi. Entrambi producono lo stesso risultato: una richiesta HTTP entra, una risposta esce, e in mezzo girano autenticazione, validazione, accesso ai dati.
Che cosa dice Microsoft oggi
I modelli di progetto di .NET 10 propongono le Minimal API come impostazione predefinita per le nuove API web, e la documentazione ufficiale le tratta come la via principale per le nuove API. I controller non sono deprecati e non lo saranno: ASP.NET Core MVC continua a ricevere correzioni e funzioni, perché milioni di applicazioni in produzione ne dipendono. Chi ti dice che i controller sono "il vecchio modo" parla per sentito dire.
Perché la domanda arriva sempre troppo presto
La scelta pesa poco il primo mese e molto il ventiquattresimo. Con venti endpoint qualunque struttura regge. Con trecento endpoint e otto sviluppatori, la struttura decide quanto tempo serve a un nuovo collega per trovare dove aggiungere una rotta, quanto è facile applicare una regola di sicurezza a tutte le rotte di un gruppo e quanto costa spostare una funzione in un altro progetto.
La regola da usare subito
Se il team ha meno di cinque sviluppatori, l'API ha meno di cento endpoint e nessuno ha codice MVC esistente da riutilizzare, parti dalle Minimal API con una struttura per funzionalità. In tutti gli altri casi ragiona sulle sezioni successive prima di decidere. Nessuna delle due strade è una trappola, e la migrazione dall'una all'altra, come vedremo, si fa per gruppi di endpoint.
Che cosa cambia davvero fra Minimal API e controller sotto il cofano?
Sotto il cofano la differenza sta nel modo in cui ASP.NET Core trova e prepara l'endpoint: i controller passano da una macchina più ricca (filtri, associazione dei modelli, convenzioni, rilevamento per riflessione all'avvio), le Minimal API da una più snella dove la funzione viene compilata in un delegato alla prima richiesta.
Entrambi gli stili usano lo stesso sistema di instradamento, lo stesso contenitore di dipendenze e la stessa catena di middleware. Una richiesta attraversa autenticazione, autorizzazione e CORS nello stesso modo, qualunque sia lo stile scelto. Il punto di divergenza arriva dopo l'instradamento.
Il percorso del controller
Il controller viene creato a ogni richiesta dal contenitore di dipendenze, i parametri passano da un sistema di associazione dei modelli molto completo, si eseguono i filtri (autorizzazione, risorsa, azione, eccezione, risultato) e il valore restituito attraversa i formattatori di output. Questa ricchezza è il motivo per cui i controller sono ancora comodi nei progetti con regole trasversali complesse: ogni punto del ciclo ha un aggancio ufficiale.
Il percorso della Minimal API
La funzione che dichiari è già l'endpoint. ASP.NET Core ne analizza la firma una volta, costruisce un delegato e inserisce i parametri direttamente: servizi dal contenitore, valori dalla rotta, dal corpo o dalla query. Non esiste una classe da istanziare a ogni richiesta e non c'è il ciclo dei filtri classici, anche se gli endpoint filter coprono il bisogno di intercettare la chiamata.
Che cosa è cambiato con .NET 10
Con .NET 10 le Minimal API hanno recuperato due lacune storiche. La prima è la validazione integrata: registri AddValidation() nei servizi e le annotazioni sui modelli (Required, Range, StringLength) vengono verificate automaticamente, con risposta di errore standard. La seconda è il supporto nativo a OpenAPI 3.1 nel pacchetto Microsoft.AspNetCore.OpenApi, che prima richiedeva librerie esterne. Chi aveva scartato le Minimal API "perché non validano" deve rifare il conto.
Come si scrive lo stesso endpoint nei due stili?
Lo stesso endpoint "leggi un ordine per numero" occupa circa quindici righe in un controller e cinque in una Minimal API; il risparmio riguarda la cerimonia, non la logica, che resta identica e vive in un servizio dedicato.
Mettiamo i due stili uno accanto all'altro con un caso reale: la lettura di un ordine, con il servizio iniettato e la risposta 404 se l'ordine non esiste. Prima il controller:
// Controllers/OrdiniController.cs: l'azione vive in una classe, la rotta arriva dagli attributi.
[ApiController]
[Route("api/ordini")]
public class OrdiniController(IOrdiniServizio ordini) : ControllerBase
{
[HttpGet("{numero:int}")]
public async Task<ActionResult<OrdineDto>> Leggi(int numero, CancellationToken ct)
{
var ordine = await ordini.TrovaAsync(numero, ct);
if (ordine is null)
{
return NotFound();
}
return Ok(ordine);
}
}Poi la stessa funzione come Minimal API:
// Program.cs: la funzione è l'endpoint, il servizio arriva dal contenitore come parametro.
app.MapGet("/api/ordini/{numero:int}",
async (int numero, IOrdiniServizio ordini, CancellationToken ct) =>
await ordini.TrovaAsync(numero, ct) is { } ordine
? TypedResults.Ok(ordine)
: Results.NotFound());Cosa guadagni e cosa perdi
Il guadagno è evidente: meno righe, nessuna classe da creare, il percorso e il codice stanno nello stesso punto. Lo si paga in leggibilità quando la funzione cresce: una lambda con otto parametri e cinque rami diventa illeggibile in fretta. La cura è la stessa che useresti altrove, ossia spostare la logica in un servizio e lasciare nell'endpoint solo la traduzione fra HTTP e dominio.
Il trucco dei risultati tipizzati
Nell'esempio compare TypedResults. Se dichiari il tipo di ritorno come Results<Ok<OrdineDto>, NotFound>, il compilatore sa quali risposte può produrre l'endpoint, la documentazione OpenAPI si genera da sola e i test controllano il tipo preciso invece di un oggetto generico. Nei controller la stessa cosa si ottiene con ActionResult<T> e con gli attributi ProducesResponseType, più lunghi da scrivere e facili da dimenticare.
Dove sta la logica, in entrambi i casi
Un endpoint, minimal o in un controller, dovrebbe fare tre cose: leggere la richiesta, chiamare un servizio, restituire la risposta. Se dentro c'è un ciclo, un calcolo o una query, la scelta dello stile è l'ultimo dei problemi. Il modo di separare questi strati è raccontato in Dependency injection in C# e .NET 10.
Le Minimal API sono più veloci dei controller?
Le Minimal API sono più veloci dei controller nei benchmark sintetici, con differenze nell'ordine dei microsecondi per richiesta e un avvio un po' più rapido; in un'applicazione reale la differenza si perde dentro il tempo del database, che vale migliaia di volte tanto.
La domanda arriva sempre, e la risposta onesta ha due parti. Sì, un endpoint minimal attraversa meno codice di framework, quindi in un test che restituisce la stringa "ciao" la differenza si misura. No, non è la differenza che conta quando l'endpoint legge da SQL Server, chiama un servizio esterno o serializza una lista di mille righe.
Un conto da fare con i numeri tuoi
Prendi un endpoint reale che risponde in 40 millisecondi. Di quei 40, il database ne occupa 32, la serializzazione 5, l'instradamento e il resto del framework meno di 1. Passare da controller a Minimal API può togliere una frazione di quel millisecondo. Passare da una query senza indice a una con indice toglie 30 millisecondi. Nel caso descritto in query lente con Entity Framework Core una pagina scende da 4,1 secondi a 38 millisecondi cambiando indice e proiezione, e nessuno stile di API ci sarebbe arrivato.
Dove la differenza si vede davvero
Ci sono due casi in cui lo stile pesa. Il primo è l'avvio a freddo: una funzione serverless o un contenitore che parte spesso beneficia di un'applicazione minimal, specie con la compilazione anticipata (AOT), che le Minimal API supportano con un generatore di codice dedicato mentre i controller, basati sulla riflessione, non supportano. Il secondo è l'alta densità: molte istanze piccole sullo stesso nodo, dove ogni megabyte di memoria all'avvio ha un costo.
La regola prestazioni
Misura prima di scegliere. Se il tuo servizio non è né serverless né con migliaia di istanze, ignora le prestazioni dello stile e guarda ai tre fattori che decidono davvero i tempi: le query, le chiamate a servizi esterni, il numero di allocazioni nel codice di conversione. Sono lì che si trovano i secondi, e un'ora con Query Store vale più di un mese di discussioni sul framework.
Come si tiene in ordine un'API minimal quando passa da venti a trecento endpoint?
Si tiene in ordine con una struttura per funzionalità: ogni area dell'applicazione (ordini, clienti, fatture) ha un file con i propri endpoint, raccolti con MapGroup e registrati da un metodo di estensione. Il file di avvio resta di venti righe.
L'errore più frequente nelle Minimal API è lasciare tutto in Program.cs. Funziona con dieci endpoint, diventa un muro di testo con cinquanta e un campo di battaglia per le fusioni di codice con duecento. La cura è prevista dal framework e costa mezza giornata una volta sola.
// Funzionalita/Ordini/OrdiniEndpoints.cs: un file per area, un metodo di registrazione.
public static class OrdiniEndpoints
{
public static IEndpointRouteBuilder MappaOrdini(this IEndpointRouteBuilder app)
{
var gruppo = app.MapGroup("/api/ordini")
.RequireAuthorization()
.WithTags("Ordini");
gruppo.MapGet("/{numero:int}", LeggiOrdine);
gruppo.MapPost("/", CreaOrdine);
gruppo.MapDelete("/{numero:int}", AnnullaOrdine).RequireAuthorization("Amministratore");
return app;
}
private static async Task<Results<Ok<OrdineDto>, NotFound>> LeggiOrdine(
int numero, IOrdiniServizio ordini, CancellationToken ct) =>
await ordini.TrovaAsync(numero, ct) is { } ordine
? TypedResults.Ok(ordine)
: TypedResults.NotFound();
// CreaOrdine e AnnullaOrdine seguono lo stesso schema.
}Cosa fa il gruppo per te
Un gruppo condivide prefisso, autorizzazione, filtri e metadati per tutti gli endpoint che contiene. Una regola come "tutte le rotte di fatturazione richiedono il ruolo Contabilità" si scrive una volta sul gruppo e non in cinquanta punti. È l'equivalente dell'attributo sulla classe del controller, con la differenza che qui la regola si legge insieme alla registrazione.
Metodi con nome, non lambda anonime
Nell'esempio le funzioni hanno un nome (LeggiOrdine) e non sono scritte dentro la chiamata. Il vantaggio è triplo: lo stack di un'eccezione riporta un nome leggibile, il test richiama la funzione direttamente e la lettura del file di registrazione diventa un indice delle rotte. È la regola che separa le Minimal API ben tenute da quelle che il team odia dopo un anno.
Quando passare a un'altra struttura
Se un'area ha molta logica di dominio, un gruppo di endpoint non basta: serve uno strato applicativo dietro, con le regole di business che non dipendono da HTTP. La separazione fra interfaccia, applicazione e dominio è argomento di Domain-Driven Design in C#, e vale uguale con controller e Minimal API.
Dove stanno validazione, filtri e gestione degli errori?
Nei controller validazione e filtri sono automatici e globali per attributo; nelle Minimal API con .NET 10 la validazione si attiva con AddValidation() e i filtri diventano endpoint filter applicati al gruppo. La gestione degli errori vale per entrambi tramite un middleware unico.
È il punto in cui le due strade si somigliano di più dopo .NET 10, e quello in cui i team sbagliano più spesso la migrazione: perdono per strada un controllo che il controller faceva in silenzio.
Validazione: cosa si attiva e cosa no
Un controller con l'attributo ApiController restituisce da solo un errore 400 con i dettagli se il modello non passa le annotazioni. In una Minimal API prima di .NET 10 dovevi scrivere il controllo a mano o usare una libreria; ora una riga di registrazione abilita le annotazioni sui parametri e sui tipi. Per regole complesse (un campo dipende da un altro, un valore va verificato su database) le annotazioni non bastano né in un caso né nell'altro, e conviene una libreria dedicata come FluentValidation, che funziona con entrambi.
Filtri: stesso bisogno, nome diverso
I filtri di azione dei controller hanno un equivalente negli endpoint filter, che avvolgono la chiamata e possono leggere i parametri, bloccare l'esecuzione o modificare il risultato. Sono applicabili a un singolo endpoint o a un intero gruppo. Cambia la granularità: i controller offrono cinque tipi di filtro con un ordine definito, le Minimal API ne offrono uno solo, più semplice, in cui l'ordine è quello di registrazione.
Errori: un solo punto per tutti
In entrambi i casi la strategia giusta è lasciare che le eccezioni risalgano fino a un gestore globale, che le trasforma in risposte standard secondo il formato dei problem details (RFC 9457). Con AddProblemDetails() e UseExceptionHandler() ogni errore non previsto esce con la stessa forma, senza trace dello stack e con un identificativo di correlazione da cercare nei log. Un controllo che non c'è nei test lo scopri in produzione, quindi nella sezione sui test trovi come blindarlo.
Come si documentano e si versionano le API in .NET 10?
In .NET 10 la documentazione OpenAPI 3.1 si genera dal codice con il pacchetto Microsoft.AspNetCore.OpenApi, per Minimal API e controller allo stesso modo; il versionamento si imposta nel percorso (/v1/, /v2/) o con un'intestazione, scegliendo una sola regola per tutta l'API.
Un'API senza contratto pubblicato obbliga ogni client a indovinare. Con la generazione integrata il contratto nasce dal codice e si aggiorna a ogni build, quindi non diverge mai dalla realtà come succede ai documenti scritti a mano.
Documentazione che si genera da sola
Registri AddOpenApi() nei servizi e MapOpenApi() nell'avvio: l'applicazione espone il documento JSON a un indirizzo noto. I risultati tipizzati descrivono da soli le risposte possibili, e un metodo come WithSummary aggiunge la frase che finirà nella documentazione. I controller con ProducesResponseType ottengono lo stesso risultato con più attributi da scrivere.
Versionamento: decidi una volta
Il versionamento serve quando hai clienti esterni che non puoi costringere ad aggiornarsi. La regola più semplice da spiegare e da testare è il numero di versione nel percorso. Un gruppo per versione (MapGroup("/v1"), MapGroup("/v2")) tiene separati i contratti, e la versione vecchia si spegne con una data annunciata, non con una sorpresa. Per le API interne a una sola applicazione, il versionamento di solito è un costo che non ripaga: si cambia il client insieme al server.
Il contratto come test
Salva il documento OpenAPI nel repository e confrontalo a ogni build con quello generato. Una modifica involontaria, come un campo rinominato o uno stato di risposta cambiato, ferma la pipeline prima che arrivi a chi consuma l'API. È il controllo più economico contro le rotture silenziose e dura venti minuti da configurare.
Come si testano Minimal API e controller?
Si testano nello stesso modo: unit test sulla logica di dominio e sul servizio, test di integrazione con WebApplicationFactory che avvia l'intera applicazione in memoria e chiama gli endpoint come farebbe un client vero. Lo stile scelto incide poco.
Il punto debole di entrambi gli stili è lo stesso: i team scrivono molti test su funzioni piccole e pochi sul comportamento HTTP, e scoprono in produzione che la rotta era sbagliata, il ruolo mancava o l'errore usciva con lo stato 500 invece di 404.
// Test/OrdiniApiTests.cs: avvia l'API in memoria e chiama l'endpoint come un client.
public class OrdiniApiTests(WebApplicationFactory<Program> fabbrica)
: IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task Ordine_inesistente_restituisce_404()
{
var client = fabbrica.CreateClient();
var risposta = await client.GetAsync("/api/ordini/999999");
Assert.Equal(HttpStatusCode.NotFound, risposta.StatusCode);
}
}Perché le Minimal API si testano anche meglio
Con funzioni statiche dal nome chiaro puoi richiamare l'endpoint direttamente nei test di unità, passando un servizio finto, e verificare il risultato tipizzato (Results<Ok<OrdineDto>, NotFound>) senza avviare niente. Nei controller devi creare la classe e riempire il contesto HTTP se l'azione lo usa. È un vantaggio reale, ma piccolo: il test importante resta quello che attraversa l'intera pipeline.
Tre test da avere sempre
Un test per ogni gruppo di endpoint che controlla l'autorizzazione (chiamata senza token: 401; con ruolo sbagliato: 403). Un test per un errore non previsto che verifica la forma della risposta. Un test per la validazione con un corpo volutamente errato. Sono nove righe l'uno e coprono le tre classi di errore che nella mia esperienza arrivano più spesso in produzione dopo una migrazione di stile.
L'API come contratto
Con OpenAPI generato dal codice, il test finale è confrontare il documento prodotto con quello versionato nel repository: una modifica involontaria al contratto fa fallire la build prima che il cliente se ne accorga. Funziona con entrambi gli stili.
Come si migra da controller a Minimal API senza fermare il team?
Si migra per gruppi, non in blocco: le due modalità convivono nella stessa applicazione. Si sceglie un'area ad alta modifica, si riscrivono i suoi endpoint come Minimal API dietro gli stessi percorsi, si verifica con i test di integrazione e si cancella il controller. Un'area alla volta, nessuna riscrittura generale.
Il dato che rassicura è che ASP.NET Core accetta entrambi gli stili nello stesso progetto: puoi avere app.MapControllers() e app.MapGroup(...) nel medesimo avvio. La tecnica è semplice. Le insidie sono le regole nascoste, quelle che il controller applicava senza che nessuno se ne ricordasse.
L'elenco da controllare prima di toccare un controller
Filtri globali registrati in AddControllers, attributi di autorizzazione sulla classe, formattatori personalizzati, convenzioni di rotta, comportamento sugli errori di validazione, intestazioni aggiunte da un filtro di risultato. Ognuna di queste voci va convertita a mano, perché passare a una Minimal API non le porta con sé. Il test di integrazione scritto prima della migrazione, sullo stesso percorso, è la rete di sicurezza.
Quando conviene non migrare
Un'applicazione stabile, con trenta controller e una squadra che li conosce, non guadagna niente da una migrazione di stile. Il costo è reale (sei-dieci giorni persona per un'API di media dimensione), il guadagno in prestazioni è trascurabile e il rischio di regressione non è zero. Se il motivo è "fa più moderno", il budget va speso altrove, per esempio nell'allineamento a .NET 10 raccontato in dal .NET Framework a .NET 10.
Quando conviene
Conviene quando stai già riscrivendo l'area per altri motivi, quando i controller sono diventati contenitori di duemila righe, o quando il servizio deve partire in tempi brevi in un contenitore. In quei casi il costo marginale della conversione è basso e il codice che ne esce è più corto e più facile da leggere.
Quale scegliere in base alla tua squadra e al tuo prodotto?
Scegli le Minimal API se il team è piccolo, l'API è giovane e l'avvio rapido conta; scegli i controller se i team sono più di uno, il codice MVC esiste già o servono molti filtri con ordine preciso. Con un'applicazione mista, usa entrambi senza imbarazzo.
La tabella sotto riassume la decisione. Come griglia serve a portare la discussione dal gusto personale ai fatti, e va letta con buon senso.

| Situazione | Scelta consigliata | Motivo |
|---|---|---|
| Nuova API, team fino a 5 persone | Minimal API | Meno cerimonia, struttura per funzionalità |
| Nuova API, più team sullo stesso prodotto | Controller o Minimal con regole di cartella | Convenzioni note a tutti, filtri ordinati |
| Applicazione MVC esistente da estendere | Controller | Coerenza, nessun costo di migrazione |
| Microservizio in contenitore, avvio rapido | Minimal API | Avvio più veloce, AOT possibile |
| Funzione serverless | Minimal API | Avvio a freddo contenuto |
| Molti filtri con ordine preciso | Controller | Cinque tipi di filtro con ordine definito |
Il criterio che pesa più di tutti
Il costo vero di una scelta sbagliata si paga in persone e in tempo di inserimento. Se il team ha cinque anni di esperienza con i controller e nessuna con le Minimal API, il primo trimestre sarà più lento. Se i nuovi assunti arrivano da progetti recenti, il contrario. Un criterio sensato è quello della squadra che manterrà il codice fra due anni, non quella che lo scrive oggi.
Mescolare è legittimo
Un'API può avere i controller per le aree consolidate e le Minimal API per quelle nuove, a patto di fissare per iscritto la regola (area nuova: minimal, area esistente: controller) e di documentare dove stanno i filtri globali. Un team che mescola senza regola finisce con due dialetti e il doppio del tempo di inserimento.
La decisione in una riunione
Porta tre dati: dimensione del team, quantità di codice MVC esistente, contesto di esecuzione (server tradizionale, contenitore, serverless). Applica la tabella, scrivi la regola in una pagina del repository e chiudi. Le discussioni che durano tre riunioni nascono quando manca uno di quei tre dati.
Che cosa chiedere prima di decidere, e quanto costa sbagliare?
Prima di decidere vanno chieste cinque cose al team: quante persone toccano l'API, quanti endpoint esistono, quali regole trasversali servono, dove gira il servizio, chi lo manterrà fra due anni. Sbagliare costa da sei a dieci giorni persona di migrazione, circa 3.600-6.000 euro a 600 euro al giorno.
Il conto sembra piccolo finché non lo moltiplichi per le decisioni analoghe che un progetto accumula: stile dell'API, accesso ai dati, struttura dei progetti, gestione degli errori. Dieci scelte mediocri fatte in fretta costano più di una scelta sbagliata discussa a lungo.
Le cinque domande
Quante persone modificheranno l'API nei prossimi dodici mesi? Quanti endpoint prevediamo a due anni? Quali regole devono valere per tutti gli endpoint (autenticazione, registrazione, limiti di frequenza)? Dove gira il servizio, e quanto conta il tempo di avvio? Chi manterrà il codice quando il team attuale sarà passato ad altro? Le prime tre risposte decidono lo stile, le ultime due decidono la struttura delle cartelle.
Che cosa non conviene fare
Non conviene scegliere per moda, né per l'ultimo video visto, né perché "lo fa Microsoft". Non conviene nemmeno rimandare: un progetto che parte senza una regola scritta ne ha tre, una per sviluppatore. Il tempo speso a scrivere una pagina di convenzioni all'inizio ti restituisce, nel primo anno, qualche decina di ore di revisioni di codice più rapide.
Il collegamento con il resto dell'architettura
Stile degli endpoint, accesso ai dati e suddivisione del dominio si decidono insieme, perché si influenzano. Una scelta fatta bene in uno strato non compensa una scelta trascurata negli altri. Se stai impostando un sistema che deve vivere dieci anni, la visione d'insieme vale più di qualunque dettaglio del framework, e per costruirla serve un percorso di formazione strutturato, non una raccolta di video.
Domande che ricevo più spesso su Minimal API e controller
Le domande ricorrenti sono sempre le stesse: se i controller siano deprecati, se si possano mescolare, se le Minimal API reggano progetti grandi e se convenga migrare. Le risposte sono no, sì, sì e di solito no.
I controller non sono deprecati, e Microsoft continua a lavorarci. Le due modalità convivono nella stessa applicazione senza problemi tecnici, anche nello stesso gruppo di rotte. Le Minimal API reggono progetti con centinaia di endpoint se organizzate per funzionalità con MapGroup; a fissare il limite è la disciplina del team. Migrare conviene raramente solo per stile, e quasi sempre quando riscrivi comunque l'area.
Una scena da evitare
Ho visto più di una squadra passare un trimestre a riscrivere i controller in Minimal API "per prepararsi al futuro" e consegnare lo stesso identico prodotto con lo stesso identico numero di difetti. Il futuro di un'API dipende da quanto bene sono tenuti i suoi confini, i suoi test e il suo accesso ai dati. Il framework è l'ultimo anello della catena, e le regole per tenerla sana valgono qualunque stile tu abbia scelto.
Il passo successivo
Se oggi hai un'API in produzione, apri il repository e conta tre cose: i controller sopra le 400 righe, gli endpoint senza test di integrazione, le regole di sicurezza scritte endpoint per endpoint invece che per gruppo. Sono le tre misure che dicono quanto la tua API è sana, e nessuna dipende dallo stile scelto.
Il team di Bergamo ha chiuso la discussione in una riunione. Minimal API con struttura per funzionalità per le aree nuove, controller lasciati dove sono, una pagina di convenzioni nel repository e un test di integrazione per ogni gruppo di rotte. Nessuna riscrittura, nessun trimestre perso, e il collega arrivato da poco ha scritto il suo primo endpoint in un pomeriggio.
Se vuoi imparare a prendere decisioni di questo tipo con il metodo giusto, a progettare API che reggono anni di crescita e a guidare il confronto con la tua squadra, nel corso C# di Sviluppatore Migliore lavoriamo su progetti reali con il mio feedback diretto, in lezioni registrate e sessioni dal vivo compatibili con il lavoro. Porta la tua API: la guardiamo insieme.
La tua azienda ha bisogno di software su misura o di AI nel team?
Progettiamo e sviluppiamo software su misura, modernizziamo i sistemi vecchi e formiamo i tuoi sviluppatori a lavorare con l'AI. Raccontaci il problema: ricevi una risposta concreta entro un giorno lavorativo.
Domande frequenti
Per un progetto nuovo con team piccolo e logica semplice conviene la Minimal API, che .NET 10 propone come impostazione predefinita. I controller restano la scelta migliore con più team, molti filtri o codice MVC esistente. Le prestazioni reali sono praticamente uguali, quindi decidono organizzazione e abitudini della squadra.
No. ASP.NET Core MVC riceve ancora correzioni e funzioni, e milioni di applicazioni in produzione ne dipendono. Microsoft promuove le Minimal API per i progetti nuovi, ma non ha previsto la rimozione dei controller. Le due modalità possono convivere nella stessa applicazione.
Nei benchmark sintetici sì, di microsecondi per richiesta, con avvio più rapido e supporto alla compilazione anticipata (AOT). In un'applicazione reale la differenza sparisce dentro il tempo del database, che vale migliaia di volte tanto. Conviene misurare query e chiamate esterne prima dello stile.
Sì. Con .NET 10 si registra AddValidation() nei servizi e le annotazioni sui modelli (Required, Range, StringLength) vengono verificate automaticamente, con una risposta di errore standard. Per regole complesse si usa una libreria come FluentValidation, che funziona con entrambi gli stili.
Sì. ASP.NET Core accetta MapControllers e MapGroup nello stesso avvio. È utile per migrare un'area alla volta, a patto di scrivere la regola (area nuova minimal, area esistente controller) e di documentare dove stanno i filtri globali.
Con una struttura per funzionalità: un file per area con i suoi endpoint raccolti da MapGroup, registrati da un metodo di estensione. Le funzioni hanno un nome e la logica sta in un servizio. Così Program.cs resta di una ventina di righe anche con trecento endpoint.
Per un'API di media dimensione servono da sei a dieci giorni persona, circa 3.600-6.000 euro con una tariffa di riferimento di 600 euro al giorno. Conviene solo se riscrivi comunque l'area o se i controller sono diventati ingestibili; per sola moda il budget va speso altrove.
