.NET en Linux: más simple de lo que parece

Publicado: 2022-08-16

Desarrollar soluciones .NET en Linux siempre ha sido un desafío porque Visual Studio de Microsoft requiere Windows para funcionar. Después de trabajar en varios proyectos .NET, decidí probar los límites del desarrollo de .NET en Linux. Este sencillo tutorial se centra en una aplicación ASP.NET MVC con SQL Server para mostrar lo elegante y eficaz que puede ser el desarrollo de .NET en mi sistema operativo preferido.

Entorno de desarrollo

En primer lugar, debemos asegurarnos de que las herramientas .NET y el SDK asociados con nuestro tipo particular de Linux estén instalados utilizando la guía estándar de Microsoft.

Mi entorno de desarrollo preferido consta de un entorno de desarrollo integrado (IDE) con ventanas, una potente herramienta de gestión y consulta de bases de datos, la propia base de datos y herramientas para la creación y la implementación. Utilizo las siguientes herramientas para lograr una funcionalidad sólida y permitir una hermosa experiencia de codificación:

  • IDE: Código de Visual Studio
  • Herramienta de consulta y gestión de bases de datos: DBeaver
  • Base de datos: Microsoft SQL Server (instalación de Linux)
  • Herramientas de compilación: interfaz de línea de comandos (CLI) de .NET SDK
  • Máquina virtual y contenedores: Docker

Asegúrese de que estas herramientas estén correctamente instaladas antes de continuar con nuestra aplicación de muestra.

Proyecto Andamio

En esta aplicación de ejemplo, destacaremos el desarrollo y la funcionalidad de ASP.NET a través de una serie de casos de uso para un sistema hipotético de gestión de inventario de una zapatería. Al igual que con cualquier aplicación .NET nueva, necesitaremos crear una solución y luego agregarle un proyecto. Podemos aprovechar las herramientas de la CLI de .NET SDK para montar nuestra nueva solución:

 mkdir Shoestore && cd Shoestore dotnet new sln

A continuación, cree un proyecto ASP.NET que contenga una clase principal explícita para simplificar, ya que esta estructura de proyecto es más familiar para los desarrolladores de ASP.NET. Vamos a crear nuestro proyecto usando el patrón MVC:

 mkdir Shoestore.mvc && cd Shoestore.mvc dotnet new mvc --use-program-main=true

A continuación, agregue el proyecto a la solución:

 # Go to the root of the solution cd .. dotnet sln add Shoestore.mvc/

Ahora tenemos una solución predeterminada y su proyecto ASP.NET contenido. Antes de continuar, asegúrese de que todo se construya:

 cd Shoestore.mvc/ dotnet restore dotnet build

Las buenas prácticas de desarrollo fomentan la colocación de servicios clave y el tiempo de ejecución de la aplicación en contenedores Docker para mejorar la implementación y la portabilidad. Por lo tanto, creemos un contenedor Docker simple para admitir nuestra aplicación.

Portabilidad de aplicaciones

Las imágenes de Docker suelen hacer referencia a otra imagen principal de Docker como punto de partida aceptado para requisitos esenciales como el sistema operativo y las soluciones básicas, incluidas las bases de datos. Siguiendo esta práctica recomendada de Docker, cree un archivo Dockerfile y un archivo Docker Compose para una configuración adecuada del servicio mientras hace referencia a las imágenes principales publicadas por Microsoft. Usaremos etapas de Docker para mantener nuestra imagen pequeña. Las etapas nos permiten usar el SDK de .NET mientras creamos nuestra aplicación, de modo que el tiempo de ejecución de ASP.NET solo se requiere mientras se ejecuta nuestra aplicación.

Cree el Shoestore.mvc Shoestore.mvc con el siguiente contenido:

 # Shoestore\Shoestore.mvc\Dockerfile # Build stage FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /shoestore COPY Shoestore.mvc/*.csproj ./ # Restore project packages RUN dotnet restore COPY Shoestore.mvc/* ./ # Create a release build RUN dotnet build -c Release -o /app/build # Run the application and make it available on port 80 FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app EXPOSE 80 # Assets and views COPY Shoestore.mvc/Views ./Views COPY Shoestore.mvc/wwwroot ./wwwroot COPY --from=build /app/build ./ ENTRYPOINT [ "dotnet", "Shoestore.mvc.dll" ]

A continuación, crearemos el archivo docker-compose.yml en el directorio raíz de nuestra solución. Inicialmente, solo contendrá una referencia al .Dockerfile de nuestro servicio de aplicación:

 # Shoestore/docker-compose.yml version: "3.9" services: web: build: context: . dockerfile: Shoestore.mvc/Dockerfile ports: - "8080:80"

También configuremos nuestro entorno con un archivo .dockerignore para asegurarnos de que solo los artefactos de compilación se copien en nuestra imagen.

