Come migrare un progetto da ASP.NET MVC a ASP.NET CORE Una guida passo per passo che illustra i passaggi fondamentali per convertire un progetto ASP.NET MVC in un'applicazione basata su ASP.NET CORE

How to fix the "No executable found matching command dotnet-ef" error in Visual Studio with .NET Core

Quella che segue è una guida pratica su come procedere alla migrazione di un progetto dal framework ASP.NET MVC verso ASP.NET Core. Le indicazioni passo passo scritte dal team del progetto open-source nopCommerce possono essere facilmente applicate a qualsiasi altro progetto ASP.NET MVC.

La guida spiega anche le ragioni per cui potrebbe essere opportuno passare ad ASP.NET Core soprattutto per tutti quei progetti non ancora a pieno regime.

Perchè migrare verso ASP.NET Core?

Prima di iniziare con i primi passi della guida (utilizzando come esempio il progetto nopCommerce), diamo un rapido sguardo ai vantaggi di questo framework.

  • Sicurezza: ASP.NET Core è un framework già piuttosto maturo,noto e diffuso e ha già alle spalle diversi aggiornamenti che lo rendono piuttosto stabile, tecnologicamente avanzato e resistente ad attacchi XSRF/CSRF.
  • Multipiattaforma: è uno degli aspetti che contraddistingue ASP.NET Core rendendolo estremamente popolare. Le applicazioni sviluppate in ASP.NET Core possono girare indifferentemente tanto in ambiente Windows che in ambiente Unix.
  • Architettura Modulare: ASP.NET Core viene fornito sotto forma di pacchetti NuGet, e questo consente di ottimizzare le applicazioni includendo i pacchetti necessari. Questo aspetto migliora le prestazioni di ogni soluzione e riduce il tempo richiesto per l’aggiornamento dei singoli componenti.

Questa è la seconda importante caratteristica che consente agli sviluppatori di integrare agevolmente nuove funzionalità nella loro soluzione.

Le performance sono un altro punto di forza. ASP.NET Core può gestire 23 volte (2.300%)  più richieste al secondo di ASP.NET 4.6, ed 8 volte (800%) più richieste al secondo di node.js.

Puoi controllare un test dettagliato sulle perfomance nella screenshot seguente:

Come migrare un progetto da ASP.NET MVC a ASP.NET CORE

Risultati del test di numero massimo di risposte al secondo in base al framework

Il Middleware è una nuova pipeline dell’app leggera e rapida per la gestione delle richieste. Ogni componente del middleware si occupa di processare una richiesta HTTP, e può decidere di restituire un risultato o passare la richiesta al componente successivo.

Questo approccio conferisce allo sviluppatore un pieno controllo della pipeline HTTP e contribuisce allo sviluppo di semplici moduli. Tutto ciò questo costituisce un fattore molto importante soprattutto per progetti open-source.

Oltretutto ASP.NET Core offre funzionalità che semplificano lo sviluppo di progetti web. nopCommerce utilizzava già alcune di queste caratteristiche, ad esempio il modello Model-View-Controller, la sintassi Razor, il model binding, e la validazione. Quella che segue invece è una lista delle caratteristiche più rilevanti introdotte con ASP.NET Core:

  • Helper Tag (Tag helper). Parti di codice server che si occupano della creazione e rendering di elementi HTML nei file Razor.
  • Componenti di Visualizzazione (View component). Un nuovo strumento simile alle viste parziali (partial view), ma con performance decisamente superiori. nopCommerce usa i componenti di visualizzazione ogni qualvolta sia necessario riutilizzare parti rilevanti di logica nel rendering.
  • Inserimento delle Dipendenze (Dependency injection) nelle viste. Sebbene buona parte dei dati visualizzati nelle viste provenga dal controller, nopCommerce utilizza in alcuni casi la dependency injection direttamente nelle viste.

Ovviamente ASP.NET Core ha moltissime altre caratteristiche, quelle che abbiamo indicato sono semplicemente quelle più interessanti.

Consideriamo adesso i punti da tenere in mente quando si migra un progetto su un nuovo framework.

Migrazione

I contenuti che seguono contengono parecchi link alla documentazione ufficiale di ASP.NET Core per offrire informazioni estremamente dettagliate su questo argomento e una guida completa per gli sviluppatori che si trovino a dover affrontare questo delicato compito per la prima volta.

