Migrazione .NET Framework: la guida 2026 per i CTO
Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 27 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Ha guidato progetti enterprise, formato centinaia di sviluppatori e aiutato aziende di ogni dimensione a semplificare la complessità trasformando il software in guadagni per il business.

Se hai un'applicazione su .NET Framework 4.8 che gira in produzione e funziona, la domanda giusta non è "conviene migrare". È "quanto mi costa ogni mese che aspetto". Questa guida ti dà i numeri per rispondere: che cosa cambia davvero fra .NET Framework e .NET 10, quali pezzi del tuo codice non passeranno mai dall'altra parte, quanto dura una migrazione reale, quanto costa e con quale metodo si fa senza fermare la produzione nemmeno un giorno.

Non è una guida per sviluppatori curiosi. È scritta per chi deve decidere: il CTO che deve mettere una cifra in un budget, l'imprenditore che ha un gestionale su cui gira il fatturato e non può permettersi tre mesi di blocco, il responsabile IT che ha già ricevuto due preventivi con una differenza del 300% fra loro e non sa quale dei due è serio.

La migrazione .NET Framework è il progetto di modernizzazione più frequente nelle aziende italiane con software sviluppato fra il 2008 e il 2018. È anche quello che viene rimandato più a lungo, perché l'applicazione funziona e il rischio percepito del cambiamento è più alto del dolore percepito dello status quo. È esattamente questo calcolo che va rifatto con i numeri veri.

Perché la migrazione .NET Framework torna urgente proprio adesso

Per anni la risposta onesta alla domanda "devo migrare da .NET Framework" è stata: non necessariamente, non subito. .NET Framework 4.8 è un componente di Windows e resta supportato finché è supportata la versione di Windows su cui gira. Non c'è una data di spegnimento che ti obbliga a muoverti domani. Questa è la ragione per cui migliaia di applicazioni aziendali italiane sono ancora lì.

Quello che è cambiato nel 2026 non è il supporto di .NET Framework. Sono tre cose intorno.

Il framework è congelato, l'ecosistema no

.NET Framework 4.8 riceve correzioni di sicurezza, non funzionalità nuove. Da anni. Nel frattempo l'intero ecosistema si è spostato: le librerie NuGet che usi rilasciano versioni che richiedono .NET 8 o superiore e smettono di pubblicare il target net48, gli SDK dei fornitori cloud fanno lo stesso, gli strumenti di sviluppo assistito dall'intelligenza artificiale sono addestrati e ottimizzati sulle API moderne. Non è un blocco improvviso: è un'erosione. Ogni trimestre la lista delle cose che non puoi aggiornare si allunga di un pezzo.

Il segnale operativo da guardare non è la data di fine supporto del framework. È il momento in cui, per aggiornare una libreria che ti serve per una richiesta del cliente, scopri che l'ultima versione compatibile con net48 è di due anni fa e ha una vulnerabilità aperta. Quando arriva quel momento, la migrazione non è più un progetto pianificabile: è un'emergenza.

Le finestre di supporto della piattaforma nuova

Chi migra oggi deve sapere dove atterrare. Il calendario delle versioni moderne è rigido e prevedibile: le versioni LTS (Long Term Support) hanno tre anni di supporto, quelle STS (Standard Term Support) diciotto mesi. .NET 8, la LTS che ha assorbito la maggior parte delle migrazioni fra il 2024 e il 2025, esce di supporto a novembre 2026. .NET 10, rilasciata a novembre 2025, è la LTS su cui atterrare oggi.

La conseguenza pratica è che una migrazione avviata nel 2026 deve puntare a .NET 10, non a .NET 8. Sembra ovvio, e invece è uno degli errori più costosi che vediamo nei preventivi: il fornitore riusa il piano di un progetto del 2024, ti porta su .NET 8, e nel giro di pochi mesi ti trovi a dover rifare un aggiornamento di runtime su un'applicazione appena migrata, con il team che ha già smontato il ponteggio.

Il costo del cloud che paghi senza saperlo

Questa è la voce che convince i decisori più di ogni argomento tecnico. .NET Framework gira solo su Windows. Se la tua applicazione è in Azure o in AWS, stai pagando istanze Windows, e la licenza Windows è dentro il prezzo orario della macchina. La stessa applicazione su .NET 10 gira in un container Linux, che costa meno a parità di risorse, si avvia in una frazione del tempo e può scendere a zero repliche quando nessuno la usa.

Su un'applicazione interna usata otto ore al giorno in giorni feriali, la differenza fra una macchina Windows accesa sempre e un container Linux che scala a zero non è una percentuale: è un ordine di grandezza. È il tipo di risparmio che ripaga una parte consistente del progetto di migrazione nel primo anno, e che nessuno mette nel business case perché il costo del cloud sta su un'altra riga di bilancio rispetto al costo dello sviluppo.

Linea del tempo del supporto di .NET Framework 4.8, .NET 8 e .NET 10 con le date di fine supporto

Che cosa cambia davvero fra .NET Framework 4.8 e .NET 10

La confusione più diffusa fra chi decide senza scrivere codice è pensare che si tratti di un aggiornamento di versione, come passare da 4.6 a 4.8. Non lo è. .NET Framework e .NET sono due prodotti diversi che condividono il linguaggio e una grossa fetta della libreria standard, ma hanno runtime, modello di distribuzione e modello di estensione diversi.

Il runtime non è più parte del sistema operativo

.NET Framework è installato in Windows. Una macchina ne ha una versione sola, condivisa da tutte le applicazioni. Aggiornarlo significa toccare il sistema operativo, e per questo su un server con dieci applicazioni nessuno lo aggiorna mai.

