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 esta 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 agrega archivo .gitignore"
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 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.

dotnet new globaljson --sdk-version 2.2.401
dotnet new sln
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 requiere el paquete de Nuget Microsoft.EntityFrameworkCore.Design y referencias a los proyectos Sakila.Core y Sakila.Infrastructure

dotnet add src\Sakila.Web\Sakila.Web.csproj package MySql.Data.EntityFrameworkCore
dotnet add src\Sakila.Web\Sakila.Web.csproj package Microsoft.EntityFrameworkCore.Design
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.

git add .
git commit -m "Se crea la estructura del proyecto"

Generando el modelo de clases en C#

Para generar el modelo de clases de C# de una base existente se usa el comando dotnet ef dbcontext scaffold y para ello necesitamos una cadena de conexion y el nombre del proveedor de Entity Framework Core. Para el caso de MySQL

Parámetro Valor
Cadena de conexion MySQl database=Sakila;server=localhost;port=3306;user id=root;password=Password
Provedor MySql.Data.EntityFrameworkCore

Si tienes duda como crear una cadena de conexión con C#. Tambien usamos las siguientes opciones del comando dotnet ef dbcontext scaffold. Puedes usar dotnet ef dbcontext scaffold --help para ver más detalles sobre este comando.

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 colocarán los archivos de salida
-c SakilaContext Especifica el nombre del DbContext
-context-dir ./Sakila.Infrastructure Especifica la ubicación del DbContext

Nuestro comando para generar los el modelo de la base de datos Sakila el comando es el siguiente:

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 SakilaContext que no es fácil de mantener porque este método contiene una gran cantidad de líneas, un indice de mantenimiento bajo 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; }
    }
}

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");
  }
}

Otro punto a considerar el espacio de nombres que las herramientas de linea de comandos de EF Core usa por default espacio de nombres del proyecto de inicio en este caso Sakila.Web. Ajustaremos los espacios de nombres a Sakila.Core.Entities y Sakila.Infrastructure dependiendo de l proyecto al que pertenezacan las clases.

Este 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"

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 la cadena de conexión al archivo appsettings.json en una seccion llamada ConnectionStrings

{
  "ConnectionStrings": {
    "Sakila": "database=Sakila;server=localhost;port=3306;user id=root;password=Password"
  },
}

Posteriormente tenemos que agregar el servicio en el método ConfigureServices usando el método de extensión AddDbContext<T> y leyendo la cadena de conexion del archivo de configuración. Tambien es necesario agregar las instruciones using Microsoft.EntityFrameworkCore; y using Sakila.Infrastructure; al archivo Startup.cs

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 constructor junto con un campo privado donde haremos uso de esta dependencia SakilaContext, en este caso la clase HomeController.

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

Para mostrar como usar la clase SakilaContext modificamos el método de acción Index de la clase HomeController de la siguiente manera:

public IActionResult Index()
{
  return View(_context.Actor.ToList());
}

La vista Views\Home\Index.cshtml contendrá el siguiente código para mostrar los nombres de los actores de la base sakila en una lista en la página de inicio de la aplicación.

@model List<Sakila.Core.Entities.Actor>  
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Bienvenidos</h1>
    <p>Los actores en la base Sakila son:</p>
</div>

<div>
    <ul class="list-group">
        @foreach (var actor in Model)
        {
        <li class="list-group-item">@actor.FirstName @actor.LastName;</li>
        }
    </ul>
</div>

Para ejecutar la aplicación ejecuta el siguiente comando:

dotnet run --project src\Sakila.Web\Sakila.Web.csproj

Cuando estemos listos podemos publicar la rama a nuestro repositorio remoto de Gtihub.

git add .
git commit -m "Se agrega el DbContext al proyecto Web"
git push origin estructura-proyecto

El último paso sera crear un Pull request para integrar nuestros cambios la rama master.