Windows Forms nel 2026: tenerlo, portarlo o riscriverlo
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.

Un'azienda di componenti meccanici di Vicenza ha un gestionale scritto in Windows Forms nel 2012. Venticinque persone lo usano ogni giorno: ordini, magazzino, distinte base, spedizioni. Funziona, il database è in ordine, nessuno si lamenta. Poi il responsabile dei sistemi scopre tre cose nello stesso mese. Lo sviluppatore che conosce il programma ha dato le dimissioni. Il fornitore dei componenti per le griglie ha smesso di rilasciare aggiornamenti per la versione che usano. E un cliente grosso chiede, nel questionario di sicurezza, quale versione di .NET gira sui loro computer.

Cerca "Windows Forms" e trova pareri opposti. Chi dice che è morto da vent'anni, chi dice che è l'unico strumento serio per un programma da scrivania. Un fornitore propone di riscrivere tutto sul web. Un altro propone di "aggiornare" senza dire a cosa. Il preventivo più basso e quello più alto sono separati da un fattore tre, e nessuno dei due spiega il conto.

Questo articolo ti dice che cosa significa davvero "Windows Forms" nel 2026, quali sono le quattro strade che esistono, quanto costa ciascuna in tre, cinque e otto anni per un'azienda tipo, e quale numero decide la scelta. Il risultato ha una particolarità che ti anticipo: per un orizzonte breve la strada più economica è non toccare niente. Per un orizzonte lungo non lo è più. Il punto in cui le due linee si incrociano si calcola, e dipende da due numeri che hai già in azienda.

Troverai il conto in euro con tutte le ipotesi dichiarate, il codice C# che ho compilato e provato per davvero (il file di progetto per .NET 10, la classe che porta la logica fuori dalle form, i test, e il controllo automatico che impedisce alle form di tornare a parlare con il database), un metodo per misurare in dieci minuti quanta logica sta nelle tue form, e i passi per portare il programma senza fermare l'azienda.

Una premessa. Dal 1999 progetto software per aziende e ho portato in produzione programmi in Windows Forms, WPF e web. Non ti vendo una tecnologia. Il caso che uso è un'azienda tipo, costruita con ordini di grandezza di settore, non il caso di un cliente reale. I numeri sono ipotesi scritte in chiaro: servono a farti rifare il conto con i tuoi.

Che cosa significa "Windows Forms" nel 2026?

Quando qualcuno dice "il nostro programma è in Windows Forms" può voler dire due cose molto diverse, e la differenza vale decine di migliaia di euro.

La prima è Windows Forms su .NET Framework. È la versione storica, quella uscita nel 2002 con Visual Studio .NET e arrivata fino alla 4.8. È il caso della maggior parte dei gestionali aziendali scritti fra il 2005 e il 2018. Il .NET Framework 4.8 non è scomparso: Microsoft lo mantiene come componente di Windows e lo supporta finché è supportato il sistema operativo su cui gira. Ma non riceve più funzioni nuove. Il linguaggio C# che puoi usare è fermo a una versione di anni fa, le librerie moderne si vedono sempre meno, e chi lo conosce bene sta andando in pensione.

La seconda è Windows Forms su .NET moderno. Dal 2019 Microsoft ha portato Windows Forms dentro la piattaforma .NET che prosegue (prima .NET Core, poi .NET 5, 6, 8 e così via). Oggi, a ottobre 2026, la versione a supporto lungo è .NET 10, uscita a novembre 2025 e supportata per tre anni, fino a novembre 2028. La versione 8 esce di supporto a novembre 2026, fra poche settimane. Windows Forms su .NET 10 è un prodotto vivo: riceve correzioni, miglioramenti alla gestione dello schermo ad alta risoluzione, un editor grafico che è stato riscritto per funzionare nella nuova piattaforma. Resta una tecnologia solo per Windows, e non è nata per cambiare il modo in cui si disegnano le interfacce. È nata per far continuare a vivere il lavoro fatto.

Questa distinzione toglie metà della confusione. Chi dice che "Windows Forms è morto" parla quasi sempre della prima. Chi dice che "funziona benissimo" parla spesso della seconda, o di un programma della prima che non ha ancora presentato il conto. Non sono la stessa cosa e non vanno confrontate come se lo fossero.

Un'altra precisazione. In questo articolo non parlo di ClickOnce, di installatori o di come distribuisci il programma ai venticinque computer: è un tema a parte, e lo tocco solo dove cambia il costo. Non parlo nemmeno di WPF, che è la tecnologia sorella con un altro modello di disegno: se ti interessa, c'è la guida su WPF nel 2026.

Windows Forms su .NET Framework 4.8 e su .NET 10: stesso aspetto per l'utente, ma il primo è fermo e il secondo riceve aggiornamenti fino a novembre 2028

Per chi guida l'azienda la domanda cambia, quindi. Non è "Windows Forms sì o no". È: il mio programma gira sul motore fermo o sul motore vivo, e quanto tempo gli chiedo ancora di vivere?

Com'è fatta l'azienda tipo e da quali numeri parte?

Per non parlare in astratto uso un caso. È un gestionale interno di un'azienda di medie dimensioni, scritto in Windows Forms su .NET Framework 4.8. Le misure sono ordini di grandezza di settore, scritte in modo che tu possa sostituirle.

