Lecciones aprendidas tras migrar más de 25 proyectos a .NET Core

21/10/2020Artículo original

Hace poco terminamos una de las mayores tareas de refactorización que hemos hecho en elmah.io: migrar todo a .NET Core. elmah.io consta actualmente de 5 aplicaciones web y 57 funciones de Azure repartidas en aproximadamente 25 Function Apps. En este post, compartiré algunas de las lecciones que hemos aprendido mientras llevábamos a cabo esta tarea.

Vamos al grano. He dividido este post en tres categorías. Una sobre los problemas de migración de .NET Framework a .NET Core en general. Otra específicamente sobre la migración de ASP.NET a ASP.NET Core. Y la tercera sobre la migración de las Azure Functions a la versión más reciente. Siéntete libre de sumergirte en el tema que más te interese. Algunos de los contenidos están divididos en temas más específicos que nos fuimos encontrando, mientras que otros son más bien una descripción textual de dónde nos encontramos actualmente.

.NET Core

Para empezar con buenas noticias, esperaba muchos más problemas al migrar el código de .NET Framework a .NET Core. Cuando empezamos a experimentar con la migración, .NET Core estaba en la versión 1.x y faltaban aún muchas características de .NET Framework “clásico”. Desde la versión 2.2 en adelante, no recuerdo haber echado de menos nada. Si saltas directamente a la última versión estable de .NET Core (no veo motivos por lo que no debieses hacerlo), hay muchas probabilidades de que tu código se compile y funcione sin necesidad de realizar ningún cambio.

Hay que estar atento a los niveles de soporte

Una cosa que debes tener en cuenta cuando saltes de .NET “clásico” a .NET Core, es un despliegue más rápido de las nuevas versiones. Eso incluye también intervalos de soporte más cortos. Con .NET “clásico”, 10 años de soporte eran lo más normal, cuando ahora los 3 años de soporte de .NET Core son el periodo que se ofrece. Además, cuando se elige la versión de .NET Core con la que quieres compilar, hay que mirar el nivel de soporte que te proporciona cada versión. Microsoft marca ciertas versiones con soporte de largo plazo (LTS) que es alrededor de 3 años, mientras que otras son versiones intermedias. Estables, pero aún así versiones con un período de soporte más corto. En general, estos cambios requieren que actualices la versión .NET Core más a menudo de lo que estás acostumbrado o asumir que vas a ejecutar tus aplicaciones en una versión del framework ya no soportada.

  Tutorial SQL #6: Agrupaciones y funciones de agregación

Aquí tienes una buena visión general de las diferentes versiones y sus niveles de soporte: Política de soporte de .NET Core.

ASP.NET Core

Migrar nuestros sitios web ASP.NET MVC a ASP.NET Core ha sido la mayor tarea de todas. No quiero asustarte para que no migres y, de hecho, la mayor parte del tiempo lo pasamos migrando algunos viejos frameworks de autenticación y haciendo tareas similares no relacionadas con .NET Core. La parte MVC en ASP.NET Core funciona de manera muy parecida a la antigua y se llega muy lejos haciendo buscar y reemplazar global con algunos patrones. En las siguientes secciones, he enumerado varios problemas con los que nos encontramos durante la migración.

Cómo hacer la actualización

La ruta de actualización no es exactamente directa. Puede que existan algunas herramientas que te ayudan con ello, pero al final acabé migrando todo a mano. Para cada sitio web, hice una copia de todo su repositorio. Después borré todos los archivos de la carpeta de trabajo y creé un proyecto ASP.NET Core MVC nuevo. Luego porté cada cosa una por una. Empezando por copiar los controladores, vistas y modelos y haciendo uso de algunos patrones globales de búsqueda-reemplazo para lograr que compilase. Casi todo fue distinto a partir de ahí. Estábamos usando un viejo marco de trabajo de autenticación, y lo portamos a la característica de autenticación que trae de serie .NET Core (no a Identity). Lo mismo con Swagger, que requiere un nuevo paquete NuGet para .NET Core. etc. Nos llevó mucho tiempo migrar con seguridad el sitio web principal.

Entendiendo el middleware

Como seguramente ya sabrás, muchas de las características de ASP.NET Core están construidas sobre middlewares. El concepto de middleware no existe en ASP.NET y por lo tanto es algo que debes aprender. La forma en que configuras los middlewares y especialmente el orden en el que los instalas es algo que nos ha causado un cierto dolor de cabeza. Mi recomendación sería que estudies muy a fondo la documentación de Microsoft para este caso. Además, Andrew Lock escribió una larga serie de artículos de alta calidad sobre middleware en los que recomiendo a todo el mundo que se sumerja: https://andrewlock.net/tag/middleware/

Newtonsoft.Json vs System.Text.Json

Hasta ASP.NET Core 3.0, la serialización y deserialización de JSON se llevaba a cabo con el extremadamente popular paquete Newtonsoft.Json. Microsoft decidió lanzar su propio paquete para lo mismo en ASP.NET Core 3.0 y versiones posteriores, lo que causó algunos problemas al actualizar de la versión 2.2. a la 3.1.

  Un estudio sugiere que el ruido no afecta el trabajo de los desarrolladores, excepto cuando toca arreglar bugs