Fase 1. Accertarsi di avere a disposizione tutti gli strumenti necessari

La prima cosa da fare è accertarsi di avere Visual Studio aggiornato alla versione 15.3 o successiva ed installare l’ultima versione dell’SDK .NET Core.

Un altro strumento utile da utilizzare prima di iniziare il processo di migrazione è .Net Portability Analyzer. Questo tool può costituire un ottimo punto partenza per farsi una prima idea di quanto complessa possa essere la migrazione del progetto verso la nuova piattaforma. E’ opportuno precisare che lo strumento non copre tutte le criticità della migrazione per cui molti è assai probabile che nel corso del processo emergano imprevisti che dovranno essere risolti di volta in volta.

Gli step che descriveremo da qui in avanti sono quelli seguiti nella migrazione del progetto nopCommerce; il primo passo è stato aggiornare i link alle librerie usate nel progetto in modo che fosserp compatibili con .NET Core.

Fase 2. Analisi dei pacchetti NuGet per verificare la compatibilità con gli standard .Net

Se in un progetto si utilizzano pacchetti NuGet occorre assicurarsi che questi siano compatibili con .NET Core: a tal proposito, un ottimo strumento per verificare la compatibilità dei pacchetti è NuGetPackageExplorer.

Fase 3. Il nuovo formato del file csproj in .NET Core

Con .NET Core è stato introdotto un nuovo metodo per l’aggiunta di riferimenti a pacchetti di terze parti. Quando si aggiunge una nuova libreria di classi occorre aprire il file di progetto e modificare il contenuto in questo modo:

I riferimenti alle librerie collegate saranno caricati automaticamente.

Per maggiori informazioni sulle differenze tra i file project.json and CSPROJ, leggere la documentazione ufficiale qui e qui.

Fase 4. Aggiornamento dei Namespace

Per aggiornare i namespace, sarà sufficiente eliminare tutti i riferimenti al namespace System.Web e sostituirli con Microsoft.AspNetCore.

Fase 5. Utilizzare Startup.cs piuttosto che Global.asax per la configurazione dell’applicazione

ASP.NET Core ha un nuovo sistema per avviare l’app. Il punto di ingresso (entry point) è Startup ed è in questo file nel metodo Configure che i middleware vengono aggiunti alla pipeline.

Elementi da Gestire nel file Startup.cs:

  • Aree. Per aggiungere aree in una applicazione ASP.NET Core, occorre aggiungere una route al file Startup.cs.

Utilizzo delle Aree

Il concetto di Aree (Areas in lingua inglese) è particolarmente importante, e per questo merita un approfondimento ulteriore. Nel codice seguente possiamo vedere come è possibile aggiungere un’area Admin:

In conseguenza alla configurazione di cui sopra, nella root dell’applicazione andremo a creare una cartella chiamata Areas all’interno della quale dovrà essere presente una cartella Admin.

Come migrare un progetto da ASP.NET MVC a ASP.NET CORE

L’attributo [Area("Admin")]  potrà a quel punto essere utilizzato per collegare il controller a quest’area:

Le informazioni relative al routing, necessarie per istruire il middleware MVC su quale controller eseguire in base alla URL richiesta dalla HTTP request, sono definite nell’attributo [Route("admin")] , utilizzando la tecnica nota come attribute-based routing introdotta a partire dalla versione 5 di ASP.NET MVC.

Per maggiori informazioni sull’attribute-based routing consigliamo di dare un’occhiata a questo articolo.

Fase 6. Migrazione dei gestori e dei moduli HTTP verso il Middleware

Gli handler e i moduli HTTP si avvicinano molto al concetto di Middleware in ASP.NET Core, ma a differenza dei moduli l’ordine dei middleware si basa sull’ordine in cui vengono aggiunti alla pipeline. L’ordine dei moduli si basa principalmente sugli eventi del ciclo di vita dell’applicazione. L’ordine del middleware per le risposte è l’opposto dell’ordine per le richieste mentre l’ordine dei moduli per richieste e risposte è esattamente lo stesso. Avendo chiaro in mente questo concetto, si può procedere all’aggiornamento.

Cosa dovrebbe essere aggiornato:

  • Migrazione di moduli verso il middleware (AuthenticationMiddleware, CultureMiddleware, etc.)
  • Migrazione dei gestori verso il middleware
  • Utilizzo del nuovo strumento middleware

L’autenticazione in nopCommerce non si basa su alcun sistema predefinito; viene invece utilizzato un AuthenticationMiddleware personalizzato, sviluppato secondo la nuova struttura di ASP.NET Core, nel seguente modo:

ASP.NET offre molti middleware già pronti per l’uso, ma ogni sviluppatore può creare i propri middleware personalizzati ed aggiungerli alla pipeline della richiesta. Per semplificare questo processo, nel progetto nopCommerce abbiamo aggiunto una interfaccia specifica (INopStartup) che ogni nuovo middleware deve implementare.

Ecco un esempio di come è possibile aggiungere e configurare un middleware custom:

Fase 7. Utilizzo della Dependency Injection di ASP.NET Core

L’inserimento delle dipendenze (Dependency injection) è uno degli aspetti chiave quando si progetta un’app  in ASP.NET Core. Con questa tecnica è possibile sviluppare applicazioni cosiddette “loosely coupled” (applicazioni dove ciascun componente conosce ed utilizza meno informazioni possibili degli altri componenti)  più testabili, modulari e dunque più facili da gestire.

Per iniettare dipendenze si usano i contenitori IoC (Inversion of Control). In ASP.NET Core, questo contenitore è rappresentato dall’interfaccia IServiceProvider. I servizi vengono installati nell’app nel metodo Startup.ConfigureServices().

Ogni servizio può essere configurato con una di queste diverse durate (scope):

  • temporaneo (transient)
  • con ambito (scoped)
  • singleton

Fase 8. Utilizzo della shell di compatibilità con progetti WebAPI (Shim)

Per semplificare la migrazione di Web API esistenti suggeriamo di utilizzare il pacchetto NuGet Microsoft.AspNetCore.Mvc.WebApiCompatShim che supporta queste funzionalità compatibili:

  • Aggiunta di in un tipo ApiController;
  • Associazione dei modelli secondo la modalità web API;
  • Estensione dell’associazione dei modelli in modo che le azioni del controller possano accettare parametri di tipo HttpRequestMessage;
  • Aggiunta di strumenti di formattazione di messaggi che abilitano le azioni a restituire risultati del tipo HttpResponseMessage.

Fase 9. Migrazione della Configurazione dell’Applicazione

Precedentemente alcune impostazioni venivano salvate nel web.config. Adesso si utilizza un nuovo approccio basato su un sistema di coppie chiave-valore impostate tramite provider di configurazione. Questo è il sistema raccomandato da ASP.NET Core e nel nostro progetto noi usiamo il file appsettings.json.

Se per qualsiasi ragione si volesse continuare ad utilizzare file *.config è comunque possibile utilizzare il pacchetto NuGet System.Configuration.ConfigurationManager, anche se in questo caso l’applicazione perderà la sua portabilità e potrà girare esclusivamente su IIS.

E’ anche possibile utilizzare come provider di configurazione Azure key storage, In questo caso, che non è quanto abbiamo fatto nel nostro progetto, si può fare riferimento a questa guida.

Fase 10. Migrazione dei contenuti statici su wwwroot

Per quanto riguarda i contenuti statici occorre definire una directory radice web. Questa directory di default è  wwwroot ma è possibile configurare una cartella differente indicandola nel middleware.

Come migrare un progetto da ASP.NET MVC a ASP.NET CORE

Fase 11. Migrazione di Entity Framework verso EF Core.

Se il progetto usa alcune caratteristiche specifiche di Entity Framework 6, non supportate da EF Core, probabilmente è opportuno eseguire l’applicazione sul Framework .NET rinunciando alla portabilità dell’app che in questo caso potrà girare solo in ambiente Windows con server IIS.

Quello che segue è un elenco delle principali modifiche di cui sarà necessario tener conto:

  • il namespace System.Data.Entity è sostituito da Microsoft.EntityFrameworkCore;
  • La signature del costruttore DbContext è stata modificata. Adesso occorre iniettare DbContextOptions;
  • Il metodo HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) è stato sotituito da ValueGeneratedNever();
  • Il metodo WillCascadeOnDelete(false) è stato sostituito da OnDelete(DeleteBehavior.Restrict);
  • Il metodo OnModelCreating(DbModelBuilder modelBuilder) è stato sostituito da OnModelCreating(ModelBuilder modelBuilder);
  • Il metodo HasOptional non esiste più;
  • L’oggetto configuration è cambiato, e poichè l’attributo ComplexType non è più disponibile, viene utilizzato OnModelCreating;
  • L’interfaccia IDbSet è stata sostituita da DbSet;
  • ComplexType – il supporto per i ComplexType è apparso prima in EF Core 2 con il tipo di entità Owned, e poi con EF Core 2.1 con il supporto per tabelle senza chiave primaria con QueryType;
  • Chiavi esterne in proprietà shadow EF Core, generate usando il Template [Entiy]Id a differenza di EF6 che usa [Entity]_Id.
    Su EF Core aggiungere le chiavi all’entità come normali proprietà;
  • Per supportare la Dependency Injection per DbContext, è necessario configurare il DbContex in ConfigureServices.

Per verificare che EF Core generi una database con la stessa struttura di quello generato da Entity Framework è possibile utilizzare SQL Compare.

Fase 12. rimuovere tutti i riferimenti a HttpContext, sostituire tutte le vecchie classi ed aggiornare i namespace

Durante la migrazione del progetto ci si accorgerà che un gran numero di classi sono state rinominate o spostate sotto altri namespace ed ora è necessario attenersi ai nuovi requisiti.

Ecco una lista delle modifiche principali in cui ci si potrà imbattere:

  • HttpPostedFileBase 🡪 FormFile
  • l’accesso ad HttpContext adesso può avvenire tramite IHttpContextAccessor
  • HtmlHelper 🡪 HtmlHelper
  • ActionResult 🡪 ActionResult
  • HttpUtility 🡪 WebUtility
  • ISession anzichè HttpSessionStateBase accessibile da HttpContext.Session nel namespace Microsoft.AspNetCore.Http
  • Cookies restituisce IRequestCookieCollection: un IEnumerable <KeyValuePair<string, string> >, per cui al posto di HttpCookie possiamo utilizzare KeyValuePair <string, string> nel namespace Microsoft.AspNetCore.Http

Modifiche nei Namespace

  • SelectList 🡪 Microsoft.AspNetCore.Mvc.Rendering
  • UrlHelper 🡪 WebUtitlity
  • MimeMapping 🡪 FileExtensionContentTypeProvider
  • MvcHtmlString 🡪 IHtmlString and HtmlString
  • ModelState, ModelStateDictionary, ModelError 🡪 Microsoft.AspNetCore.Mvc.ModelBinding
  • FormCollection 🡪 IFormCollection
  • Url.Scheme 🡪 this.Url.ActionContext.HttpContext.Request.Scheme

 Altre modifiche

  • IsNullOrEmpty(IHtmlString) 🡪 String.IsNullOrEmpty(variable.ToHtmlString())
  • [ValidateInput (false)] – non esiste più e non è necessario
  • HttpUnauthorizedResult 🡪 UnauthorizedResult
  • [AllowHtml] – la direttiva non esiste più e non è necessaria
  • SetInnerText – il metodo è sostituito da InnerHtml.AppendHtml
  • AllowGet quando restituisce Json non è più necessario
  • JavaScriptStringEncode. JavaScriptEncoder.Default.Encode
  • RawUrl. Request.Path + Request.QueryString dovrebbe essere connesso separatamente
  • AllowHtmlAttribute – la classe non esiste più
  • XmlDownloadResult – adesso si può utilizzare semplicemente return File(Encoding.UTF8.GetBytes (xml), “application / xml”, “filename.xml”);
  • [ValidateInput(false)] – la direttiva non esiste più e non è necessaria

Fase 13. Aggiornamento delle funzionalità di Autenticazione ed autorizzazione

Come già detto in precedenza, il progetto nopCommerce non utilizza il sistema di autenticazione predefinito ma bensì un sistema implementato nel middleware.

Comunque ASP.NET Core ha un suo sistema per l’autenticazione che è possibile approfondire nella documentazione ufficiale.