.NET moderno funziona al contrario: il runtime viaggia con l'applicazione. Puoi avere sulla stessa macchina un servizio su .NET 8 e uno su .NET 10, aggiornare uno senza toccare l'altro, e distribuire l'applicazione come un pacchetto autosufficiente che non ha bisogno di nulla di preinstallato. Questo cambia le regole del rilascio più di qualsiasi differenza di API: la paura di aggiornare il framework, che è la ragione per cui il tuo software è fermo dal 2017, semplicemente scompare.

Il file di progetto e la gestione dei pacchetti

Il vecchio formato csproj elencava ogni singolo file del progetto. Il formato SDK-style ne elenca zero: include tutto quello che trova nella cartella. Un csproj di un progetto reale passa da settecento righe a quindici. I riferimenti ai pacchetti passano da packages.config a PackageReference, con risoluzione transitiva delle dipendenze: non devi più elencare a mano le dipendenze delle dipendenze.

Questo passaggio merita attenzione perché è la parte di migrazione che puoi fare oggi, su .NET Framework, senza cambiare runtime e senza rischio. Un progetto net48 in formato SDK-style compila e gira esattamente come prima. Ne parliamo nel piano operativo: è la prima fase, ed è quella che rende possibili tutte le altre.

La configurazione

web.config e app.config con le loro trasformazioni per ambiente lasciano il posto a appsettings.json e al sistema di configurazione a livelli sovrapposti: file base, file per ambiente, variabili d'ambiente, segreti esterni. Esiste un pacchetto ponte, System.Configuration.ConfigurationManager, che permette di continuare a leggere i vecchi file durante la transizione. Va usato come ponte, non come destinazione: lasciare la configurazione nel vecchio modello significa rinunciare al motivo principale per cui si migra, cioè poter mettere i segreti fuori dal codice e cambiare configurazione senza ricompilare.

La dependency injection e il ciclo di vita

In .NET Framework l'iniezione delle dipendenze era una scelta: Unity, Castle Windsor, Autofac, oppure niente e tutto statico. In .NET moderno è parte della piattaforma, e tutto il modello di hosting (avvio, arresto, servizi in background, controllo dello stato di salute) è costruito intorno a essa. Questo è un vantaggio enorme sul lungo periodo ed è anche il punto in cui un codice vecchio pieno di singleton statici e di HttpContext.Current letto dentro la logica di business fa più resistenza. Ne riparliamo nella sezione sulle trappole.

L'inventario onesto: che cosa si rompe quando migri

Questa è la sezione che chiedo sempre di leggere per prima a chi mi porta un preventivo di migrazione da valutare. Un preventivo serio elenca queste voci una per una e dice quale c'è nel tuo codice. Un preventivo che parla solo di "aggiornamento a .NET 10" e dà un prezzo a corpo non è stato preceduto da un'analisi.

Quello che non esiste dall'altra parte

ASP.NET Web Forms. Non c'è, non ci sarà, non esiste un percorso di conversione automatica. Il modello a postback con lo stato della vista è incompatibile con il modello di richiesta di ASP.NET Core. Un'applicazione Web Forms va riscritta nella parte di interfaccia. Il percorso naturale oggi è Blazor, che permette di mantenere un modello a componenti con stato lato server e riusa il codice C# esistente, ed è per questo il percorso più discusso nella migrazione a Blazor delle applicazioni aziendali.

WCF lato server. Il servizio WCF che espone endpoint SOAP non ha un porting ufficiale Microsoft. Esiste CoreWCF, progetto della .NET Foundation, che copre una parte significativa degli scenari e permette di riusare i contratti esistenti: è una strada reale e la usiamo, ma copre un sottoinsieme, e le configurazioni con binding esotici, transazioni distribuite o sicurezza a livello di messaggio vanno verificate una per una prima di promettere una data. Il lato client invece funziona bene: i pacchetti System.ServiceModel.* permettono di continuare a consumare servizi SOAP di terzi da .NET moderno.

.NET Remoting. Rimosso. Se lo hai, va sostituito, tipicamente con gRPC o con chiamate HTTP. Non è un lavoro difficile ma è un lavoro, e va contato.

AppDomain. Il modello di isolamento a domini applicativi non c'è. Se il tuo software carica plugin a caldo o esegue codice cliente in un dominio separato, quel meccanismo va riscritto su AssemblyLoadContext, che risolve il caricamento e lo scaricamento ma non offre l'isolamento di sicurezza. È una delle poche voci che, se presente, può cambiare la strategia di tutto il progetto.

Servizi web ASMX. Non supportati. Vanno riesposti come API REST o come servizi CoreWCF.

Code Access Security e Security Transparency. Rimossi. Il modello di sicurezza è cambiato radicalmente: nel software gestionale tipico non li usa nessuno, ma nel software che esegue codice di terze parti sono spesso il cuore dell'architettura.

Windows Workflow Foundation. Non nel prodotto Microsoft. Esiste CoreWF, comunitario. Se hai processi di business modellati in WF, questa voce da sola vale una valutazione dedicata.

Quello che passa, ma con attenzione

Entity Framework 6. Gira su .NET moderno. Puoi migrare l'applicazione tenendo EF6 e rimandare il passaggio a EF Core, ed è quasi sempre la scelta giusta: fare le due cose insieme raddoppia la superficie di rischio. Va però messo in conto che EF6 non riceve funzionalità nuove e che alcuni provider di database di terze parti non hanno versioni compatibili.

WPF e Windows Forms. Supportati su .NET, solo su Windows. Il porting di un'applicazione desktop è generalmente il caso più semplice: cambia il file di progetto, cambia il modo di distribuire, e la maggior parte del codice compila. Le complicazioni arrivano dai controlli di terze parti e dalle interoperabilità COM.

System.Drawing. Fuori da Windows non funziona più. Se generi PDF, miniature o grafici con System.Drawing e vuoi finire su container Linux, quella parte va portata su un'altra libreria. È una delle voci più sottovalutate, perché sta nascosta in un modulo di stampa che nessuno guarda da anni.

BinaryFormatter. Rimosso dalle versioni recenti di .NET. Se hai cache, sessioni o file persistiti serializzati con BinaryFormatter, ti serve un formato di serializzazione nuovo e una strategia per leggere i dati vecchi già scritti. È il tipo di dettaglio che non emerge in analisi e ti esplode in produzione.

HttpContext.Current. Nel modello nuovo non esiste una variabile statica ambientale con il contesto della richiesta. Se il tuo codice di business la legge in profondità, la migrazione tocca strati che pensavi di non dover toccare. È il singolo indicatore più affidabile della difficoltà reale di una migrazione web: contare quante volte compare HttpContext.Current fuori dai controller dice più di qualsiasi stima a occhio.

Il pacchetto che salva metà del lavoro

Esiste un pacchetto, Microsoft.Windows.Compatibility, che riporta su .NET moderno diverse migliaia di API di .NET Framework specifiche di Windows: registro di sistema, WMI, permessi ACL, servizi Windows, code MSMQ, disegno. Non risolve i casi elencati sopra, ma abbatte drasticamente il numero di errori di compilazione al primo tentativo, e permette di distinguere subito i problemi veri da quelli che si risolvono con una riga nel file di progetto.

Schema delle tecnologie .NET Framework che passano a .NET moderno e di quelle che vanno riscritte

Quanto dura e quanto costa una migrazione .NET Framework

Ogni volta che do dei numeri su questo argomento aggiungo lo stesso avvertimento: il costo di una migrazione non dipende dalla dimensione del codice, dipende dalla lista della sezione precedente. Due applicazioni da centomila righe possono costare l'una un decimo dell'altra. Detto questo, gli ordini di grandezza esistono e servono a capire se un preventivo è nel mondo reale.

I tre profili che coprono la maggior parte dei casi

Profilo semplice: libreria o servizio senza interfaccia. Un servizio Windows, una libreria di dominio, un processo batch, una API su Web API 2 senza dipendenze bloccanti. Conversione del progetto, aggiornamento dei pacchetti, sostituzione della configurazione, test. Da poche giornate a tre settimane di lavoro effettivo. È il caso in cui gli strumenti automatici fanno davvero la maggior parte del lavoro.

Profilo medio: applicazione web ASP.NET MVC o Web API con database. La stessa lista più il modello di hosting, l'autenticazione, la gestione della sessione, i filtri, i moduli HTTP, i job pianificati. Da uno a tre mesi di lavoro effettivo per applicazione, con un team di due persone. È il caso più frequente e quello in cui il metodo incrementale conviene sempre.

Profilo pesante: Web Forms, WCF server, o desktop con forte interoperabilità. Qui non si parla di migrazione ma di riscrittura di uno o più strati. Da sei mesi a due anni, e il progetto va gestito come una modernizzazione a tappe con rilascio progressivo, non come una migrazione. È il territorio che descrivo nella guida sulla modernizzazione del software legacy.

Le voci che i preventivi dimenticano

Il costo dichiarato di una migrazione è quasi sempre il costo della conversione del codice. Il costo reale contiene almeno altre quattro voci.

La prima è il collaudo. Se l'applicazione non ha test automatici, e nella maggior parte dei casi non li ha, il collaudo è manuale e va fatto due volte: una prima della migrazione per sapere che cosa fa il sistema oggi, una dopo per verificare che faccia la stessa cosa. Su un gestionale con quaranta schermate questa voce da sola vale settimane.

La seconda è la pipeline di rilascio. La nuova applicazione si distribuisce in un altro modo. Compilazione, pubblicazione, container, configurazione, segreti, monitoraggio: tutto va rifatto. Sono giornate che non sono nel preventivo perché non sono "sviluppo".

La terza è la doppia manutenzione. Durante il progetto il software vecchio continua a ricevere richieste di modifica dai clienti. Ogni modifica va portata anche sul ramo nuovo. È il costo nascosto che rende ogni migrazione lunga più costosa di due migrazioni brevi, ed è la ragione tecnica per cui il metodo incrementale batte quasi sempre la riscrittura.

La quarta è la formazione. Un team che ha scritto .NET Framework per dieci anni non produce codice .NET moderno idiomatico al primo tentativo. Se nessuno in azienda ha già fatto questo percorso, i primi due mesi hanno una produttività ridotta, oppure serve qualcuno che abbia già fatto il percorso e faccia da guida.

Che cosa contare dall'altra parte della bilancia

Il calcolo va chiuso con i risparmi, altrimenti la decisione la si prende sempre guardando solo il costo. Le voci misurabili sono: il costo infrastrutturale (licenze Windows, macchine sovradimensionate perché il vecchio runtime consuma di più, ambienti che non scalano a zero), il costo del tempo sprecato dagli sviluppatori su una piattaforma ferma, il costo delle vulnerabilità che non puoi chiudere, e il costo del rischio di dover fare tutto d'urgenza fra due anni. Ho messo per iscritto il metodo per quantificarle nell'analisi su quanto ti costa davvero il software legacy.

Le quattro strategie possibili e quando sceglierle

Esistono quattro modi di affrontare una migrazione .NET Framework. Sceglierne uno per gusto invece che per caratteristiche del sistema è l'errore strategico che costa di più.

Riscrittura da zero

Si butta e si rifà. Ha un fascino irresistibile per gli sviluppatori e un tasso di fallimento che tutti conoscono. È giustificata in un caso solo: quando il modello di dominio del software vecchio è sbagliato, non quando è vecchio il codice. Se il software fa la cosa giusta in un modo antiquato, riscriverlo significa pagare due volte per riottenere quello che già hai, e reintrodurre da capo tutte le regole di eccezione che vent'anni di clienti hanno inciso nel codice e che nessuno ha documentato.

La regola che uso: se il committente non sa elencare almeno tre comportamenti del sistema attuale che vuole cambiare, non è un progetto di riscrittura, è un progetto di migrazione travestito.

Big bang

Si migra tutto, si collauda, si rilascia in un fine settimana. Funziona, ed è la scelta giusta per il profilo semplice: un servizio, una libreria, una API piccola. Su un'applicazione media diventa pericoloso, perché concentra tutto il rischio in un solo momento e non offre una via di ritorno che non sia il ripristino completo. Su un'applicazione grande è la strategia con cui si producono i progetti che durano diciotto mesi e non vanno mai in produzione.

Strangler fig: la migrazione incrementale

Il sistema nuovo cresce intorno al vecchio e ne assorbe una funzionalità alla volta, finché il vecchio non ha più niente da fare e si spegne. Un componente davanti a tutto smista le richieste: quelle già migrate vanno all'applicazione nuova, tutte le altre continuano ad andare alla vecchia. L'utente non se ne accorge, l'indirizzo non cambia, e a ogni rilascio una fetta del sistema è definitivamente al sicuro.

È la strategia giusta per la stragrande maggioranza delle applicazioni web aziendali, e nella prossima sezione spiego con quali strumenti si realizza concretamente su .NET.

Convivenza a lungo termine

Si migra la parte che ha valore muovere e si lascia il resto dov'è, collegando i due mondi con delle API. Non è una sconfitta: è la scelta corretta quando una parte del sistema è stabile, non evolve, non ha problemi di sicurezza e non impedisce nulla. Un modulo di stampa etichette scritto nel 2011 che funziona e che nessuno tocca da otto anni non è debito tecnico. È infrastruttura ammortizzata.

Il metodo incrementale: strangler fig con YARP e gli adattatori System.Web

Fino a qualche anno fa la migrazione incrementale di un'applicazione ASP.NET era teoricamente giusta e praticamente impossibile: la sessione, l'autenticazione e il contesto della richiesta non si potevano condividere fra il mondo vecchio e quello nuovo, quindi l'utente veniva sloggato ogni volta che attraversava il confine. Oggi non è più così, e questo è il singolo cambiamento che ha reso le migrazioni .NET Framework un progetto gestibile invece che un salto nel buio.

I due pezzi che rendono possibile la convivenza

Il primo è YARP, il proxy inverso di Microsoft scritto in .NET. Si mette davanti alle due applicazioni e decide per ogni richiesta dove mandarla, in base al percorso. È configurabile a caldo, quindi spostare una rotta dal vecchio al nuovo è un cambio di configurazione, non un rilascio.

Il secondo sono gli adattatori System.Web (Microsoft.AspNetCore.SystemWebAdapters). Sono la parte che risolve il problema vero: permettono al codice scritto contro System.Web di funzionare dentro ASP.NET Core, e permettono alle due applicazioni di condividere la sessione e l'identità dell'utente durante la transizione. Concretamente: l'utente entra dall'applicazione vecchia, clicca su una voce di menu la cui rotta è già stata migrata, atterra sull'applicazione nuova e resta autenticato con la sua sessione. Non se ne accorge.

Come si lavora, in pratica

Si crea un progetto ASP.NET Core nuovo accanto a quello esistente. Si mette YARP davanti, configurato per mandare tutto all'applicazione vecchia: al primo giorno il comportamento del sistema è identico a prima, e questo è il punto. Da lì si migra una rotta alla volta, in ordine di rischio crescente: prima le pagine di sola lettura, poi quelle che scrivono, per ultime quelle con logica delicata come i pagamenti o la fatturazione.

Ogni rotta migrata è un rilascio piccolo, reversibile con un cambio di configurazione, verificabile in produzione su un traffico reale. Il codice di dominio nel frattempo viene estratto dalla vecchia applicazione in librerie che compilano per entrambi i target (net48 e net10.0), così una sola versione del codice serve i due mondi e la doppia manutenzione sparisce quasi del tutto.

Perché questo metodo costa meno anche se sembra più lungo

Sulla carta la migrazione incrementale ha più lavoro: il proxy, i target doppi, gli adattatori. Nella realtà costa meno per tre ragioni misurabili. La prima è che elimina la doppia manutenzione, che su un progetto di sei mesi vale più del costo dell'impalcatura. La seconda è che sposta il collaudo dentro il progetto invece che alla fine: ogni rotta viene verificata in produzione, su traffico vero, quando il team ha ancora quel pezzo in testa. La terza è che il valore arriva subito: dopo un mese hai già una parte del sistema sulla piattaforma nuova, non un ramo di sviluppo che non ha mai visto un utente.

C'è anche una ragione non tecnica, e in azienda pesa più delle altre. Un progetto che rilascia ogni due settimane mantiene la fiducia della direzione. Un progetto che chiede sei mesi e poi rilascia una volta sola la perde alla prima slittata, e i progetti di migrazione slittano sempre almeno una volta.

Schema della migrazione incrementale con YARP che smista le richieste fra applicazione .NET Framework e ASP.NET Core

Il piano operativo in sei fasi

Questo è il piano che uso, nell'ordine in cui lo uso. L'ordine non è estetico: ogni fase riduce il rischio della successiva, e saltarne una si paga sempre più avanti.

Fase 1: Inventario e analisi di portabilità (1-2 settimane)

Prima di toccare qualsiasi cosa serve sapere che cosa c'è. Si esegue l'Upgrade Assistant di Microsoft in modalità di sola analisi su tutta la soluzione, si estrae la lista dei pacchetti NuGet con la loro compatibilità, si cercano nel codice le occorrenze delle voci bloccanti della sezione precedente (HttpContext.Current, AppDomain, BinaryFormatter, System.Drawing, riferimenti COM, servizi WCF), e si censiscono le integrazioni esterne.

Il risultato di questa fase non è tecnico: è una tabella con quante ore vale ogni voce. Se un fornitore ti dà un preventivo senza aver fatto questa fase, ti sta dando un numero inventato, e la cosa si scoprirà al terzo mese.

Fase 2: Conversione al formato SDK, restando su .NET Framework (1-3 settimane)

Questa è la fase che quasi nessuno fa per prima ed è quella che rende tutto il resto possibile. Si convertono i progetti al formato SDK-style e i pacchetti da packages.config a PackageReference, continuando a compilare per net48. L'applicazione resta identica, gira come prima, si rilascia come prima. Il rischio è vicino a zero e si può fare a piccoli passi in mezzo al normale lavoro.

Serve perché sblocca due cose: il multi-targeting (compilare lo stesso progetto per due runtime) e gli strumenti moderni. Senza questa fase, ogni tentativo successivo si impantana in problemi di build che non c'entrano niente con la migrazione.

Fase 3: Estrazione del dominio in librerie multi-target (2-6 settimane)

Si separa il codice che non dipende dal web e da Windows (entità, regole, servizi di dominio, accesso ai dati) in librerie che compilano sia per net48 sia per net10.0. Dove il codice dipende da API non disponibili, si nasconde la dipendenza dietro un'interfaccia con due implementazioni.

Alla fine di questa fase la stessa logica di business gira su entrambe le piattaforme. È il momento in cui il progetto smette di essere una scommessa: da qui in poi ogni pezzo migrato riusa codice già collaudato invece che riscritto. È anche il momento in cui, se l'applicazione ha strati mescolati, emerge il conto vero. Sulle scelte di separazione conviene restare sobri: l'articolo su quando la Clean Architecture serve davvero spiega perché in una migrazione non è il momento di introdurre astrazioni che nessuno ha chiesto.

Fase 4: Impalcatura e prima rotta (1-2 settimane)

Si crea il progetto ASP.NET Core, si mette YARP davanti con tutto instradato verso il vecchio, si configurano gli adattatori System.Web per la sessione condivisa, si costruisce la pipeline di rilascio nuova, e si migra una sola rotta poco rischiosa fino in produzione.

L'obiettivo di questa fase non è la rotta: è avere il percorso completo dal codice alla produzione funzionante e collaudato. Da qui in avanti il progetto è ripetitivo, e la ripetitività è ciò che rende le stime affidabili.

Fase 5: Migrazione a ondate (il grosso del progetto)

Si migrano le rotte a gruppi, in ordine di rischio crescente. Ogni ondata si chiude con un rilascio in produzione e con la verifica dei numeri: tempi di risposta, errori, uso di memoria. Le funzionalità delicate (pagamenti, fatturazione, chiusure contabili) si migrano per ultime e con una finestra di ritorno esplicita.

Il ritmo sostenibile per un team di due persone su un'applicazione media è di un'ondata ogni due settimane. Chi promette il doppio o non ha fatto le fasi 2 e 3, o sta contando solo la compilazione e non il collaudo.

Fase 6: Spegnimento e pulizia (2-4 settimane)

Quando l'ultima rotta è passata, si toglie il proxy, si eliminano i target doppi dalle librerie, si buttano gli adattatori di compatibilità e si spegne l'applicazione vecchia dopo un periodo di osservazione. Questa fase viene sempre rimandata e va invece messa a piano fin dall'inizio: un sistema che resta metà migrato per sempre ha il costo di manutenzione di due sistemi e i vantaggi di nessuno dei due.

Le sei fasi di un piano di migrazione da .NET Framework a .NET 10 con le durate indicative

WCF, Web Forms e i report: le tre trappole che fanno saltare i preventivi

Nella mia esperienza, quando una migrazione .NET Framework sfora il budget in modo drammatico, nove volte su dieci la causa è una di queste tre. Meritano un paragrafo ciascuna perché vanno affrontate in analisi, non in corso d'opera.

WCF con configurazioni non banali

Un servizio WCF con binding HTTP di base e contratti semplici passa su CoreWCF con poco lavoro. Il problema sono i casi che ci si è costruiti sopra negli anni: sicurezza a livello di messaggio con certificati, transazioni distribuite, sessioni affidabili, code MSMQ come trasporto, comportamenti personalizzati per la serializzazione.

La verifica va fatta prima e sul campo: si prende il file di configurazione del servizio, si elenca ogni binding e ogni comportamento, e per ciascuno si verifica il supporto. È mezza giornata di lavoro che evita di scoprire al quinto mese che l'integrazione con il gestionale del cliente più importante non si può portare senza cambiare anche il lato client, che è di un altro fornitore.

Quando il porting non è praticabile, la strada che funziona è mantenere il servizio WCF su .NET Framework dietro un'API moderna che lo incapsula, e migrarlo per ultimo o mai. È una convivenza a lungo termine deliberata, e va detto al committente in fase di offerta, non dopo.

Web Forms e la sua superficie nascosta

Il problema di Web Forms non è la conversione delle pagine. È che in quindici anni di manutenzione la logica di business è finita dentro il code-behind. Ogni pagina ha il suo pezzo di regole, e le stesse regole sono state copiate in tre pagine diverse con piccole varianti che qualcuno ha introdotto per una richiesta specifica di un cliente e che ora sono comportamento atteso.

La stima realistica non si fa contando le pagine. Si fa aprendo il code-behind delle dieci pagine più importanti e misurando quanta logica non di interfaccia contengono. Se il rapporto è basso, è un progetto di conversione dell'interfaccia. Se è alto, è un progetto di estrazione del dominio con l'interfaccia come conseguenza, e vale tre o quattro volte tanto.

I motori di reportistica

Crystal Reports, i report legati a System.Drawing, i generatori di PDF vecchi, i componenti di terze parti con licenza per macchina. È la voce più frequentemente dimenticata e quella che più spesso impedisce l'atterraggio su Linux, che è dove sta gran parte del risparmio economico della migrazione.

Va verificata all'inizio con una domanda sola: questo componente ha una versione supportata su .NET 10 e funziona senza Windows? Se la risposta è no, ci sono tre uscite, in ordine di costo: sostituire il componente, isolare la generazione dei report in un servizio separato che resta su Windows, o accettare di atterrare su container Windows rinunciando a una parte del risparmio. Sono tutte accettabili. Quella non accettabile è scoprirlo a progetto avviato.

Come si migra un'applicazione che non ha test automatici

La maggior parte delle applicazioni .NET Framework aziendali non ha test automatici, o ne ha pochi e scaduti. Questo è il vero motivo per cui le migrazioni fanno paura: nessuno sa dire con certezza se il sistema nuovo fa esattamente quello che faceva il vecchio.

La risposta sbagliata è "scriviamo i test prima di migrare". Scrivere una copertura completa su un sistema legacy costa quanto la migrazione, e nessuno lo autorizzerà mai. La risposta giusta è costruire una rete di sicurezza mirata, non completa.

I test caratterizzanti sui percorsi che contano

Un test caratterizzante non verifica che il codice sia corretto. Verifica che il codice nuovo faccia esattamente quello che faceva il vecchio, compresi i comportamenti sbagliati. Si prende un caso reale, si registra l'uscita del sistema attuale, e la si usa come valore atteso.

Non serve farlo su tutto. Serve farlo sui percorsi che, se si rompono, fermano l'azienda: il calcolo del prezzo, la generazione del documento fiscale, la chiusura di periodo, l'esportazione verso il gestionale. Su un sistema medio sono fra dieci e trenta scenari, e coprirli è lavoro di giorni, non di mesi.

Il confronto in parallelo sui dati veri

Questa è la tecnica che dà più tranquillità in assoluto. Durante la migrazione si fa girare la versione nuova accanto alla vecchia sugli stessi dati di ingresso, senza che la nuova produca effetti, e si confrontano le uscite. Su un processo batch si esegue lo stesso ciclo notturno con entrambe le versioni e si confrontano i risultati riga per riga. Su un'applicazione web, con il proxy davanti, si può duplicare una parte del traffico di sola lettura verso la versione nuova e confrontare le risposte.

Le differenze che emergono sono quasi sempre di due tipi: arrotondamenti e formattazione delle date e dei numeri, che cambiano fra runtime e culture, e ordinamenti non deterministici che nel vecchio sistema erano stabili per caso. Sono entrambi problemi reali e sono entrambi il tipo di cosa che, scoperta in produzione, costa una giornata di credibilità con il cliente.

Il collaudo umano guidato

Quello che resta lo collauda una persona, ma non a caso. Si prende l'elenco delle funzioni ordinato per frequenza d'uso reale, che si ricava dai log o dalle statistiche di utilizzo, e si collauda dall'alto verso il basso finché il tempo assegnato non finisce. Collaudare in ordine di frequenza invece che in ordine di menu cambia completamente il rendimento di quelle ore.

Che cosa guadagni davvero: prestazioni, costo del cloud, sicurezza

Un progetto di migrazione va venduto internamente su benefici misurabili, non sulla modernità. Questi sono quelli che si possono misurare prima e verificare dopo.

Prestazioni

Il guadagno di velocità nel passaggio da .NET Framework a .NET moderno è reale e grande, ma va detto con onestà: dipende dal carico di lavoro. Su applicazioni web con molta serializzazione, molte richieste in ingresso e molto lavoro asincrono, i miglioramenti di throughput sono nell'ordine di alcune volte, non di percentuali, ed è il caso più comune nelle applicazioni aziendali. Su un batch che passa il novanta per cento del tempo ad aspettare il database, il guadagno è vicino a zero, perché il collo di bottiglia non è il runtime.

Il modo corretto di stimarlo prima è misurare dove va il tempo oggi. Se l'applicazione è lenta per via delle query, la migrazione non la renderà veloce e prometterlo è un errore che si paga al collaudo.

C'è però un guadagno che vale sempre: l'occupazione di memoria e il tempo di avvio. Un processo che parte in un secondo invece che in venti cambia le regole del rilascio, permette lo scaling automatico e rende praticabile lo spegnimento a riposo, che è dove sta il risparmio economico.

Costo dell'infrastruttura

Questo è il beneficio più facile da quantificare prima, ed è quello che convince chi firma. Le voci sono tre: la licenza Windows che sparisce quando si atterra su Linux, il dimensionamento che cala perché il runtime nuovo fa lo stesso lavoro con meno memoria, e la possibilità di scendere a zero repliche fuori dagli orari di utilizzo.

La terza voce è quella grossa e vale solo per i carichi non continui: applicazioni interne, portali per clienti con uso concentrato in fascia lavorativa, ambienti di collaudo. Un ambiente di collaudo che si accende solo quando serve invece di restare acceso ventiquattro ore su ventiquattro costa una frazione. È lo stesso principio che descrivo per le PMI nell'articolo su come ridurre i costi su Azure senza un team cloud.

Sicurezza e conformità

Il punto qui non è che .NET moderno sia più sicuro in astratto. È che puoi aggiornarlo. Su .NET Framework, chiudere una vulnerabilità in una libreria significa spesso scoprire che l'ultima versione compatibile è vecchia e vulnerabile anche lei. Su .NET moderno aggiorni il pacchetto, ricompili e rilasci.

Per un'azienda che deve rispondere a un questionario di sicurezza di un cliente grande, o che ha obblighi normativi sul software che tratta dati personali, la differenza fra "possiamo chiudere la vulnerabilità in tre giorni" e "quella libreria non ha una versione corretta per la nostra piattaforma" è la differenza fra vincere e perdere una fornitura. Nella pratica commerciale italiana degli ultimi due anni questa voce è diventata più decisiva del costo.

Quando conviene non migrare