MisuraValoreCome si rileva
Form (finestre) del programma80Conta i file con il disegno delle finestre, quelli che finiscono per .Designer.cs
Quota di logica dentro le form60%Quanto codice sta nei file delle form e non in classi separate (te lo faccio misurare più avanti)
Modifiche richieste ogni anno120Richieste evase negli ultimi dodici mesi: un campo, una regola, un filtro, una stampa
Utenti25Persone che aprono il programma ogni giorno
Costo di un'ora di programmatore60 euroCosto del lavoro diviso per le ore lavorate
Costo di una giornata480 euro8 ore a 60 euro
Componenti di terze parti1 suite di griglie e una di reportGuarda i riferimenti del progetto: sono le librerie con un nome di fornitore

La seconda riga è quella che sorprende di più. La quota di logica dentro le form dice quanto del lavoro vero (le regole di sconto, i calcoli di magazzino, le validazioni) è scritto dentro il codice che risponde ai clic sulle finestre. Quando è alta, ogni passo che vuoi fare con quel programma, portarlo, provarlo, cambiarlo, costa di più, perché per toccare una regola devi toccare una finestra. Non è un difetto di Windows Forms. È il modo in cui lo strumento ti ha insegnato a lavorare: fai doppio clic su un pulsante, e Visual Studio ti apre il punto dove scrivere il codice. Funziona benissimo per cento righe. A centomila diventa un debito.

Un modo pratico per sapere dove sei: se il tuo sviluppatore, alla domanda "dov'è la regola dello sconto?", risponde "dipende da quale finestra", sei sopra il 50%. Se risponde con il nome di una classe, sei sotto il 30%.

Quali strade esistono?

Le strade vere sono quattro. Ne ho lasciata fuori una che non è una strada: "vediamo cosa succede". Ha un costo, ed è dentro la prima.

A. Restare dove sei: .NET Framework 4.8, così com'è

Non si tocca l'architettura. Si continuano a fare le 120 modifiche l'anno e a sistemare quello che si rompe. Costruzione zero, avviamento zero. Il costo sta tutto nella manutenzione e cresce nel tempo, per tre ragioni che spiego più avanti: ogni modifica richiede un po' più di ore (strumenti vecchi, librerie che non si aggiornano), ogni anno servono giornate di "tenuta" per tenere in piedi ciò che invecchia, e il rischio di trovarsi costretti a migrare in fretta, che è il più caro di tutti, non compare nel conto ma esiste.

B. Portare a .NET 10 e restare in Windows Forms

Il programma resta lo stesso, con le stesse finestre e lo stesso aspetto. Cambia il motore: il progetto passa da .NET Framework 4.8 a .NET 10. Si aggiornano i pacchetti, si sostituiscono i componenti che non esistono più, si rimette in piedi l'installazione. Il lavoro è proporzionale al numero di form e a quanto codice sta dentro, perché ogni finestra va aperta, compilata e provata. Dopo, il programma è in grado di usare C# moderno, le librerie correnti, gli strumenti di analisi attuali.

C. Portare a .NET 10 e portare fuori la logica

Come la B, con un passo in più: mentre si porta il programma, la logica di business esce dalle form e va in classi separate, che non sanno niente di finestre e si provano con test automatici. Costa di più subito. Rende meno costose tutte le modifiche successive, e apre una porta che le altre non aprono: la stessa logica può servire domani una pagina web, un'applicazione mobile, un'integrazione, senza riscriverla.

D. Riscrivere tutto sul web

Si butta l'interfaccia e si ricostruisce con una tecnologia web (Blazor, Razor Pages, un'applicazione a pagine). Ha senso quando gli utenti sono fuori sede, quando devono entrarci clienti o fornitori, quando il programma deve funzionare da un tablet. Costa di più di tutte: si rifanno le ottanta finestre una per una, si forma chi le usa, si cambia l'esperienza di lavoro. Per un confronto più dettagliato fra le tecnologie web c'è la guida su Razor Pages, MVC e Blazor.

Il punto da fissare: la C non è un compromesso fra B e D. È la B fatta bene, con in più la possibilità di arrivare alla D un pezzo alla volta, se e quando servirà.

Quanto costa ogni strada in tre, cinque e otto anni?

Costo totale per orizzonte: a 3 anni restare costa 78.975 euro, a 5 anni 141.799 contro 147.571 per portare a .NET 10, a 8 anni 254.547 contro 202.895 per portare ed estrarre la logica

Il conto ha quattro voci per ogni strada. Ti dichiaro le ipotesi una per una, perché sono il punto debole di ogni confronto di questo genere.

  1. Costruzione. Per la B, 0,25 giornate per form più 1,25 giornate moltiplicate per la quota di logica nelle form. Con il 60% fa 1,0 giornata a form, cioè 80 giornate, 38.400 euro. Per la C, la stessa cifra più 0,9 giornate per ogni form in cui c'è logica da estrarre (60% di 80 form, quindi 43,2 giornate), cioè 59.136 euro in tutto. Per la D, 2,4 giornate a form: 92.160 euro. Per la A, zero.
  2. Avviamento. Analisi, scheletro, formazione. B: 6 giornate più 6.000 euro per aggiornare o sostituire i componenti di terze parti. C: 10 giornate più gli stessi 6.000. D: 18 giornate, 4.800 euro di predisposizione di accesso e hosting, e mezza giornata di formazione per ciascuno dei 25 utenti. A: zero.
  3. Manutenzione. 120 modifiche l'anno. Ore per modifica: A 3,0 il primo anno, con un aumento del 7% ogni anno; B 2,6 con +1% l'anno; C 2,1 con +1%; D 2,4 con +1%. Il 7% che pesa sulla A è l'ipotesi più discutibile, e lo dico subito: è il costo di strumenti che invecchiano, librerie che non si aggiornano, esperti che si trovano a fatica.
  4. Tenuta. Le giornate l'anno spese per tenere in piedi, senza aggiungere nulla. A: 6 giornate il primo anno, con un aumento del 10% annuo (certificati, protocolli di rete che cambiano, versioni di Windows, un componente che smette di funzionare). B e C: 2 giornate l'anno. D: 3 giornate più 300 euro di hosting.

Il risultato, per i tre orizzonti:

Strada3 anni5 anni8 anni
A. Restare su .NET Framework78.975141.799254.547
B. Portare a .NET 10106.883147.571210.068
C. Portare ed estrarre la logica118.631151.863202.895
D. Riscrivere sul web169.180208.445268.696

Leggi la tabella con calma, perché dice una cosa scomoda. A tre anni la strada più economica è non fare nulla: 78.975 euro contro 106.883 per portare il programma. A cinque anni sono quasi pari, con la A che costa ancora un po' meno (141.799 contro 147.571). A otto anni la situazione si rovescia: restare costa 254.547 euro, portare ed estrarre la logica 202.895. La differenza è di oltre 50.000 euro, e continua ad allargarsi.

Questo è il motivo per cui il preventivo da solo non serve. Se il fornitore ti dice "migrare costa 40.000 euro", il numero è vero e non ti dice niente: ti serve il confronto con quello che costa restare nello stesso periodo. E il conto del restare ha una forma curva, non una riga piatta.

Guarda dove nasce la differenza a otto anni. Per la A, la manutenzione pesa 221.612 euro e la tenuta 32.935: tutto il costo è un flusso che non si ferma. Per la C, la costruzione è 59.136 euro e l'avviamento 10.800, che si pagano una volta; la manutenzione è 125.279 e la tenuta 7.680. La C costa di più il primo giorno e molto meno ogni giorno dopo.

La D è la più cara a ogni orizzonte. Non vuol dire che sia sbagliata: vuol dire che si sceglie per ragioni che questo conto non misura (utenti fuori sede, accesso da dispositivi diversi, clienti che devono entrare), non per risparmiare.

Un'avvertenza che ti devo. Questi numeri non sono tariffe. Un gruppo che ha già portato altri programmi avrà giornate più basse. Un programma con molti componenti di terze parti non più mantenuti avrà un avviamento molto più alto dei 6.000 euro che ho messo. Uso questi valori per far vedere la forma del conto, non per sostituirlo. Il valore del conto sta nella forma: la A cresce, le altre salgono una volta e poi crescono poco.

Qual è il numero che decide quanto aspettare?

Costo cumulato anno per anno: la linea di restare su .NET Framework incrocia quella di portare a .NET 10 al sesto anno e continua a salire più ripida

Il numero è uno solo, anche se ha due facce: quanti anni deve ancora vivere il programma, letto insieme a quante modifiche gli chiedi ogni anno. Ho rifatto il conto anno per anno e cerco il primo anno in cui portare il programma costa meno di restare.

Con 120 modifiche l'anno e il 60% di logica nelle form, il primo anno è il sesto. Prima conviene restare, da lì in poi no. Quello che cambia il sesto anno è il numero di modifiche:

Modifiche l'annoPrimo anno in cui portare costa meno di restareCome leggerlo
309Un programma quasi fermo: restare è ragionevole a lungo
608Una modifica ogni sei giorni lavorativi: dipende da quanto vivrà
1206Una modifica ogni due giorni e mezzo: è il caso tipico di un gestionale vivo
2404Un programma in cui si cambia qualcosa ogni giorno: conviene muoversi presto

Il secondo numero è la quota di logica nelle form, e dice quanto costa portare:

Logica nelle formPrimo anno in cui portare costa meno di restareAnno da cui la C costa meno della B
20%43
40%55
60%67
80%78

Due cose si vedono. La prima: più logica sta dentro le form, più tempo ci vuole perché portare convenga, ma conviene lo stesso. La seconda, che mi interessa di più: la C batte la B solo dopo diversi anni, e più la logica è sparsa nelle form più tardi. Se la tua logica è al 20%, estrarre il resto costa poco e si ripaga già al terzo anno. Se è all'80%, estrarre è un lavoro enorme e si ripaga all'ottavo.

La lettura pratica, in tre righe. Se il programma vivrà ancora meno di quattro anni e cambia poco, resta dov'è e metti da parte il budget. Se vivrà più di sei anni e lo si cambia spesso (120 modifiche l'anno o più), portalo ora, non fra tre anni, perché ogni anno di attesa è un anno del costo più caro. Se sta nel mezzo, porta a .NET 10 senza estrarre tutto e decidi dopo, guardando i numeri veri.

Un modo diretto per usare queste tabelle: moltiplica le modifiche l'anno per gli anni di vita che prevedi. Sotto le 500 modifiche in totale restare quasi sempre costa meno. Sopra le 1.000 portare conviene quasi sempre. In mezzo decide la quota di logica nelle form.

Come si misura quanta logica sta nelle form?

Il numero che serve davvero è la quota di codice che sta nei file delle form (quelli con il codice che risponde ai clic) rispetto a tutto il codice scritto a mano. Non è una misura perfetta: dentro una form ci sono anche righe che appartengono giustamente all'interfaccia. Ma è un buon indicatore, e si calcola in un minuto con uno script. Lo lanci dalla cartella della soluzione:

$tutti = Get-ChildItem -Recurse -Filter *.cs |
    Where-Object { $_.Name -notlike '*.Designer.cs' -and $_.FullName -notmatch '\\(obj|bin)\\' }

$righe = { param($f) ($f | ForEach-Object { (Get-Content $_.FullName | Measure-Object -Line).Lines } | Measure-Object -Sum).Sum }

$form = $tutti | Where-Object { Select-String -Path $_.FullName -Pattern ': Form\b|: UserControl\b' -Quiet }

$totale = & $righe $tutti
$nelleForm = & $righe $form

'{0} righe in tutto, {1} nelle form: {2:P0}' -f $totale, $nelleForm, ($nelleForm / $totale)

Lo script esclude i file generati dal disegnatore e le cartelle di compilazione, poi conta le righe dei file che contengono una classe derivata da Form o da UserControl. Il risultato è il tuo 60%, o 25%, o 85%. Se vuoi un controllo più preciso, aggiungi un secondo conteggio: quanti file di form contengono parole come SqlConnection, SqlCommand o DbContext. Ogni file che le contiene è una finestra che parla direttamente con il database, e cioè una finestra che non si può provare da sola.

Io lo guardo sempre per primo, prima ancora di parlare di preventivi. Il motivo è semplice: quel numero decide quanto costerà ogni lavoro successivo, non solo la migrazione. Con il 25% sai che il programma è già abbastanza ordinato e che portarlo è un lavoro di aggiornamento. Con l'85% sai che stai per fare due lavori nello stesso momento, e che sarebbe meglio dichiararlo.

Come si porta un programma Windows Forms a .NET 10?

Il passaggio da .NET Framework a .NET 10 non è un salto nel buio, ma ha una sequenza che conviene rispettare. La mia, in sei passi.

1. Prima si misura e si ferma il programma com'è

Prima di toccare qualcosa, rilevi i tempi di apertura delle dieci finestre più usate e di tre operazioni lunghe (una stampa, un'elaborazione, un'esportazione). Salvi queste misure. Poi conservi il programma attuale installabile da qualche parte: se qualcosa va storto, deve esistere un modo di tornare indietro in pochi minuti, e non è la ricompilazione del vecchio codice.

2. Si fa il censimento di ciò che non si porta da solo

Alcune cose del .NET Framework non esistono più nella piattaforma moderna, oppure esistono in forma diversa. Le più comuni nei gestionali:

  • Componenti di terze parti. Le griglie e i report di un fornitore sono il rischio più grande. Controlla ciascuno: esiste una versione per .NET moderno? A che prezzo? Se il fornitore non l'ha prodotta, quella finestra è un lavoro di sostituzione, non di porting.
  • Accesso ai dati con System.Data.SqlClient. Va sostituito con Microsoft.Data.SqlClient, che è la versione che riceve aggiornamenti. Il cambio è quasi sempre meccanico, ma attenzione: la nuova versione richiede una connessione cifrata per impostazione predefinita, e un server con un certificato non valido, che prima passava, ora dà errore.
  • Servizi WCF. Un client WCF si porta con i pacchetti System.ServiceModel. Un server WCF non si porta: si sostituisce con CoreWCF, con un'API web, o con gRPC. Se il tuo programma chiama un servizio che hai scritto tu e non c'è tempo, conviene lasciarlo dov'è e portare prima il client.
  • .NET Remoting e creazione di AppDomain. Non esistono più. Sono rari nei gestionali recenti, comuni in quelli del 2008.
  • Report con Crystal Reports o ReportViewer. Ognuno ha una strada diversa, e va verificata caso per caso. È spesso la voce che muove di più il preventivo.
  • Riferimenti a librerie a 32 bit o a controlli ActiveX. Un controllo ActiveX nelle form si può ancora usare, con limiti. Una libreria nativa a 32 bit chiede di compilare tutto a 32 bit, e va deciso con consapevolezza.

Il censimento è la parte del lavoro che separa i preventivi seri da quelli di fantasia. Se un fornitore ti dà un prezzo senza averlo fatto, il prezzo è una scommessa.

3. Si porta prima il codice che non ha finestre

Le librerie di accesso ai dati e di regole, se già separate dalle form, si portano per prime, e conviene farle compilare per entrambi i mondi nello stesso tempo. Una libreria destinata a netstandard2.0 viene usata sia dal vecchio .NET Framework 4.8 sia da .NET 10. È l'accorgimento che permette di non fermare nulla: per mesi il vecchio programma e il nuovo usano lo stesso codice di regole, compilato una volta sola.

4. Si converte il progetto dell'eseguibile