Per la protezione dei dati piuttosto che il MachineKey si utilizza il sistema di protezione predefinito. Di regola le chiavi sono generate all’avvio dell’applicazione e come sistema di storage è possibile scegliere tra queste opzioni:

  • File system – storage basato su file system
  • Azure Storage – storage su oggetto Azure BLOB
  • Redis – storage sul sistema di cache Redis
  • Registry – utilizzato se le l’applicazione non ha accesso al file system
  • EF Core – storage su database

Se non è possibile utilizzare i provider predefiniti si può specificare un proprio provider imlpementando un IXmlRepository custom.

Fase 14. Aggiornamento di JS e CSS

Il modo in cui vengono utilizzate le risorse statiche è cambiato, adesso devono essere posizionate nella cartella wwwroot a meno che non venga impostata una diversa posizione.

Quando si utilizzano blocchi javascript predefiniti raccomandiamo di posizionarli alla fine della pagine utilizzando l’attributo del tag script asp-location = “Footer”. Stesso discorso per i file js.

Utilizzare l’estensione BundlerMinifier al posto di System.Web.Optimization. L’estensione si occupa di creare bundle e di minificare il javascript e css in fase di compilazione (leggi la documentazione).

Fase 15. Migrazione delle viste

Innanzitutto le Child Action non sono più utilizzate ed al suo posto ASP.NET Core raccomanda l’utilizzo di uno strumento con elevate performance – Componenti di Visualizzazione (ViewComponent) – che viene chiamato in modo asincrono.

Ecco come ricevere una stringa da un componente:

Non è più necessario utilizzare HtmlHelper, ASP.NET Core include molti Tag Helper predefiniti. Quando l’applicazione è in esecuzione, Razor li gestisce lato server ed infine li converte in elementi html standard.

Ciò rende lo sviluppo di applicazioni molto più semplice. Ovviamente è possibile implementare propri tag helper.

Abbiamo iniziato ad utilizzare la dependency injection nelle viste piuttosto che abilitare impostazioni e servizi utilizzando EngineContext.

Quindi gli elementi principali da tenere a mente nella migrazione delle viste sono i seguenti:

  • Conversione di Views/web.config in Views/_ViewImports.cshtml – per importare namespaces ed iniettare le dipendenze. Questo file non supporta altre funzionalità di Razor quali ad esempio le definizioni di sezione.
  • Conversione di namespaces.add in @using
  • Migrazione delle impostazioni verso il sistema di configurazione dell’applicazione
  • Render e Styles.Render non sono più disponibili, vanno sostituiti da link a dati di output di libman o BundlerMinifier

Conclusioni

La migrazione di una applicazione web complessa è un processo lungo che necessariamente presenta ostacoli e imprevisti. Noi abbiamo pianificato la migrazione verso il nuovo framework in modo che la prima versione stabile del progetto venisse rilasciata al più presto. Tuttavia così facendo non abbiamo potuto trasferire l’intero progetto su .NET Core, ed in particolare sono rimaste fuori tutte le funzionalità relative ad EntityFramework.

Pertanto per la nostra prima release abbiamo utilizzato un approccio misto – l’architettura .NET Core con i riferimenti al Framework .NET. Essere stati i primi non è stato facile, ma siamo certi di aver preso la decisione corretta e la nostra grande comunità ci ha supportati: siamo riusciti ad adattare completamente il nostro progetto solo dopo la release di .NET Core 2.1, avendo già per quella data una soluzione stabile già funzionante e basata sulla nuova architettura. A quel punto rimaneva solo da sostituire qualche pacchetto e riscrivere il lavoro con EF Core. Così abbiamo dovuto impiegare solo pochi mesi e due release per migrare completamente il progetto verso il nuovo framework. Possiamo dire con una certa serenità che nopCommerce è il primo grande progetto sottoposto ad una migrazione di questa natura.

In questa guida abbiamo provato a mettere insieme l’intero processo di migrazione in una forma strutturata e a descrivere vari colli di bottiglia in modo che altri sviluppatori possano basarsi su questo materiale e seguire la roadmap nella soluzione degli stessi task.

Articolo tradotto da Os2 web agency

 

About Vincenzo Micciché

Vincenzo Micciché è socio fondatore di Os2 dal 1998. Come responsabile tecnico per conto della Os2 ha seguito la realizzazione di numerosi progetti web. Negli ultimi anni si dedicato in particolare ai progetti ecommerce basati su CMS NopCommerce.

View all posts by Vincenzo Micciché

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.