Con nuestro servicio de aplicación ahora conectado y su entorno de ejecución listo para ejecutarse, necesitamos crear nuestro servicio de base de datos y conectarlo a nuestra configuración de Docker.

El servicio de base de datos

Agregar Microsoft SQL Server a nuestra configuración de Docker es sencillo, especialmente porque estamos usando una imagen de Docker proporcionada por Microsoft sin cambiarla. Agregue el siguiente bloque de configuración al final del archivo docker-compose.yml para configurar la base de datos:

 db: image: "mcr.microsoft.com/mssql/server" environment: SA_PASSWORD: "custom_password_123" ACCEPT_EULA: "Y" ports: - "1433:1433"

Aquí, ACCEPT_EULA evita que la instalación se detenga, y nuestra configuración de ports permite que el puerto predeterminado de SQL Server pase sin traducción. Con eso, nuestro archivo Compose incluye tanto nuestro servicio de aplicación como nuestra base de datos.

Antes de personalizar el código de la aplicación, verifiquemos que nuestro entorno Docker funcione:

 # From the root of the solution docker compose up --build

Suponiendo que no aparezcan errores durante el inicio, nuestra aplicación de muestra incompleta debería estar disponible a través de un navegador web en la dirección local http://localhost:8080 .

Herramientas de generación de código

Ahora podemos centrarnos en la parte divertida: personalizar el código de la aplicación y garantizar que los datos de la aplicación persistan en la base de datos de Microsoft SQL Server. Usaremos las herramientas Entity Framework (EF) y .NET SDK para conectar la aplicación a la base de datos y montar el modelo, la vista, el controlador y la configuración requerida por EF de la aplicación.

Antes de que podamos especificar las herramientas que necesitamos, debemos crear un archivo tool-manifest :

 # From the root of the solution dotnet new tool-manifest

Agregue las herramientas EF y SDK a este archivo con estos simples comandos:

 dotnet tool install dotnet-ef dotnet tool install dotnet-aspnet-codegenerator

Para verificar la correcta instalación de estas herramientas, ejecute dotnet ef . Si aparece un unicornio, están instalados correctamente. A continuación, ejecute dotnet aspnet-codegenerator para probar las herramientas ASP.NET; la salida debe ser un bloque de uso general de la CLI.

Ahora podemos usar estas herramientas para crear nuestra aplicación.

MVC: Modelo

La primera tarea en la construcción de nuestra aplicación es crear el modelo. Dado que este modelo se agregará a la base de datos más adelante, incluiremos los paquetes MS SQL Server y EF en nuestro proyecto:

 cd Shoestore.mvc/ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet restore

A continuación, cree un objeto de contexto de base de datos EF que determine qué modelos se agregan a la base de datos y permita que nuestro código acceda y consulte fácilmente esos datos de la base de datos.

Cree un directorio de Data para albergar el código específico de EF y cree el archivo Data/ApplicationDBContext.cs con el siguiente contenido:

 // Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs using Microsoft.EntityFrameworkCore; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options){} }

A continuación, configure la cadena de conexión de la base de datos, que debe coincidir con las credenciales que configuramos en nuestro Dockerfile . Establezca el contenido de Shoestore/Shoestore.mvc/appsettings.json de la siguiente manera:

 { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Shoestore": "Server=db;Database=master;User=sa;Password=custom_password_123;" } }

Con la cadena de conexión de la base de datos configurada y el contexto de la base de datos codificado, estamos listos para codificar la función Main de nuestra aplicación. Incluiremos el manejo de excepciones de la base de datos para simplificar la depuración del sistema. Además, debido a que un error de .NET en el código generado hace que el contenedor de Docker proporcione nuestras vistas de manera incorrecta, necesitaremos agregar un código específico a la configuración de nuestro servicio de visualización. Esto establecerá explícitamente las rutas de archivo a nuestra ubicación de vista en nuestra imagen de Docker:

 using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Data; namespace Shoestore.mvc; // ... public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Associate our EF database context and configure it with our connection string var connectionString = builder.Configuration.GetConnectionString("Shoestore"); builder.Services.AddDbContext<ApplicationDBContext>( options => options.UseSqlServer(connectionString)); // Middleware to catch unhandled exceptions and display a stack trace builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // Add services to the container. // ASP.NET has a known issue where the final built app doesn't know where the view // files are (in the Docker container). // The fix is to specifically add view locations. builder.Services .AddControllersWithViews() .AddRazorOptions(options => { options.ViewLocationFormats.Add("/{1}/{0}.cshtml"); options.ViewLocationFormats.Add("/Shared/{0}.cshtml"); });

Pase a la declaración IsDevelopment if dentro del mismo archivo para agregar un punto final de migración de base de datos a nuestro sistema cuando está en modo de desarrollo. Agregue una instrucción else con el siguiente código:

 // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { // Leave the contents of the if block alone. These are hidden for clarity. } else { app.UseMigrationsEndPoint(); }

A continuación, ejecute una prueba rápida para asegurarse de que los nuevos paquetes y las ediciones del código fuente se compilen correctamente:

 // Go to mvc directory cd Shoestore.mvc dotnet restore dotnet build

Ahora, completemos el modelo con nuestros campos obligatorios creando el Shoestore.mvc\Models\Shoe.cs :

 namespace Shoestore.mvc.Models; public class Shoe { public int ID { get; set; } public string? Name { get; set; } public int? Price { get; set; } public DateTime CreatedDate { get; set; } }

EF genera SQL basado en el modelo asociado, su archivo de contexto y cualquier código EF en nuestra aplicación. Los resultados de SQL luego se traducen y se devuelven a nuestro código, según sea necesario. Si agregamos nuestro modelo de Shoe al contexto de nuestra base de datos, EF sabrá cómo traducir entre MS SQL Server y nuestra aplicación. Hagamos esto en el archivo de contexto de la base de datos, Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs :

 using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Models; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options) { } private DbSet<Shoe>? _shoe { get; set; } public DbSet<Shoe> Shoe { set => _shoe = value; get => _shoe ?? throw new InvalidOperationException("Uninitialized property" + nameof(Shoe)); } }

Finalmente, usaremos un archivo de migración de base de datos para llevar nuestro modelo a la base de datos. La herramienta EF crea un archivo de migración específico para MS SQL Server basado en el contexto de la base de datos y su modelo asociado (es decir, Shoe ):

 cd Shoestore.mvc/ dotnet ef migrations add InitialCreate

Esperemos a ejecutar nuestra migración hasta que tengamos un controlador y una vista en su lugar.

MVC: controlador y vista

Crearemos nuestro controlador utilizando la herramienta de generación de código ASP.NET. Esta herramienta es muy poderosa pero requiere clases auxiliares específicas. Utilice los paquetes de estilo de Design para la estructura básica del controlador y su integración con EF. Agreguemos estos paquetes:

 cd Shoestore.mvc\ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design && \ dotnet add package Microsoft.EntityFrameworkCore.Design && \ dotnet restore

Ahora, crear nuestro controlador predeterminado es tan simple como invocar el siguiente comando:

 cd Shoestore.mvc\ dotnet dotnet-aspnet-codegenerator controller \ -name ShoesController \ -m Shoe \ -dc ApplicationDBContext \ --relativeFolderPath Controllers \ --useDefaultLayout \ --referenceScriptLibraries

Cuando el generador de código crea nuestro controlador, también crea una vista simple para ese controlador. Con nuestras bases de MVC completas, estamos listos para que todo funcione.

Prueba de migración y aplicación

Las migraciones de EF suelen ser un asunto simple, pero cuando Docker está involucrado, el proceso se vuelve más complejo. En el próximo artículo de nuestra serie, exploraremos el camino maravillosamente tortuoso para hacer que esas migraciones funcionen en nuestra solución Docker, pero por ahora, solo queremos que nuestra migración se ejecute.

Todos los archivos de configuración y migración están incluidos en nuestro repositorio. Clonemos el proyecto completo en nuestra máquina local y realicemos la migración:

 git clone https://github.com/theZetrax/dot-net-on-linux.git cd ./dot-net-on-linux docker composer up

La operación docker composer crea nuestra aplicación, ejecuta la migración e inicia nuestra aplicación ASP.NET con el tiempo de ejecución de .NET. Para acceder a la solución en ejecución, visite http://localhost:8080/Shoes .

Aunque la interfaz de nuestra aplicación es simple, demuestra la funcionalidad en todos los niveles, desde la vista hasta la base de datos.

.NET en Linux simplemente funciona

Consulte el repositorio completo para obtener una descripción general de nuestra solución. El próximo artículo cubrirá nuestra migración en detalle, así como consejos y trucos para hacer que nuestras imágenes de Docker sean más eficientes.

.NET en Linux es más que una quimera: es una combinación viable de lenguaje, tiempo de ejecución y sistema operativo. Es posible que muchos desarrolladores criados en Visual Studio no tengan experiencia en el uso de .NET CLI al máximo, pero estas herramientas son efectivas y poderosas.

Lecturas adicionales en el blog de ingeniería de Toptal:

  • Creación de una API web de ASP.NET con ASP.NET Core
  • 8 razones por las que Microsoft Stack sigue siendo una opción viable
  • Cómo mejorar el rendimiento de la aplicación ASP.NET en Web Farm con almacenamiento en caché
  • Un tutorial de Elasticsearch para desarrolladores de .NET
  • ¿Qué es Kubernetes? Una guía para la contenedorización y el despliegue

El blog de ingeniería de Toptal agradece a Henok Tsegaye por revisar los ejemplos de código presentados en este artículo.