Una guida che dice sempre di sì non serve a nessuno. Ci sono situazioni in cui la migrazione .NET Framework è la scelta sbagliata, e riconoscerle fa risparmiare più di qualsiasi ottimizzazione.

Il software è in dismissione. Se il sistema verrà sostituito da un prodotto commerciale entro due anni, migrare è buttare soldi. Si chiudono le vulnerabilità esposte, si isola il sistema, si tira fino alla sostituzione.

Il software è fermo e non evolve. Un applicativo interno, senza superficie esposta a internet, che fa una cosa sola e la fa bene, senza richieste di modifica da anni, non ha un problema da risolvere. La regola pratica: se nessuno ha aperto quel codice negli ultimi diciotto mesi e nessuno ha in programma di aprirlo, non è debito tecnico.

Non c'è nessuno che possa mantenere il risultato. Migrare un sistema che poi resta senza presidio significa spostare il problema di qualche anno e pagarlo due volte. La domanda che va fatta prima di partire è chi rilascerà la prossima versione, non chi farà la migrazione.

Il problema vero è un altro. Capita spesso: si arriva chiedendo una migrazione e il vero problema è che le query sono lente, che il database non ha indici, che non esiste un ambiente di collaudo o che i rilasci si fanno a mano di notte. Nessuna di queste cose si risolve cambiando runtime, e tutte costano molto meno da risolvere. Ho visto più di un'azienda risolvere il problema che la spingeva a migrare con due settimane di lavoro sul database, e rinviare la migrazione di due anni con piena ragione.

Gli errori che costano di più

Chiudo con la lista degli errori che ho visto ripetersi, in ordine di quanto costano.

Migrare e ristrutturare insieme

È il più caro di tutti. La tentazione è irresistibile: già che tocchiamo quel codice, sistemiamo anche l'architettura, passiamo a EF Core, introduciamo i microservizi, cambiamo il database. Il risultato è che quando qualcosa non funziona non sai più se è colpa della migrazione o del cambiamento, e la capacità di isolare i problemi, che è l'unica cosa che tiene in piedi un progetto del genere, sparisce.

La regola è una sola e non ha eccezioni: prima si migra a parità di comportamento, poi si migliora. Sono due progetti, con due collaudi e due rilasci. Fatti in sequenza costano meno che fatti insieme, e questo è controintuitivo solo finché non lo si è provato una volta.

Fidarsi dello strumento automatico

L'Upgrade Assistant, gli strumenti di conversione assistita e i più recenti assistenti basati sull'intelligenza artificiale fanno un lavoro eccellente sulla parte meccanica: il file di progetto, i riferimenti, le API rinominate, la conversione della sintassi. Fanno male, per loro natura, la parte che richiede di sapere che cosa fa il software: le scelte architetturali, la gestione degli errori che dipendono dal contesto, i comportamenti al limite.

Usarli è giusto e fa risparmiare settimane. Considerarli sufficienti produce un codice che compila e che nessuno capisce, con bug che emergono nei casi rari, cioè nei casi che in azienda sono i più costosi. La proporzione realistica: gli strumenti coprono la parte meccanica, che è la maggioranza delle righe ma la minoranza delle ore.

Non definire quando il progetto è finito

Un progetto di migrazione senza un criterio di completamento scritto non finisce: si trasforma in manutenzione. Il criterio va scritto prima e deve essere verificabile: tutte le rotte migrate, il proxy rimosso, l'applicazione vecchia spenta, i target doppi eliminati, la pipeline vecchia cancellata. Finché una di queste voci è aperta, il progetto è aperto e costa.

Migrare senza misurare prima