Il file di progetto cambia stile: da quello lungo e pieno di elenchi di file, tipico del .NET Framework, a quello corto e a "stile SDK". Lo strumento di Microsoft per l'aggiornamento assistito fa gran parte del lavoro e lascia un elenco di ciò che non ha saputo convertire. Il risultato, per un programma Windows Forms su .NET 10, è questo:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
    <ApplicationVisualStyles>true</ApplicationVisualStyles>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Gestionale.Domain\Gestionale.Domain.csproj" />
  </ItemGroup>
</Project>

Quattordici righe contro le centinaia del vecchio file. Le due proprietà ApplicationHighDpiMode e ApplicationVisualStyles sostituiscono righe che prima stavano in un manifesto e in codice scritto a mano: dicono al programma di gestire correttamente gli schermi con scala diversa da un monitor all'altro (il problema classico dei portatili collegati a un secondo schermo, in cui le finestre diventavano sfocate o minuscole). Il punto di ingresso diventa:

using Gestionale.App.Forms;
using Gestionale.Domain;

namespace Gestionale.App;

internal static class Program
{
    [STAThread]
    private static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new OrdineForm(new CalcolatoreSconto()));
    }
}

La riga ApplicationConfiguration.Initialize() applica le impostazioni dichiarate nel file di progetto. Il resto è quello che già conosci.

5. Si apre ogni finestra, una per una

Qui il lavoro è lento e non si scorciatoia. Ogni form si apre nel disegnatore di .NET 10, si compila, si prova con i dati veri. Le difformità più frequenti sono piccole: un carattere che si vede diverso, un controllo che si ridimensiona in modo diverso, una proprietà che non c'è più. Ho messo 1,0 giornata a form nella stima, con la logica al 60%, e raramente è meno: la parte veloce è il compilare, la parte lenta è rifare la prova di ogni finestra con una persona che la usa davvero.

6. Si rilascia in parallelo, non al posto

Il nuovo programma si installa accanto al vecchio, a un gruppo ristretto di due o tre utenti, per due settimane. Con lo stesso database, lo stesso accesso, gli stessi file. Se emerge un difetto, quegli utenti tornano al vecchio programma con un clic. Quando due settimane passano senza segnalazioni, si estende a tutti. L'ultimo passo è spegnere il vecchio. Non c'è mai un "giorno del grande passaggio" da temere.

Come si porta la logica fuori dalle form?

Prima la regola dello sconto sta dentro il codice del pulsante di una finestra; dopo sta in una classe che si prova con un test e la finestra si limita a chiamarla

Questa è la differenza fra la strada B e la strada C. Mostro il caso più comune: la regola che calcola lo sconto di un ordine, scritta nel codice del pulsante o del campo di una finestra. Succede perché lo strumento ti porta lì: doppio clic sul pulsante e scrivi. Ogni regola finisce nel posto dove è stata scritta la prima volta.

La classe che la contiene, dopo l'estrazione, non sa niente di finestre. Prende dei numeri e restituisce un numero:

using System;

namespace Gestionale.Domain;

public sealed class CalcolatoreSconto
{
    // Sconto per fascia di importo, più un punto in più per i clienti con più di cinque anni.
    public decimal Calcola(decimal importo, int anniCliente)
    {
        if (importo < 0) throw new ArgumentOutOfRangeException(nameof(importo));

        decimal percentuale = importo switch
        {
            >= 10_000m => 0.08m,
            >= 5_000m => 0.05m,
            >= 1_000m => 0.02m,
            _ => 0m
        };

        if (anniCliente > 5 && percentuale > 0) percentuale += 0.01m;

        return Math.Round(importo * percentuale, 2, MidpointRounding.AwayFromZero);
    }
}

Due dettagli su cui conviene fermarsi. Il primo: la classe sta in una libreria destinata a netstandard2.0, quindi la usa il vecchio programma e il nuovo. Il secondo: l'arrotondamento è dichiarato, MidpointRounding.AwayFromZero, cioè il modo in cui si arrotonda a scuola e in contabilità. Quello predefinito di .NET arrotonda al numero pari più vicino, e se la tua regola vecchia dentro la form usava quello predefinito, il nuovo risultato può differire di un centesimo su un ordine ogni tanto. Sono le cose che si scoprono scrivendo un test, e che altrimenti si scoprono dal cliente.

La finestra, dopo, si limita a chiamare la classe:

using Gestionale.Domain;

namespace Gestionale.App.Forms;

public sealed class OrdineForm : Form
{
    private readonly CalcolatoreSconto _sconto;
    private readonly TextBox _importo = new() { Left = 12, Top = 12, Width = 120 };
    private readonly Label _risultato = new() { Left = 12, Top = 48, Width = 240 };

    public OrdineForm(CalcolatoreSconto sconto)
    {
        _sconto = sconto;
        Text = "Ordine";
        Controls.AddRange(new Control[] { _importo, _risultato });
        _importo.TextChanged += (_, _) => Aggiorna();
    }

    private void Aggiorna()
    {
        if (decimal.TryParse(_importo.Text, out var importo) && importo >= 0)
            _risultato.Text = $"Sconto: {_sconto.Calcola(importo, anniCliente: 6):N2}";
        else
            _risultato.Text = "Importo non valido";
    }
}

La finestra riceve la classe dall'esterno (non la crea da sé), e questo permette di dargliene un'altra in una prova. È il piccolo gesto di architettura che rende possibile tutto il resto. Ora la regola si prova senza aprire una finestra:

using Gestionale.Domain;
using Xunit;

namespace Gestionale.Tests;

public class ScontoTests
{
    private readonly CalcolatoreSconto _sconto = new();

    [Theory]
    [InlineData(500, 1, 0)]
    [InlineData(1_000, 1, 20)]
    [InlineData(5_000, 1, 250)]
    [InlineData(5_000, 6, 300)]
    [InlineData(10_000, 6, 900)]
    public void Applica_la_fascia_giusta(decimal importo, int anni, decimal atteso)
        => Assert.Equal(atteso, _sconto.Calcola(importo, anni));

    [Fact]
    public void Rifiuta_gli_importi_negativi()
        => Assert.Throws<ArgumentOutOfRangeException>(() => _sconto.Calcola(-1, 1));
}

Ho compilato questo codice e lanciato i sette test: passano tutti. Non sono molti, ma ognuno è un'affermazione sulla regola che il vecchio programma non aveva mai messo per iscritto. Ed è l'affermazione che confronti con il vecchio quando porti: se il vecchio, con 5.000 euro e un cliente di sei anni, dava 300, anche il nuovo deve dare 300.

Un consiglio pratico. Non estrarre tutte le regole prima di portare. Estrai quelle che stai per toccare: la regola che cambia ogni mese, quella che ha avuto un errore l'anno scorso, quella che il nuovo collega non capisce. Le altre vengono con il tempo, una per volta, ogni volta che le tocchi. In un anno hai fatto metà del lavoro senza aver mai fermato nulla, e senza un progetto "grande".

Come si impedisce alle form di tornare a parlare con il database?

Estrarre la logica è la metà del lavoro. L'altra metà è evitare che, fra due anni, un programmatore con fretta scriva una query dentro il codice di un pulsante. Non serve una regola scritta in un documento che nessuno rilegge. Serve un test che fallisce.

using Xunit;

namespace Gestionale.Tests;

public class ArchitetturaTests
{
    private static readonly string[] Vietati = { "SqlConnection", "SqlCommand", "DbContext" };

    [Fact]
    public void Le_form_non_parlano_con_il_database()
    {
        var radice = TrovaRadice();
        var cartellaForm = Path.Combine(radice, "Gestionale.App", "Forms");

        var colpevoli = Directory.EnumerateFiles(cartellaForm, "*.cs", SearchOption.AllDirectories)
            .Where(f => Vietati.Any(v => File.ReadAllText(f).Contains(v)))
            .Select(Path.GetFileName)
            .ToList();

        Assert.True(colpevoli.Count == 0,
            "Queste form toccano il database direttamente: " + string.Join(", ", colpevoli));
    }

    private static string TrovaRadice()
    {
        var dir = new DirectoryInfo(AppContext.BaseDirectory);
        while (dir is not null && !Directory.Exists(Path.Combine(dir.FullName, "Gestionale.App")))
            dir = dir.Parent;
        return dir?.FullName ?? throw new DirectoryNotFoundException("Radice della soluzione non trovata");
    }
}

Il test cerca nei file delle form le parole che indicano un accesso diretto al database, e fallisce elencando i nomi dei file colpevoli. È rozzo: non capisce il codice, cerca testo. Ma è efficace, costa venti righe, e viene eseguito a ogni compilazione. Se lo fai girare nella pipeline di rilascio, nessuna form con una query dentro arriva più al cliente. Nel mio uso lo affianco a uno strumento di analisi più fine solo quando il gruppo cresce.

Lo metti il giorno in cui finisci la prima estrazione. Se lo metti prima, fallisce subito su ottanta file, e nessuno lo guarderà più. Se lo metti dopo, ha già una cartella pulita da difendere. Il momento giusto è quando la cartella delle form è appena stata ripulita.

Quando conviene davvero la strada web?

La D, la riscrittura sul web, è la più cara a ogni orizzonte. Ma ci sono casi in cui è la risposta giusta, e te li dico perché anche saper dire "no" a una migrazione fa parte del mestiere.

  • Gli utenti non sono più in sede. Se i venditori usano il programma da casa del cliente, o i tecnici dal furgone, un programma da scrivania obbliga a soluzioni di accesso remoto che costano e si rompono. Qui il web non è una moda: toglie un problema.
  • Entrano persone esterne. Clienti che controllano un ordine, fornitori che caricano un documento, consulenti che consultano un report. Ogni utente esterno su un programma per Windows significa installare, assistere, aggiornare.
  • Il numero di sedi è cresciuto. Con cinque sedi da aggiornare, un'installazione su venticinque computer diventa un'installazione su centocinquanta.
  • Dovete poter lavorare da tablet o telefono. Windows Forms non ci arriva.

E i casi in cui il web è uno spreco, che sono i più frequenti. Un programma per l'inserimento veloce di dati con la tastiera: chi digita seicento righe al giorno vuole le scorciatoie e i tempi di risposta di un'applicazione che gira sul suo computer, e un'interfaccia web, anche ben fatta, è spesso più lenta nel suo uso più intenso. Un programma che parla con periferiche (una bilancia sulla porta seriale, una stampante di etichette, un lettore di codici): si può fare anche sul web, con più fatica. Un programma che usa solo venticinque persone, nella stessa sede, con computer che l'azienda controlla: la comodità del web non porta niente.

C'è una via di mezzo che per molte aziende è la migliore, ed è il motivo per cui ho messo la C come strada autonoma. Si porta il programma a .NET 10 e si estrae la logica. Poi, se serve un accesso da fuori per tre sole funzioni (il cliente che controlla lo stato di un ordine, il venditore che apre un'offerta, il tecnico che chiude un intervento), si costruiscono quelle tre pagine web usando le classi già estratte. Hai pagato la riscrittura di tre schermate, non di ottanta. Il resto del programma continua a essere un programma da scrivania, che è quello che i tuoi utenti vogliono.

È lo stesso ragionamento della guida sulla migrazione di programmi aziendali a Blazor: si sposta sul web ciò che deve stare sul web, non tutto.

Che cosa succede se si aspetta troppo?

Il conto che ti ho mostrato assegna un costo medio di restare. Non tiene conto del caso peggiore, che è quello che spaventa chi guida un'azienda: il giorno in cui si è costretti a migrare in fretta. Succede in tre modi.

Il primo è la persona. Il programmatore che conosce il gestionale se ne va, e la persona che lo sostituisce non vuole, o non sa, lavorare con strumenti di quindici anni fa. Si trova, ma a un costo diverso e con una curva di apprendimento più lunga. Capita spesso che un'azienda paghi mesi di consulenza solo per far capire il programma a un sostituto: un costo che, sommato, supera quello di portarlo quando si era ancora in tempo.

Il secondo è il componente. Il fornitore della suite di griglie o di report smette di supportare la versione che usi. All'inizio non succede niente. Poi arriva un aggiornamento di Windows, o un nuovo certificato, e una finestra smette di funzionare. Nel mondo delle librerie di terze parti, il giorno in cui smettono di funzionare è sempre un giorno a ridosso di una scadenza.

Il terzo è il cliente. Un cliente grosso, o un'assicurazione, o un bando, chiede una dichiarazione sulla versione del software in uso e sulle sue vulnerabilità. Un programma su una piattaforma senza evoluzione non è automaticamente insicuro, ma la risposta diventa difficile da dare, e a volte il contratto dipende da quella risposta.

Nel conto, la migrazione fatta in emergenza costa tipicamente fra una volta e mezza e due volte quella pianificata: giornate di straordinario, nessun tempo per fare i controlli, prezzi di fornitore che salgono quando hai fretta, e il fermo vero, quello che non è nella tabella. Per la tua azienda tipo vuol dire, a seconda di quanta logica sta nelle form, fra 71.000 e 95.000 euro per la B invece di 47.280 (38.400 di costruzione più 8.880 di avviamento). Non lo metto nelle tabelle perché è un rischio e non un costo certo, ma è il motivo per cui non consiglio mai di restare come strategia: è una scelta da fare con una data di scadenza.

Se scegli la A, dichiaralo come tale. Scrivi sul calendario l'anno entro cui rifai il conto, e un segnale che ti obbliga a guardarlo prima: la partenza del programmatore, la fine del supporto di un componente, la richiesta di un cliente. Una decisione di rinviare con una data è un'ottima decisione. Una di rinviare senza una data è la strada più costosa.

Quanto conta l'intelligenza artificiale in tutto questo?

Conta, e cambia i numeri in un punto preciso. Gli assistenti di programmazione di oggi sono molto bravi in due lavori che pesano nella nostra stima: la conversione meccanica del codice (cambiare i riferimenti, aggiornare i pacchetti, adattare le chiamate a librerie nuove) e la scrittura dei test per codice che esiste già e di cui nessuno conosce più le regole.

La prima parte riduce la costruzione. Come ipotesi prudente, non come misura, conta di risparmiare un quinto della conversione delle 80 form quando c'è un gruppo che sa rivedere ciò che l'assistente produce, e quasi nulla quando non c'è nessuno che sappia riconoscere un errore. La seconda parte è quella che più mi interessa. Scrivere i test di caratterizzazione di una regola che esiste solo nel codice di un pulsante è un lavoro noioso e cosciente, da fare prima di toccare la regola. Un assistente lo fa bene, a patto che tu verifichi i valori attesi contro il comportamento reale del vecchio programma.

Non cambia il conto di fondo: serve sempre qualcuno che apra ogni finestra e la provi con una persona che la usa. Non cambia la quota di logica nelle form, né l'anno in cui conviene muoversi. Cambia la pendenza della costruzione. Se vuoi capire come si inserisce l'IA nel lavoro di un gruppo di sviluppo senza perdere il controllo, c'è la guida sull'adozione dell'IA nei gruppi di sviluppo.

