Bienvenido a la primera publicación del tutorial de ASP.NET Core en español en este crearemos una aplicación web basada en la plantilla MVC de ASP.NET Core y tiene como propósito principal mostrar una forma de hacer tareas comunes en este framework como ejecutar las operaciones CRUD con una base de datos, validar formularios ,instalar la aplicación , crear un contenedor de docker, usar la inyección de dependencias y algunos temas adicionales. Este estará basado en mi experiencia personal e incluirá las referencias a otros recursos de donde aprendido como son libros, blogs o la documentación misma.

En este primera sección creamos la estructura del proyecto,agregaremos el control de código fuente con Git y crearemos el modelo de la base de datos. Adicionalmente alojaremos el código fuente del proyecto en un repositorio de Github.

Aunque en mi trabajo del dia a dia uso Visual Studio y harto el ratón siempre me ha atraído el poder de la linea de comandos y poder teclar sin el uso del ratón por tal motivo siempre que exista una herramienta de linea de comandos intentare hacer uso de ella, esto siguiendo el consejo “Aprende a usar la herramientas de linea de comanando” del libro 97 Things Every Programmer Should Know. Para poder seguir este tutorial debes tener instalado las siguientes herramientas de línea de comandos y tener una cuenta en Github. Incluimos el comando para para verificar la versión que están instaladas.

  • SDK de dotnet : dotnet --version
  • git git --version

Si tienes alguna duda con alguna de ellas revisa la documentación o dejame un comentario que intentare responder a la brevedad.

Control de código fuente y Estructura del proyecto

Seguiremos una estructura del proyecto muy común en el contexto del Open Source en un proyecto que sigue la arquitectura limpia que describen en el libro gratuito Architect Modern Web Applications with ASP.NET Core and Azure. Usamos la version de 5.7 de MySQL y la version 2.2 de ASP.NET Core. El código estará alojado en un repositorio de Github pero es posible alojarlo en cualquier otro servicio que soporte git como Azure Repos, BitBucket o Gitlab. Particularmente en mi trabajo uso Gitlab en una máquina virtual y funciona de maravilla.

git y Github

Todo el código de la aplicación estará dentro de la carpeta Sakila que incluirá dos carpetas: src para el código y test para los proyectos de prueba. A nivel de la estos directorios incluiremos archivos que tienen un alcance para todo el proyecto como los siguientes archivos .gitignore, LICENSE, Dockerfile,global.json o el archivo README entre otros.

En proyectos más grandes pueden existir carpetas adicionales a src y test como ejemplo ve la estructura del código fuente de ASP.NET Core y revisa el contenido de la carpeta eng y docs.

├───src
└───test

La primer tarea a realizar es el repositorio git, agregaremos el archivo .gitignore basado en la plantilla de Visual Studio y crearemos repositorio de Github. Para crear el repositorio en Github usamos las siguientes opciones en la pantalla. Observa que hay una opción para activar las Azure Pipelines esto es por que tengo instalada está extensión en mi cuenta espera un post para agregar el archivo .yml y definir el proceso de integración continua en Azure Devops.

Pantalla para crear un nuevo repositorio en Github

Archivo .gittgnore es una buena practica incluir siempre el archivo .gitignore en el repositorio. Hay una gran colección de archivos con opciones predefinidas en el repositorio Github gitignore. Generalmente usamos el archivo .gitignore para Visual Studio. El SDK de .NET Core trae una nueva plantilla que copia el archivo .gitignore para Visual Studio con dotnet new gitignore

Para comenzar con el proyecto crea una carpeta llamada Sakila y ejecuta inicializa el repositorio de git.

mkdir Sakila
cd Sakila
git init

Hasta aquí tenemos un repositorio local sin ningún archivo. Para agregar una relación con el repositorio creado en Github necesitamos agregar lo que en git se conoce como un remoto. Esto lo hacemos mediante el siguiente comando

git remote add origin https://github.com/jahbenjah/Sakila.git

Copiamos el contenido del archivo .gitignore del repositorio de Github y lo agregamos al control de código fuente mediante el comando

git add .gitignore

Con esto ya podemos publicar nuestro código el el repositorio remoto para ello agregammos un commit y publicamos la rama master al repositorio remoto.

git commit -m "Se crea estructura inicial del proyecto"
git push -u origin master

A partir de aquí empezaremos a repetir esté patrón para la publicación y seguiremos el flujo de Github,es decir, para cada característica nueva de la aplicación crearemos una rama de git y cuando estén listos los cambios crearemos un Pull Request para incorporar nuestros cambios a la rama master. Para crear una rama y cambiarse de rama ejecuta

git branch estructura-proyecto
git checkout estructura-proyecto

Git en si mismo requiere de grandes conocimientos por lo que te recomiendo guardar el libro Pro Git en tus favoritos. Siguiendo el consejo “Conoce tu IDE del libro 97 Things Every Programmer Should Know deberías saber que Visual Studio tiene soporte para git y una extensión para Github de tal forma que la mayoría de las tareas de git las puedes hacer mediante la interfaz gráfica.

Proyecto

Crearemos 3 proyectos contenidos en la carpeta Sakila/src. Tenemos planeado agregaremos pruebas unitarias a esta aplicación en un articulo posterior

  • Sakila.Core Biblioteca de clases para la lógica de la aplicación
  • Sakila.Infrastructure Biblioteca de clases para el código de Entity Framework Core que se comunica con la base de datos.
  • Sakila.Web aplicación de ASP.NET Core usando la plantilla de MVC para para mostrar como acceder a la base de datos en este proyecto.

Nota Si cuentas con más de una versión del SDK de .NET Core es recomendable que uses un archivo global.json para especificar la versión de las herramientas que deseas utilizar. Se sugiere que el archivo global.json se coloque en la carpeta raíz que contiene todo el código. Puedes verificar los SDK instalado en tu computadora con el comando dotnet --list-sdks. Puedes crear un archivo global.json con el comando dotnet new globaljson --sdk-version 2.2.401.

En una terminal ejecuta los siguientes comandos dentro de la carpeta Sakila para crear una solución y los proyectos ejecuta los siguientes comandos uno seguido de otro. La opción -o permite especificar el directorio de salida. Para nuestra conveniencia agregamos una solución que puede usarse con Visual Studio si a si lo prefieres.

cd Sakila
dotnet new sln
dotnet new globaljson --sdk-version 2.2.401
dotnet new classlib -o src\Sakila.Core --no-restore
dotnet new classlib -o src\Sakila.Infrastructure --no-restore
dotnet new mvc -o src\Sakila.Web --no-restore

# Agrega los proyectos a la solución
dotnet sln add src\Sakila.Core\Sakila.Core.csproj
dotnet sln add src\Sakila.Infrastructure\Sakila.Infrastructure.csproj
dotnet sln add src\Sakila.Web\Sakila.Web.csproj

Elimina los archivos Class1.cs de los proyectos Sakila.Core y Sakila.Infrastructure. Si usas Linux puedes usar rm en lugar de del

del src\Sakila.Core\Class1.cs
del src\Sakila.Infrastructure\Class1.cs

Hasta aquí tenemos una solución con los tres proyectos sin ninguna relación entre ellos. En la siguiente sección agregaremos los paquetes de Nuget y referencias a proyectos necesarios.

Referencias a proyectos y Paquetes de Nuget

El proyecto Sakila.Core unicamente requiere el paquete MySql.Data y esto porque la tabla address utiliza el tipo de dato MySqlGeometry de MySQL. Lo instalamos con el siguiente comando :

dotnet add src\Sakila.Core\Sakila.Core.csproj package MySql.Data

El proyecto Sakila.Infrastructure requiere el paquetes de Nuget MySql.Data.EntityFrameworkCore y una referencia al proyecto Sakila.Core para agregarlas ejecuta el siguiente comando para instalarlo:

dotnet add src\Sakila.Infrastructure\Sakila.Infrastructure.csproj package MySql.Data.EntityFrameworkCore
dotnet add src\Sakila.Infrastructure\Sakila.Infrastructure.csproj reference src\Sakila.Core\Sakila.Core.csproj

Finalmente en el proyecto Sakila.Web instala el paquete de Nuget Microsoft.EntityFrameworkCore.Design con el comando: También agrega referencias a los proyectos Sakila.Core y Sakila.Infrastructure

dotnet add src\Sakila.Web\Sakila.Web.csproj package Microsoft.EntityFrameworkCore.Design --version 2.2.2
dotnet add src\Sakila.Web\Sakila.Web.csproj reference src\Sakila.Core\Sakila.Core.csproj
dotnet add src\Sakila.Web\Sakila.Web.csproj reference src\Sakila.Infrastructure\Sakila.Infrastructure.csproj

Hasta aquí tenemos lista la estructura del proyecto por lo que es un buen momento para agregar un nuevo commit a nuestro el repositorio de Git local. Posteriormente comenzaremos el proceso para generar el modelo de clases de C# a partir de una base existente. Si usas Visual Studio fácilmente puedes usar Archivo>Nuevo Proyecto en la interfaz gráfica para crear estos proyectos.

Cadena de conexión para MySQL

Lo primero que necesitamos son las credenciales para acceder a la base de datos. Con estas debemos crear una cadena de conexión que no es más que un conjunto de claves y valores separadas por comas. Si tienes duda como crearla puedes ver mi post sobre cadenas de conexión con C# para más detalles.

server=localhost;database=Sakila;port=3306;user id=root;password=TuContraseña.