Se non hai i numeri di oggi (tempi di risposta, memoria, costo mensile dell'infrastruttura, tempo del ciclo di rilascio) non potrai dimostrare il miglioramento domani. È un errore che non fa fallire il progetto ma che fa fallire il progetto successivo, perché quando chiederai il budget per la modernizzazione dopo non avrai niente da mostrare a chi firma. Mezza giornata di misurazione prima di iniziare vale più di qualsiasi relazione finale.

Trattare il team come una variabile indipendente

Le persone che manterranno il sistema nuovo devono partecipare a farlo. Una migrazione fatta interamente da un fornitore esterno e consegnata chiavi in mano produce un sistema moderno che il team interno non sa toccare, e nel giro di un anno si torna a una situazione di blocco, questa volta con una piattaforma nuova. Il modello che funziona è misto: chi ha già fatto il percorso guida e fa le scelte difficili, il team interno esegue e impara. Costa qualche settimana in più e vale anni.

Il punto, in breve

La migrazione .NET Framework non è un aggiornamento di versione ed è un errore trattarla come tale, ma non è nemmeno il salto nel buio che era cinque anni fa. Gli strumenti per farla per gradi esistono, funzionano, e permettono di tenere il sistema in produzione tutti i giorni del progetto.

Quello che decide l'esito non è la tecnologia: è l'analisi iniziale e l'ordine delle fasi. Un'applicazione web media, analizzata bene, convertita al formato SDK, con il dominio estratto in librerie a doppio target e le rotte spostate a ondate dietro un proxy, è un progetto di due o tre mesi con rischio controllato e valore in produzione dal primo mese. La stessa applicazione affrontata come una riscrittura big bang è un progetto di un anno con esito incerto. Il codice è lo stesso: cambia il metodo.

Se hai un'applicazione su .NET Framework e devi decidere, il primo passo non è scegliere il fornitore. È fare l'inventario: la lista delle dipendenze bloccanti, la conta delle occorrenze di HttpContext.Current fuori dai controller, la verifica dei componenti di reportistica, il costo attuale dell'infrastruttura. Sono due settimane di lavoro che trasformano una decisione basata sull'istinto in una decisione basata su una tabella, e cambiano completamente la qualità dei preventivi che riceverai.

Se vuoi che quell'inventario lo faccia qualcuno che ha già portato dall'altra parte applicazioni con questo profilo, e che ti dica anche quando la risposta giusta è non migrare, quello è esattamente il lavoro che facciamo.

Domande frequenti

Non c'è una data che ti obbliga: .NET Framework 4.8 è un componente di Windows e resta supportato finché è supportata la versione di Windows su cui gira. Quello che ti obbliga è l'ecosistema intorno. Il framework è congelato e riceve solo correzioni di sicurezza, mentre le librerie NuGet, gli SDK dei fornitori cloud e gli strumenti di sviluppo smettono progressivamente di pubblicare il target net48. Il momento in cui la migrazione smette di essere pianificabile e diventa un'emergenza è quello in cui, per chiudere una vulnerabilità, scopri che l'ultima versione compatibile della libreria è vecchia di due anni ed è vulnerabile anche lei. La domanda giusta non è se migrare ma quanto ti costa ogni mese di attesa, ed è un conto che si può fare con i numeri: costo dell'infrastruttura Windows, tempo sprecato dal team su una piattaforma ferma, vulnerabilità che non puoi chiudere.

.NET 10, rilasciata a novembre 2025, è la versione LTS su cui atterrare oggi. Il calendario delle versioni moderne è rigido: le LTS hanno tre anni di supporto, le STS diciotto mesi. .NET 8, che ha assorbito la maggior parte delle migrazioni fra il 2024 e il 2025, esce di supporto a novembre 2026: puntare lì oggi significa dover rifare un aggiornamento di runtime pochi mesi dopo aver finito, con il team che ha già smontato il ponteggio. È un errore che compare spesso nei preventivi, perché il fornitore riusa il piano di un progetto precedente senza aggiornare la destinazione. Prima di firmare, verifica che il target dichiarato sia .NET 10 e non .NET 8.

Le voci che non esistono dall'altra parte e vanno riscritte sono: ASP.NET Web Forms (nessun percorso di conversione, la parte di interfaccia va rifatta, tipicamente su Blazor), WCF lato server (esiste CoreWCF della .NET Foundation ma copre un sottoinsieme, e i binding con sicurezza a livello di messaggio, transazioni distribuite o MSMQ come trasporto vanno verificati uno per uno), .NET Remoting (rimosso), AppDomain (sostituito da AssemblyLoadContext, che risolve il caricamento ma non l'isolamento di sicurezza), i servizi web ASMX, Code Access Security e Windows Workflow Foundation. Passano invece, con attenzione: Entity Framework 6, WPF e Windows Forms (solo su Windows), e la maggior parte del codice di dominio. Attenzione a System.Drawing, che fuori da Windows non funziona più, e a BinaryFormatter, rimosso dalle versioni recenti.

Il costo non dipende dalla dimensione del codice ma dalla lista delle dipendenze bloccanti: due applicazioni da centomila righe possono differire di dieci volte. Gli ordini di grandezza sono tre. Profilo semplice (libreria, servizio Windows, processo batch, API senza dipendenze bloccanti): da poche giornate a tre settimane di lavoro effettivo. Profilo medio (applicazione web ASP.NET MVC o Web API con database): da uno a tre mesi con un team di due persone. Profilo pesante (Web Forms, WCF lato server, desktop con forte interoperabilità): da sei mesi a due anni, e va gestito come modernizzazione a tappe, non come migrazione. A queste cifre vanno aggiunte quattro voci che i preventivi dimenticano quasi sempre: il collaudo (doppio, prima e dopo), la nuova pipeline di rilascio, la doppia manutenzione durante il progetto e la formazione del team.

Sì, ed è il metodo che consigliamo nella grande maggioranza dei casi. Si chiama strangler fig e su .NET si realizza con due strumenti: YARP, il proxy inverso di Microsoft, che sta davanti alle due applicazioni e smista ogni richiesta in base al percorso, e gli adattatori System.Web (Microsoft.AspNetCore.SystemWebAdapters), che permettono al codice scritto contro System.Web di funzionare dentro ASP.NET Core e alle due applicazioni di condividere sessione e identità dell'utente. Concretamente: al primo giorno tutto è instradato verso l'applicazione vecchia e il comportamento è identico a prima, poi si sposta una rotta alla volta, in ordine di rischio crescente. Ogni rotta migrata è un rilascio piccolo, reversibile con un cambio di configurazione e verificabile in produzione su traffico reale. L'utente non se ne accorge e l'indirizzo non cambia.

Scrivere una copertura completa prima di migrare costa quanto la migrazione e nessuno la autorizza. La risposta che funziona è una rete di sicurezza mirata, fatta di tre cose. Primo: test caratterizzanti sui percorsi che, se si rompono, fermano l'azienda (calcolo del prezzo, documento fiscale, chiusura di periodo, esportazione verso il gestionale). Su un sistema medio sono fra dieci e trenta scenari, coprirli è lavoro di giorni. Secondo: confronto in parallelo sui dati veri, facendo girare la versione nuova accanto alla vecchia senza effetti collaterali e confrontando le uscite riga per riga. Le differenze che emergono sono quasi sempre arrotondamenti, formattazione di date e numeri, e ordinamenti che nel vecchio sistema erano stabili per caso. Terzo: collaudo umano guidato dalle statistiche d'uso reali, dall'alto verso il basso, invece che in ordine di menu.

Lascia i tuoi dati nel form qui sotto

Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 27 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Nel corso della sua carriera ha collaborato con realtà come Cotonella, Il Sole 24 Ore, FIAT e NATO, guidando team nello sviluppo di piattaforme scalabili e modernizzando ecosistemi legacy complessi.

Ha formato centinaia di sviluppatori e affiancato aziende di ogni dimensione nel trasformare il software in un vantaggio competitivo, riducendo il debito tecnico e portando risultati concreti in tempi misurabili.

Stai leggendo perché vuoi smettere di rattoppare software fragile.Scopri il metodo per progettare sistemi che reggono nel tempo.