Quali errori evitare?

  • Riscrivere sul web "perché Windows Forms è vecchio". Il criterio non è l'età, è dove lavorano gli utenti e che cosa devono fare. Un programma da scrivania per venticinque persone nella stessa sede si porta, non si riscrive. Riscrivere ti costa circa 60.000 euro in più della B a ogni orizzonte, per un vantaggio che non userai.
  • Chiedere un prezzo senza il censimento. Se nessuno ha aperto il progetto e contato i componenti di terze parti, il prezzo è una stima su una scatola chiusa. Il censimento si fa in due o tre giorni e si paga da solo.
  • Portare e cambiare l'aspetto nello stesso momento. Una migrazione che cambia anche il modo di lavorare degli utenti perde la prova più importante: "il nuovo fa quello che faceva il vecchio". Prima si porta, poi si migliora.
  • Estrarre la logica prima di portare. È la tentazione dei programmatori bravi: sistemare tutto e poi portare. Così il progetto dura il doppio e non ha un punto in cui è utilizzabile. Si porta, si rilascia, e poi si estrae un pezzo alla volta, a partire dalle regole che si toccano più spesso.
  • Dimenticare la distribuzione. Il programma nuovo deve arrivare ai venticinque computer. Se il vecchio si installava con una cartella di rete e il nuovo richiede un'altra procedura, quella procedura va decisa all'inizio, non l'ultima settimana. Il runtime di .NET si può includere nel programma, e l'installazione resta un'operazione semplice.
  • Non misurare. I tempi di apertura delle dieci finestre più usate, prima e dopo. Senza il "prima" non c'è modo di dire che è andata meglio, e ti ritrovi a discutere di sensazioni con chi ha l'abitudine al vecchio.
  • Dimenticare il fallback. Il vecchio programma resta installabile fino al giorno in cui il nuovo ha vissuto un mese senza problemi.

Quando è meglio non scegliere nessuna di queste strade?

Succede, e dirlo fa parte del mestiere. Tre casi.

  • Il programma è quasi fermo e vivrà pochi anni. Se cambia meno di trenta volte l'anno e ha davanti tre o quattro anni, la tabella dice A. L'unica cosa da fare è la copia di sicurezza ripristinabile, il programma installabile da qualche parte, e un nome nel calendario. Non è pigrizia, è un conto.
  • Quello che fa il programma lo fa anche un prodotto che si compra. Se ottanta finestre fanno quello che farebbe un gestionale in abbonamento, costruire su misura non conviene, e vale la pena chiedersi se è così prima di spendere 150.000 euro. La pagina sul software su misura parte esattamente da questa domanda.
  • Il vero problema non è la tecnologia. A volte il gestionale è lento perché il database non ha un indice, o perché una query legge un milione di righe per mostrarne dieci. Un'ora di analisi può togliere il motivo per cui si pensava di migrare. Prima di spendere, misura dove va il tempo.

Se vuoi un secondo parere sul tuo caso, la strada è la consulenza software. Per il conto completo di cosa costa restare fermi con un programma vecchio c'è la guida al costo del software legacy, e per la migrazione dell'intera piattaforma quella sul passaggio da .NET Framework a .NET 10.

In cinque righe. Conta le modifiche l'anno e gli anni che il programma deve ancora vivere. Misura quanta logica sta dentro le form. Sotto le 500 modifiche in totale resta dov'è, con una data di riesame scritta. Sopra le 1.000 portalo a .NET 10 e togli la logica dalle form un pezzo alla volta, con un test che lo difende. Porta sul web soltanto le funzioni che devono stare fuori sede, e non l'intero programma.

Domande frequenti

Sì, in due forme. Su .NET Framework 4.8 è supportato finché lo è il sistema operativo Windows, ma non riceve funzioni nuove. Su .NET 10, versione a supporto lungo uscita a novembre 2025 e supportata fino a novembre 2028, è un prodotto vivo che riceve correzioni e miglioramenti. Resta una tecnologia solo per Windows.

Dipende da quanti anni deve ancora vivere e da quante modifiche riceve. Per un gestionale da 80 form con 120 modifiche l'anno, restare su .NET Framework costa meno fino al quinto anno e portarlo a .NET 10 costa meno dal sesto: a otto anni sono circa 254.547 euro contro 202.895 portando ed estraendo la logica.

Per un'azienda tipo con 80 form e il 60% della logica dentro le form, circa 47.280 euro tra costruzione (38.400) e avviamento (8.880), più la manutenzione. Estrarre anche la logica dalle form aggiunge circa 20.700 euro e rende meno cara ogni modifica successiva. I componenti di terze parti sono la voce che sposta di più il preventivo.

Quasi mai per un programma usato da poche decine di persone nella stessa sede: la riscrittura sul web costa circa 60.000 euro in più del porting a ogni orizzonte. Ha senso quando gli utenti sono fuori sede, quando entrano clienti o fornitori, o quando serve da tablet o telefono. La via di mezzo è portare il programma ed esporre sul web solo le funzioni che servono fuori sede.

Si conta la quota di righe di codice che sta nei file delle form rispetto a tutto il codice scritto a mano, escludendo i file del disegnatore e le cartelle di compilazione. Un secondo controllo conta le form che contengono SqlConnection, SqlCommand o DbContext: sono finestre che parlano direttamente con il database e non si possono provare da sole.

Si porta per primo il codice senza finestre in una libreria che compila sia per .NET Framework 4.8 sia per .NET 10, poi si converte il progetto dell'eseguibile, si prova ogni finestra con una persona che la usa, e si rilascia il nuovo accanto al vecchio a due o tre utenti per due settimane. Il vecchio resta installabile fino a quando il nuovo ha vissuto un mese senza problemi.

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.