Esta cadena la usaremos más adelante para general el modelo de clases a partir de la base de datos.

Generando el modelo de clases en C#

Para generar el modelo de clases de C# de la base sakila usaremos el comando dotnet ef dbcontext scaffold dotnet ef dbcontext scaffold de la línea de comando de Entity Framework Core. Debido a que existen gran cantidad de opciones disponibles. A continuacion enlistamos las que opciones que usaremos:

Parámetro Valor Descripción
-s src/Sakila.Web/Sakila.Web.csproj Define el proyecto de inicio
-o ../Sakila.Core/Entities Define el directorio donde se colocaran los archivos de salida
-c SakilaContext Especifica el nombre del DbContext
-context-dir ./Sakila.Infrastructure Especifica la ubicación del DbContext

Sin más detalle ejecutamos el siguiente comando para realizar la ingenieria inversa de la base de MySQL

dotnet ef dbcontext scaffold "database=Sakila;server=localhost;port=3306;user id=root;password=Password" MySql.Data.EntityFrameworkCore -s src/Sakila.Web/Sakila.Web.csproj -o ../Sakila.Core/Entities -c SakilaContext --context-dir ../Sakila.Infrastructure

Esto nos creara el modelo de clases de C# usando por el método la API fluida de Entity Framework Core en el método OnModelCreating por ejemplo para la clase Actor que se encuentra en el esquema sakila. Desde mi punto de vista esto genera una clase que no es fácil de mantener porque este método contiene una gran cantidad de líneas, un indice de mantenimiento y una complejidad ciclomatica grande. Posteriormente agregaremos una entrada sobre como obtener las metricas del código.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");

  modelBuilder.Entity<Actor>(entity =>
  {
  entity.ToTable("actor", "sakila");

  entity.HasIndex(e => e.LastName).HasName("idx_actor_last_name");

  entity.Property(e => e.ActorId).HasColumnName("actor_id").HasColumnType("smallint(5) unsigned");

  entity.Property(e => e.FirstName)
                    .IsRequired().HasColumnName("first_name").HasMaxLength(45).IsUnicode(false);

  entity.Property(e => e.LastName)
                    .IsRequired()
                    .HasColumnName("last_name").HasMaxLength(45).IsUnicode(false);

   entity.Property(e => e.LastUpdate)
                    .HasColumnName("last_update")
                    .HasDefaultValueSql("CURRENT_TIMESTAMP");
  });
  //...

Una forma alternativa de crear el Modelo es usando las anotaciones de datos. Puedes crear un modelo usando las anotaciones de datos agregando la opción -d al comando dotnet ef dbcontext. Por ejemplo la clase Actor configurada con anotaciones de datos luce de la siguiente manera. Es necesario notar que no todas las opciones de la API fluida estan disponibles en las anotaciones de datos.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Sakila.Console
{
    [Table("actor", Schema = "sakila")]
    public partial class Actor
    {
        public Actor()
        {
            FilmActor = new HashSet<FilmActor>();
        }

        [Column("actor_id", TypeName = "smallint(5) unsigned")]
        public short ActorId { get; set; }
        [Required]
        [Column("first_name")]
        [StringLength(45)]
        public string FirstName { get; set; }
        [Required]
        [Column("last_name")]
        [StringLength(45)]
        public string LastName { get; set; }
        [Column("last_update")]
        public DateTimeOffset LastUpdate { get; set; }

        [InverseProperty("Actor")]
        public virtual ICollection<FilmActor> FilmActor { get; set; }
    }
}

Estes es un buen punto para publicar los cambios en el repositorio Git local para llevar una trazabilidad de los cambios realizados en el códiggo. Ejecuta los comandos:

git add .
git commit -m "Se crea el modelo de datos"

Un punto que debemos tener en cuenta es que cuando usamos las herramientas para generar el modelo de bases de datos la cadena de conexión se incluye en el método OnConfiguring que es considerado una mala práctica por lo que eliminaremos este método. Y la cadena de conexión la obtendremos del archivo de configuración.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
  if (!optionsBuilder.IsConfigured)
  {
    optionsBuilder.UseMySQL("database=Sakila;server=localhost;port=3306;user id=root;password=Password");
  }
}

Usar el contexto en la aplicación ASP.NET Core MVC

Para usar el contexto de la aplicación en el proyecto de ASP.NET Core requerimos hacer uso de la inyección de dependecias que viene integrado con ASP.NET Core. Lo primero que tenemos que hacer es agregar el servicio en el método ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<SakilaContext>(options => options
                      .UseMySQL(Configuration.GetConnectionString("Sakila")));
  //...
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

El siguiente paso es especificar la dependencia en el controlador. Para este caso HomeController

private readonly SakilaContext _context;
public HomeController(SakilaContext context)
{
  _context = context;
}

Para llevar