System.Text.Json parece que ya es una buena implementación y, después de algunas pruebas, decidimos ir adelante con ello (es la opción por defecto en cualquier caso). Rápidamente descubrimos que Newtonsoft.Json y System.Text.Json no son compatibles en la manera en la que serializan y deserializan los objetos C#. Como nuestro cliente usa Newtonsoft.Json para serializar JSON, experimentamos algunos escenarios en los que el cliente generaba JSON que no podía ser deserializado a C# en el servidor. Mi recomendación, en caso de que estés usando Newtonsoft.Json en el cliente, es usar también Newtonsoft.Json en el servidor:

services .AddControllersWithViews() .AddNewtonsoftJson();

Después de llamar a AddControllersWithViews o cualquier otro método que llames para configurar los endpoints, puedes llamar al método AddNewtonsoftJson para que ASP.NET Core utilice ese paquete.

El cambio necesita de un paquete NuGet adicional:

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson

Mayúsculas y minúsculas en JSON

Un problema al que nos enfrentamos en nuestra aplicación principal fue el uso de mayúsculas y minúsculas al serializar JSON. Cuando la acción de un controlador de ASP.NET Web API devuelve JSON:

return Json(new { Hello = "World" });

El JSON devuelto usa “pascal case” (o sea, todas las primeras letras de las palabras en mayúsculas):

{"Hello":"World"}

Pero cuando se devuelve lo mismo desde ASP.NET Core, el JSON devuelto usa “camel case” (la primera letra de la primera palabra en minúsculas, el resto en mayúsculas):

{"hello":"World"}

Si tú, al igual que nosotros, ya tienes un cliente basado en JavaScript que espera que “pascal case” para los objetos serializados desde C#, puede cambiar el “casing” con esta opción:

services .AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });

Compilación en tiempo de ejecución de Razor

Aunque portar las vistas de Razor de ASP.NET MVC a ASP.NET Core no requirió mucho trabajo, no tenía claro que las vistas Razor no se compilaban en tiempo de ejecución en el caso de ASP.NET Core. Escribí un artículo en el blog sobre ello: Agregar compilación en tiempo de ejecución para Razor al desarrollar con ASP.NET Core. En resumen: es necesario marcar la opción “Habilitar la compilación en tiempo de ejecución” de Razor al crear el proyecto, o bien instalar el paquete NuGet Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation y habilitar la compilación en tiempo de ejecución en Startup.cs:

services .AddControllersWithViews() .AddRazorRuntimeCompilation();

Los archivos Web.config siguen siendo válidos

La mayoría de los documentos y blogs mencionan que el archivo web.config se sustituyó por el archivo appsettings.json en ASP.NET Core. Aunque esto es cierto, todavía necesitamos el archivo web.config para implementar en IIS algunos de los encabezados de seguridad como se describe en este post: Guía de cabeceras de seguridad de ASP.NET Core.

  Cómo leer cualquier artículo de Internet sin publicidad, ni comentarios y sin aceptar cookies

Aquí hay un ejemplo del archivo web.config que utilizamos para eliminar las cabeceras Server y X-Powered-By que se ponen automáticamente en las respuestas HTTP cuando la aplicación se hospeda en IIS:

<?xml version="1.0" encoding="utf-8"?><configuration> <system.webServer> <security> <requestFiltering removeServerHeader="true" /> </security> <httpProtocol> <customHeaders> <remove name="X-Powered-By" /> </customHeaders> </httpProtocol> </system.webServer></configuration>

Soporte de Azure

Si tu aplicación se ejecuta en Azure (como las nuestras) debes averiguar qué versiones de .NET Core soporta cada región de Azure. Cuando se lanzan nuevas versiones de .NET Core, las regiones de Azure se actualizan en un período que puede llevar de semanas a incluso meses. Antes de actualizar, debes comprobar si tu región es compatible con la versión a la que te estás actualizando. La mejor forma de averiguarlo la tienes en el .NET Core en App Service Dashboard.

Bundling y minificación

El bundling y la minificación era una de las cosas que, creo que funcionaban muy bien en ASP.NET MVC. En ASP.NET Core tienes bastantes opciones diferentes para hacer lo mismo, lo cual es bueno, pero también un poco confuso cuando vienes directamente de ASP.NET MVC. Microsoft tiene un buen documento sobre el tema aquí: Agrupar y minimizar los activos estáticos en ASP.NET Core.

Al final acabamos utilizando la extensión Bundler & Minifier de Mads Kristensen, sobre la que quizás escriba una entrada específica en el blog. En resumen, especificamos los archivos de entrada y salida en un archivo bundleconfig.json:

[ { "outputFileName": "wwwroot/bundles/style.min.css", "inputFiles": [ "wwwroot/css/style1.css", "wwwroot/css/style2.css" ] }]

Este archivo se procesa con Gulp y se hace el bundling y la minificación con los paquetes gulp-cssmin, gulp-concat, y otros paquetes npm similares (tenemos un curso fenomenal de herramientas Front-End aquí). Esto hace posible ejecutarlo tanto localmente (conectando las tareas de Gulp al build de Visual Studio si lo deseas), como en nuestro servidor de Azure DevOps.

Mira mamá, no más peticiones potencialmente peligrosas

Si has estado ejecutando ASP.NET MVC y registrando peticiones fallidas, probablemente ya has visto este error muchas veces:

ASP.NET MVC no permitía etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad