El Pequeño Libro de ASP - NET Core
El Pequeño Libro de ASP - NET Core
El Pequeño Libro de ASP - NET Core
¿Estas listo para crear tú primera aplicación web con ASP.NET Core? Primero, tendrás que
conseguir los programas básicos que necesitas para iniciar es el kit de desarollador de
software o SDK de .NET Core y un editor de textos. Aunque con estas herramientas puedes
crear aplicaciones completamente funcionales tengo que aclarar que en el mundo real
existen programas especializados conocidos como Entornos de Desarollo Integrado o IDE
por sus siglas en inglés. Ejemplos de IDEs son Visual Studio de Microsoft o Rider de
JetBrains.
El SDK de .NET Core: este es un conjunto de programas crear aplicaciones ASP.NET Core, el
motor de tiempo de ejecución, las librerías base y la línea de comandos .
Tú editor de código favorito. Puedes usar Atom, Sublime, Notepad o cualquier otro editor
de código en el que prefieras escribir código. Si no tienes un editor de código favorito, dale
una oportunidad a Visual Studio Code. Es un editor de código gratuito, multiplataforma que
tiene gran soporte para escribir código en C#, Javascript, HTML y más.
Si trabajas en Windows, también puedes usar Visual Studio para construir aplicaciones
ASP.NET Core. Necesitaras Visual Studio 2019. (La versión gratuita Community está bien).
Visual Studio tiene soporte fantástico en el completado del código y refactorización, a pesar
Visual Studio Code está muy cercano.
Consigue el SDK
El Kit de Desarrollo de Software para .NET Core o SDK por sus siglas en ingles (Software
Development Kit) es un conjunto de programas que permiten desarollar y ejecutar
aplicaciones con la plataforma .NET e incluyen compiladores,gestor de paquetes, plantillas
de proyectos y más.
Para instalar el SDK de .NET Core necesitas seguir las instrucciones especificas para el
sistema operativo que usas por lo que te recomendamos seguir la documentación oficial:
Instalación del SDK de .NET Core.
dotnet --version
3.1.101
dotnet --info
Version: 3.1.101
Commit: b377529961
OS Name: Windows
OS Version: 10.0.18363
OS Platform: Windows
RID: win10-x64
Version: 3.1.1
Commit: a1388f194c
https://aka.ms/dotnet-download
Hola Mundo en C#
Antes de comenzar con ASP.NET Core, prueba creando y ejecutando una aplicación de
consola con C# simple.
Puedes hacer todo esto desde la línea de comandos. Primero, abre una Terminal (o
PowerShell en Windows). Navega a la ubicación donde deseas guardar tus proyectos, tal
cómo la carpeta de mis Documentos:
cd Documentos
El comando dotnet new sirve para crear un nuevo proyecto de .NET Core. El parámetro
console selecciona una plantilla para una aplicación de consola ( un programa que emite
texto en la pantalla). El parámetro -o HolaMundo especifica que el comando dotnet new
debe crear una carpeta llamada HolaMundo y colocar en ella los archivos del proyecto.
Cambiate a esta carpeta asi:
cd HolaMundo
dotnet new console crea un programa de C# básico que escribe el texto ¡Hola Mundo! en
la pantalla. El programa esta compuesto por dos archivos: un archivo con extensión
.csproj y un archivo con extensión .cs . El primero es conocido como el archivo del
proyecto y usa XML para definir algunos metadatos sobre el proyecto como que paquetes
requiere, que versión del framework se usa. El segunfo es el código fuente del programa
Después, cuando agregues otros paquetes, estos serán listados aquí (de forma similar a un
un archivo package.json para npm). No necesitarás editar esté archivo de forma manual
muy seguido.Si abres el primer archivo en un editor de texto, veras esto :
HolaMundo.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>
Program.cs
using System;
namespace HolaMundo
class Program
Console.WriteLine("Hola Mundo!");
Dentro de la carpeta del proyecto, usa dotnet run para ejecutar el programa. Veras que la
salida se escribe en la consola después que el código compila:
dotnet run
Hello World!
¡Esto es todo lo necesario para crear y ejecutar un programa en .NET! Enseguida, harás lo
mismo para una aplicación de ASP.NET Core.
cd ..
A continuación, crea un nueva carpeta para almacenar el proyecto completo y navega hacia
el.
mkdir AspNetCoreTodo
cd AspNetCoreTodo
A continuación, crea un nuevo proyecto con el comando dotnet new , esta vez utilizaras
opciones adicionales:
cd AspNetCoreTodo
Esto crea un nuevo proyecto usando la plantilla mvc y agrega al proyecto código adicional
para la autenticación y seguridad (Cubrieré la seguridad en el capítulo Seguridad e
identidad).
Verás unos pocos archivos en la carpeta del nuevo proyecto, Una vez que abres el nuevo
directorio, todo lo que tienes que hacer para ejecutar el proyecto es:
dotnet run
info: Microsoft.Hosting.Lifetime[0]
info: Microsoft.Hosting.Lifetime[0]
info: Microsoft.Hosting.Lifetime[0]
info: Microsoft.Hosting.Lifetime[0]
info: Microsoft.Hosting.Lifetime[0]
info: Microsoft.Hosting.Lifetime[0]
En lugar de imprimir en la consola y salir, este programa inicia un servidor web y espera
peticiones en el puerto 5000.
El directorio wwwroot contiene assets como archivos estáticos como CSS, Javascript e
imágenes. Los archivos en wwwroot serán despachados como contenido estático y
pueden ser empaquetados y minificados automáticamente.
Abrir el directorio raíz del proyecto: En Visual Studio Code, selecciona Archivo>Abrir
carpeta. Si Visual Studio Code te solicita instalar los archivos pendientes, presionar clic
en Si para agregarlos.
Foco para corregir problemas: Si tu código contiene lineas rojos (errores del
compilador, coloca el cursor sobre el código que esta en rojo y mirar el icono del foco
encendido en el margen izquierdo. el foco te sugerirá reparaciones comunes, como
agregar enunciados using faltantes en tu código:
Estos tips también aplican para Visual Studio 2019 para Windows. Si estas usando
Visual Studio, necesitaras abrir el archivo de proyecto directamente. Visual Studio te
solicitara guardar el archivo de la solución, el cuál debes guardar en el directorio raíz
de la solución (la primera carpeta llamado AspNetCoreTodo ). También puedes crear
un proyecto ASP.NET Core directamente o en Visual Studio usando la plantillas
disponibles en Archivo>Nuevo Proyecto.
Control de código fuente : GIT
Si usas Git o Github para manejar el control de código fuente, ahora es buen momento para
hacer un git init e iniciar el repositorio en el directorio raíz del proyecto:
cd ..
git init
Asegúrate que agregues un archivo .gitignore que ignora las carpeta bin y obj . La
plantilla de .gitignore para Visual Studio en el repositorio de Github funciona genial. Desde
la versión 3.0 el comando dotnet new incluye una plantilla para crear un archivo gitignore
Hay mucho más que explorar, así que profundicemos e iniciemos a desarrollar una
aplicación.
Fundamentos de MVC
En este capítulo, explorarás el patrón MVC en ASP.NET Core. MVC (Modelo-Vista-
Controlador) es un patrón de diseño ampliamente utilizado para crear aplicaciones web y es
usado en casi todos los marcos de desarollo web (ejemplos populares son Ruby on Rails y
Expres), adicionalmente marcos de trabajo del lado de cliente con Javascript como Angular.
Las aplicaciones móviles sobre iOS y Android también usan una variante de MVC.
Como el nombre sugiere MVC tiene tres componentes: modelos, vistas y controladores. Los
Controladores gestionan las solicitudes de entrada desde un cliente o un navegador web y
deciden acerca de que código ejecutar. Las Vistas son plantillas (usualmente HTML más un
lenguaje de plantillas como Handlebars, Pug o Razor) que contienen datos añadidos a el
que luego son mostrados a los usuario. Los Modelos mantienen los datos que son
agregado a las vistas, o los datos que son ingresados por los usuarios.
El controlador recibe una petición y busca alguna información en una base de datos.
El controlador crea un modelo con la información y la adjunta a la vista.
La vista es generada y mostrada en el navegador del usuario.
El usuario presiona un botón o envía un formulario, lo que enviá una nueva solicitud al
controlador y el ciclo se repite.
Si has trabajado con MVC en otros lenguajes, te sentirás como en casa en ASP.NET Core
MVC. Si eres nuevo en MVC, este capítulo te enseñara lo básico y te ayudará a iniciar.
En este libro, desarrollaras una aplicación de gestión de tareas pendientes que dejara al
usuario agregar elementos a su lista de tareas y una vez que la tarea se ha completado. Más
específicamente estarás creando:
Una aplicación web de servidor (a veces llamada Back-End) usando a ASP.NET Core, C#
y el patrón MVC.
Una base de datos para almacenar la lista de tareas del usuario usando el motor de
base de datos SQLite y un sistema llamado Entity Framework Core.
Las páginas web y la interfaz con la que el usuario interacturá vía el navegador.
Usando HTML, CSS y Javascript (llamado el FrontEnd).
Un formulario de inicio de sesión y verificación de seguridad así cada usuario
mantendrá su lista de tareas privada.
¿Suena bien? ¡Vamos a construirla! si no haz creado una aplicación nueva en ASP.NET Core
proyecto usando dotnet new mvc , sigue los pasos en el capítulo anterior, debes ser capaz
de construir y ejecutar el proyecto y ver la pantalla de bienvenida,
Crear un controlador
Actualmente ya hay algunos controladores en la carpeta Controllers, incluyendo
HomeController que generá la pantalla de bienvenida por defautl cuando visitas
http://localhost:5000 . Puedes ignorar estos controladores por ahora.
Controllers/TodoController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreTodo.Controllers
Las rutas que son manejadas por el controlador son llamadas acciones, y son
representadas por métodos en la clase controlador. Por ejemplo, el HomeController
incluye tres métodos de acción ( Index , About , y Contact ) las cuales son mapeadas por
ASP.NET Core a estas rutas URLs:
Hay un gran número de convenciones usados por ASP.NET Core, tales como patrón que
FooController se convierte en /Foo , y la acción Index puede ser omitida de la URL.
Puedes personalizar este comportamiento si así lo deseas, pero por ahora, usaremos las
convenciones predefinidas.
Los métodos de acción pueden regresar vistas, datos JSON, o códigos de estatus HTTP como
200 OK y 404 Not Found .
Crear modelos
Hay dos clases modelo diferentes que necesitan ser creadas: un modelo que representa las
tareas almacenadas en la base de datos (a veces llamadas entidades) y el modelo que será
combinado con la vista (MV en MVC) y sera enviado al navegador del usuario. Debido a que
ambos son referidos como modelos, prefiero referirme al último como View Model.
Models/TodoItem.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace AspNetCoreTodo.Models
[Required]
Esta clase define lo que base de datos necesitara para almacenar cada tarea :Un ID, un
titulo o nombre, si la tarea esta completada y la fecha de termino. Cada linea define una
propiedad de la clase:
Nota que el símbolo de interrogación ? después del tipo DateTimeOffset? este marca que
la propiedad DueAt es nullable u opcional. Si el ? no se incluye, todos los las tareas
pendientes necesitarían una fecha de entrega. Las propiedad ID y IsDone no son marcadas
como nullables, asi que ellas son requeridas y siempre tendrá un valor (o valor por
omisión).
La cadenas en C# son siempre nullables, así que no es necesario marcar el titulo como
nullable: Las cadenas de C# pueden ser nulas, vacíos o contener texto.
Cada propiedad es seguida por get; set; , las cuales es una forma corta de decir que la
propiedad es de lectura/escritura (más técnicamente que tiene métodos modificadores.)
En este punto, no importa a cual es la base de datos utilizada. Podría ser SQL Server,
MySQL, Mongo Db, Redis o algo más exótico. Este modelo define como lucirá en C# una fila
o entrada en la base de datos así no tienes que preocuparte acerca de los detalles sobre la
base de datos en el código de tú aplicación. Este simple estilo de modelo es algunas veces
llamado a POCO (Plain Old C# Object por sus siglas en ingles).
La vista modelo
Frecuentemente, el modelo que almacenas en la base de datos es similar pero no
exactamente el mismo que deseas usar en la MVC (la vista modelo). En este caso, el modelo
TodoItem representa a un único elemento de la base de datos. Pero la vista puede
necesitar mostrar dos o cientos tareas pendientes (dependiendo que tal malamente el
usuario esta procrastinando).
Debido a esto, la vista modelo puede ser una clase separada que mantienen un arreglo de
TodoItem
Models/TodoViewModel.cs
namespace AspNetCoreTodo.Models
Ahora que tienes algunos modelos, es tiempo de crear una vista que usa un TodoViewModel
y generará el HTML correcto para mostrar al usuario su lista de tareas pendientes.
La mayoría del código de las vistas es solo HTML, con ocasionales enunciados de C#
necesarios para extraer datos del modelo de la vista y convertirlos a texto o a HTML. Los
enunciados C# tienen como prefijo el símbolo @
La vista generada por la acción Index del controlador TodoController necesita obtener
los datos de la vista modelo, (una colección de tareas pendientes) y mostrarlas en un tabla
atractiva para el usuario. Por convención, las vistas van colocadas en la carpeta Views en una
subcarpeta correspondiente al nombre del controlador. El nombre del archivo es el nombre
de la acción con un una extensión .cshtml .
Crea una carpeta llamada Todo dentro la carpeta Views , y agrega este archivo:
Views/Todo/Index.cshtml
@model TodoViewModel
@{
<div class="panel-heading">@ViewData["Title"]</div>
<thead>
<tr>
<td>✔</td>
<td>Item</td>
<td>Due</td>
</tr>
</thead>
<tr>
<td>
</td>
<td>@item.Title</td>
<td>@item.DueAt</td>
</tr>
</table>
</div>
</div>
En la parte superior del archivo, la directiva @model le dice a Razor a qué modelo espera
que se vincule esta vista. Se accede al modelo a través de la propiedad Model .
El archivo de diseño
Quizás se pregunte dónde está el resto del HTML: ¿qué pasa con la etiqueta <body> o el
encabezado y pie de página de la página? ASP.NET Core utiliza una vista de diseño que
define la estructura base en la que se procesan todas las demás vistas. Esta almacenado en
Views/Shared/_Layout.cshtml .
La plantilla predeterminada de ASP.NET Core incluye Bootstarp y JQuery en su archivo de
Layout. Puedes crear rápidamente una aplicación web, Por supuesto que puedes usar tus
propias librerías CSS y Javascript si asi lo deseas.
wwwroot/css/site.css
div.todo-panel {
margin-top: 15px;
table tr.done {
text-decoration: line-through;
color: #888;
Puedes usar reglas CSS como estas para personalizar como se visualizan y lucen tus
páginas.
ASP.NET Core y Razor pueden hacer mucho más, como vistas parciales y componentes de
vistas generadas en el servidor , pero un simple Layout y una vista es todo lo que
necesitaras por ahora. La documentación oficial de ASP.NET Core(en
https://docs.asp.netcontiene muchos ejemplos si deseas aprender más.
Puedes escribir este código directamente en el controlador, pero es una mejor práctica
mantener tu código separado. ¿Por qué? en una aplicación real grande, tendrás que hacer
malabares con muchos asuntos:
Una vez más es posible hacer todas estas cosas en un solo y enorme controlador, que
rápidamente se convertiría en difícil de manejar y probar. En lugar es común ver
aplicaciones dividen en dos o tres o más capas y tiers de tal forma que cada una maneja
uno (y solo una) aspecto de la aplicación. Esto ayuda a mantener el controlador tan simple
como sea posible, y hace más fácil probar y cambiar la lógica de negocio y el código de
acceso a base de datos después.
Para este proyecto, usaras dos capa de aplicación: una capa de presentación compuesta
de controladores y vistas que interactúan con el usuario, y una capa de servicio que
combina las reglas del negocio con el código de accesos a base de datos. La capa de
presentación ya existe asi que el siguiente paso es crear un servicio que maneja las reglas
de negocio para las tareas y las guarda en una base da datos.
La mayoría de los proyectos grandes usan una arquitectura 3-tier: una capa de
presentación, una capa lógica de servicios y una capa de repositorio de datos. Un
repositorio es una clase que que solo esta enfocada en código de acceso a base de
datos (no lógica de negocios). En esta aplicación, por simplicidad, código combinaras
estas en un sola capa de servicio pero siéntete libre de experimentar con diferentes
formas de estructurar el código.
Por convención, el nombre de las interfaces tiene el prefijo "I". Crea un nuevo archivo en el
directorio Services:
Services/ITodoItemService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetCoreTodo.Models;
namespace AspNetCoreTodo.Services
Task<TodoItem[]> GetIncompleteItemsAsync();
The type or namespace name 'TodoItem' could not be found (are you missing a
using directive or an assembly reference?)
Debido a que esta es una interfaces, no hay ningún código aquí, solo la definición (o la firma
del método) GetIncompleteItemsAsync . Este método no requiere parámetros y regresa un
objeto del tipo Task<TodoItem[]> .
El tipo Task es similar un futuro o promesa, y se usa aquí porque este método sera
asíncrono. En otras palabras es posible que el método no pueda ser capaz de regresar la
lista de tareas pendientes de forma inmediata porque necesita primero interactuar con la
base de datos primero. (Más sobre esto después).
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetCoreTodo.Models;
namespace AspNetCoreTodo.Services
DueAt = DateTimeOffset.Now.AddDays(1)
};
DueAt = DateTimeOffset.Now.AddDays(2)
};
_todoItemService = todoItemService;
using AspNetCoreTodo.Services;
La primera linea de la clase declara un campo privado para tener una referencia al
ITodoItemService . Esta variable te deja usar el servicio desde el método de acción Index
después (verás como hacerlo en un minuto).
// ...
El patrón de Task es común cuando tu código realiza peticiones a la base de datos o una
API de servicio, porque no será capaz de regresar un resultado real hasta que la base de
datos (o red) responda.Si haz usado promesas o callbacks en Javascript u otro lenguaje,
Task es la misma idea: la promesa que habrá un resultado - en algún tiempo futuro.
Si haz tenido que tratar con el "infierno callback" en el código heredado de Javascript,
estas de suerte.
Si has tenido que lidiar con el "infierno de devolución de llamada" en
un código JavaScript más antiguo, estás de suerte. Tratar con el código asíncrono en
.NET es mucho más fácil gracias a la magia de la palabra clave "esperar". await
permite que su código se detenga en una operación asíncrona, y luego retome lo que
dejó cuando la base de datos subyacente o la solicitud de red finaliza. Mientras tanto,
su aplicación no está bloqueada, ya que puede procesar otras solicitudes según sea
necesario. Este patrón es simple pero requiere un poco de tiempo para
acostumbrarse, así que no se preocupe si esto no tiene sentido de inmediato. ¡Sigue
siguiéndolo!
El único problema es que necesitas actualizar la firma del método Index para devolver un
Task<IActionResult> en lugar de IActionResult , y marcarlo como async :
El único no
tiene que actualizar la firma del método Index para regresar a Task<IActionResult> en
place of sol un IActionResult , y marcarlo con async :
Declarando (o conectando) cual clase concreta para usar para cada interfaz se hace en el
método ConfigureServices de la clase Startup . Ahora mismo algo como esto:
Startup.cs
services.AddMvc();
services.AddSingleton<ITodoItemService, FakeTodoItemService>();
Esta linea le especifica a ASP.NET Core que cada que se solicite ITodoItemService en un
constructor deberá usar la clase FakeTodoItemService .
!Esto es todo! Cuando una petición llega y es dirigida al TodoController , ASP.NET Core
buscará en los servicios disponibles y automáticamente regresara el FakeTodoItemService
cuando el controlador requiere por un ITodoItemService . Debido a que los servicios son
"inyectados" desde el contenedor de servicios, este patrón es llamado inyección de
dependencias.
Finalizando el controlador
El último paso es finalizar el código del controlador . El controlador ahora tiene un lista de
tareas de la capa de servicio, y necesita poner que los items dentro de un TodoViewModel y
enlazar este modelo a la vista creada anteriormente:
Controllers/TodoController.cs
Items = items
};
return View(model);
Si no lo haz hecho ya, asegúrate que los siguientes enunciados using estén en la parte
superior del archivo:
using AspNetCoreTodo.Services;
using AspNetCoreTodo.Models;
Si estas usando Visual Studio o Visual Studio Code, el editor te sugerirá estos enunciados
using cuando colocas el cursor en las lineas rojas.
Probando
Para iniciar la aplicación presiona F5 (si estas usando Visual Studio o Visual Studio Code), o
solo teclea dotnet run en la terminal. Si el código compila sin errores, el servidor
empezara escuchando en el puerto 5000 de forma predeterminada.
Si tu navegador
navegador no se abre de forma automática, ábrelo y navega a la dirección
http://localhost:5000/todo. Verás la vista que creaste, con los datos ficticios (por ahora)
obtenidos de la base de datos .
Actualizar el layout
El archivo de layout en Views/Shared/_Layout.cshtml contiene el código HTML base para
cada vista. Este incluye la barra de navegación, la cual es generada en la parte superior de
cada página.
Para agregar un nuevo elemento la barra de navegación, encuentra el código HTML para los
elementos existentes de la barra de navegación.
Views/Shared/_Layout.cshtml
<ul class="nav navbar-nav">
Home
</a></li>
About
</a></li>
Contact
</a></li>
</ul>
Agrega tu propio elemento que apunta hacia el controlador Todo en lugar de Home :
<li>
</li>
Los atributos asp-controller y asp-action del elemento <a> se llaman tag helpers.
Antes de generar la vista, ASP.NET Core reemplaza los tag helpers por atributos HTML reales.
En este caso, se genera una URL para la ruta /Todo/Index y se agrega al elemento como un
atributo href. Esto significa que no tiene que codificar la ruta manualmente al controlador
TodoController . En su lugar, ASP.NET Core lo genera automáticamente.
Si haz utilizado Razor en ASP.NET 4.X, notarás algunos cambios de sintaxis. En lugar de
usar @Html.ActionLink() para generar un liga hacia un acción, tag helpers son ahora
la forma recomendada de crear link en tus vistas. Tag helpers son útiles para los
formularios, también (verás porque un el siguiente capítulo). Puedes aprender más
hacer de otros tag helpers en la documentación en https://docs.asp.net.
NuGet esta compuesto por tres componentes el gestor de paquetes, el cliente de NuGet y la
galería de NuGet https://www.nuget.org . Puedes buscar paquetes en la web, e instalarlos
desde tu máquina local a través de la terminal (o Interfaz gráfica, si estas usando Visual
Studio).
La columna fecha está mostrando fechas en un formato que es bueno para las máquinas
(llamado IS0 8601), pero ambiguo para humanos. ¿No sería mejor si simplemente leemos “X
días a partir de hoy”?
Puedes escribir código por ti mismo para convertir una fecha en formato ISO 8601 en una
cadena amigable para humanos, pero afortunadamente, hay una manera mucho más
rápida.
Si das una ojeada en el archivo del proyecto AspNetCoreTodo.csproj veras una nueva línea
PackageReference que hace referencia a Humanizer .
Para usar un paquete en tu código, usualmente tienes que agregar un enunciado using
que importa el paquete al principio del archivo
Debido a que Humanizar será usado para reescribir las fechas mostradas en la vista,
puedes usarlo directamente en las vistas misma. Primero añade la directiva @using al
principio de la vista.
Views/Todo/Index.cshtml
@model TodoViewModel
@using Humanizer
// ...
Después actualiza la línea que escribe la propiedad DueAt para usar el método Humanize
del paquete Humanizer:
<td>@item.DueAt.Humanize()</td>
Hay paquetes disponibles en NuGet para todo desde parsear un XML hasta aprendizaje
automático para postear en Twitter. ASP.NET Core mismo, bajo el capo, no es más que una
colección de paquetes de NuGet que son agregados a tu proyecto.
El archivo de proyecto creado por dotnet new mvc incluye una sola referencia al
paquete Microsoft.AspNetCore.All que es un "metapaquete" conveniente que hace
referencia a todos los otros paquetes de ASP.NET Core que necesitas para un proyecto
típico. De esta forma no tienes que tener cientos de referencias a paquetes en tu
archivo de proyecto.
En el siguiente capítulo, usaras otro conjunto de paquetes de NuGet (un sistema llamado
Entity Framework Core) para escribir código que interactúa con una base de datos.
Existen varios ORM para .NET, incluido uno creado por Microsoft e incluido en ASP.NET Core
de forma predeterminada: Entity Framework Core. Entity Framework Core facilita la
conexión a varios tipos de bases de datos diferentes y le permite utilizar el código C# para
crear consultas de base de datos que se asignan nuevamente a los modelos C# (POCO Plain
Old CLR Objects).
¿Recuerda cómo crear una interfaz de servicio desacopla el código del controlador de
la clase de servicio real? Entity Framework Core es como una gran interfaz sobre su
base de datos. Su código de C# puede permanecer independiente de la base de datos,
y puede intercambiar diferentes proveedores dependiendo de la tecnología de base
de datos subyacente.
Entity Framework Core puede conectarse a bases de datos relacionales como SQL Server,
PostgreSQL y MySQL también funciona con bases de datos NoSQL (documentos) como
Mongo. Durante el desarrollo, usarás SQLite en este proyecto para facilitar la configuración
Una cadena de conexión. Ya sea que se conecte a una base de datos de archivos
local (como SQLite) o una base de datos alojada en otro lugar, definirá una cadena que
contenga el nombre o la dirección de la base de datos a la que se conectará. Esto ya
está configurado para usted en el archivo appsettings.json : la cadena de conexión
para la base de datos SQLite es DataSource = app.db .
Entity Framework Core usa el contexto de la base de datos, junto con la cadena de
conexión, para establecer una conexión con la base de datos. Debe indicar a Entity
Framework Core qué contexto, cadena de conexión y proveedor de base de datos deben
utilizar en el método ConfigureServices de la clase Startup . Esto es lo que está definido
para ti, gracias a la plantilla:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
Como puedes ver, dotnet new ¡crea muchas cosas por ti! La base de datos está
configurada y lista para ser utilizada. Sin embargo, no tiene tablas para almacenar
elementos de tareas pendientes. Para almacenar sus entidades TodoItem , necesitará
actualizar el contexto y migrar la base de datos.
Actualizar el contexto
Todavía no hay mucho que hacer en el contexto de la base de datos:
Data/ApplicationDbContext.cs
: IdentityDbContext<ApplicationUser>
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options)
: base(options)
base.OnModelCreating(builder);
// ...
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options)
: base(options)
// ...
Un DbSet representa una tabla o colección en la base de datos. Al crear una propiedad
DbSet<TodoItem> llamada Items , le está diciendo a Entity Framework Core que desea
almacenar las entidades TodoItem en una tabla llamada Items .
Has actualizado la clase de contexto, pero ahora hay un pequeño problema: el contexto y la
base de datos ahora no están sincronizados, porque en realidad no hay una tabla Items en
la base de datos. (Solo actualizar el código de la clase de contexto no cambia la base de
datos en sí).
Para actualizar la base de datos para reflejar el cambio que acaba de realizar en el contexto,
debe crear una migración.
Esto crea una nueva migración llamada AddItems al examinar cualquier cambio que hayas
realizado en el contexto.
Data/Migrations/_AddItems.cs
protected override void Up(MigrationBuilder migrationBuilder)
migrationBuilder.CreateTable(
name: "Items",
Id = table.Column<Guid>(nullable: false),
},
});
// (some code...)
migrationBuilder.DropTable(
name: "Items");
// (some code...)
El método Up se ejecuta cuando aplica la migración a la base de datos. Dado que agregó un
DbSet<TodoItem> al contexto de la base de datos, Entity Framework Core creará una tabla
Items (con columnas que coinciden con un TodoItem ) cuando aplique la migración.
El método Down hace lo contrario: si necesita deshacer (roll back) la migración, la tabla
Elementos se eliminará.
Si usa una base de datos SQL completa, como SQL Server o MySQL, esto no será un
problema y no tendrá que hacer esta solución (la cual es ciertamente hacker).
Aplicar la migración
El último paso después de crear una (o más) migraciones es aplicarlas realmente a la base
de datos:
Este comando hará que Entity Framework Core cree la tabla Items en la base de datos.
Si necesita borrar por completo la base de datos y comenzar de nuevo, ejecute dotnet
ef database database seguido de dotnet ef database update para volver a armar
la base de datos y llevarla a la migración actual.
¡Eso es! Tanto la base de datos como el contexto están listos para funcionar. A continuación,
utilizará el contexto en su capa de servicio.
Services/TodoItemService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreTodo.Data;
using AspNetCoreTodo.Models;
using Microsoft.EntityFrameworkCore;
namespace AspNetCoreTodo.Services
_context = context;
.ToArrayAsync();
return items;
Luego, el método Where se usa para filtrar solo las tareas que no están completas:
Para que el método sea un poco más corto, puedes eliminar la variable intermedia items y
simplemente devolver el resultado de la consulta directamente (que hace lo mismo):
.ToArrayAsync();
services.AddScoped<ITodoItemService, TodoItemService>();
Agregar una clase de servicio que interactúa con Entity Framework Core (y su base de
datos) con el ciclo de vida de singleton (u otros ciclos de vida) puede causar
problemas, debido a cómo Entity Framework Core administra las conexiones de base
de datos por solicitud bajo el capó. Para evitarlo, utilice siempre el ciclo de vida con
ámbito para los servicios que interactúan con Entity Framework Core.
Pruébalo
Agregar un formulario
La vista Views/Todo/Index.cshtml tiene un lugar asignado para el formulario para agregar
elementos:
</div>
Para mantener las cosas separadas y organizadas, crearas un formulario como una vista
parcial. Una vista parcial es una pequeña pieza de una vista más grande que vive en un
archivo separado.
Crea una vista llamada AddItemPartial.cshtml :
Views/Todo/AddItemPartial.cshtml
@model TodoItem
<input asp-for="Title">
<button type="submit">Add</button>
</form>
El tag helper asp-action puede generar una URL para el formulario, de forma similar
cuando la usas en un elemento <a> . En este caso, el asp-action es remplazado con una
ruta real hacia la acción AddItem que crearas:
Esto toma responsabilidad de crear la vista parcial. Ahora, haz una referencia a esta desde
la vista principal Todo:
Views/Todo/Index.cshtml
</div>
if (!ModelState.IsValid)
return RedirectToAction("Index");
if (!successful)
return RedirectToAction("Index");
Model binding busca en los datos de la solicitud e intente inteligentemente coincidir los
datos con las propiedad del modelo. En otras palabras, cuando el usuario envía el
formulario y su navegador POST hacia esta acción, ASP.NET Core obtendrá esta información
desde el formulario y la colocara en la variable newItem .
Echa un vistazo AddItemPartial.cshtml a la vista una vez más. La linea @model TodoItem
al principio del archivo le dice a ASP.NET Core que la vista debe esperar ser pareada con el
modelo TodoItem . Esto hace posible usar asp-for="Title" en la etiqueta <input> para
dejar ASP.NET Core conozca que esta elemento input es para la propiedad Title .
Porque la línea @model , la vista parcial espeta recibir un objecto TodoItem cuando es
generada. Pasando a esta un new TodoItem a través Html.PartialAsync inicializa el
formulario con una tarea en blanco.(Intenta añadir { Title = "hello" } y ve que pasa!)
Durante el model binding, cualquier propiedad del model que no pueden se coincidente
con los campos en la solicitud. Desde que el formulario solo incluye un elemento input
Title , puede espera que las otras propiedades en TodoItem (la bandera IsDone , la fecha
de DueAt ) serán vacías o contienes valores predefinidos.
En lugar de reutilizar el modelo TodoItem , otra aproximación seria crear un modelo
separado (como NewTodoItem ) que solo es usado para esta acción y solo tiene las
propiedades específicas (Titulo) que necesitas para agregar una nueva tarea.
Enlazamiento de modelo es aun usar, pero de esta forma haz separado el modelo que
es usado para guardar una tarea en la base de datos desde el modelo que es usado
para enlazar la solicitud de entrada. Es a veces llamado un binding model or a data
transfer object
Después de enlazar los dato de la solicitud al modelo, ASP.NET Core también ejecuta
validación del modelo. La validación verifica si los datos en el modelo desde la solicitud de
entrada hacen sentidos o es validad. Tu puedes agregar atributos a el modelo para decirle
ASP.NET Core como debe ser validado.
if (!ModelState.IsValid)
return RedirectToAction("Index");
if (!successful)
Como ultimo paso, necesitas agregar un método a la capa de servicio. Primero agregalo a la
definición de la interfaz en ITodoItemService :
Task<TodoItem[]> GetIncompleteItemsAsync();
newItem.Id = Guid.NewGuid();
newItem.IsDone = false;
newItem.DueAt = DateTimeOffset.Now.AddDays(3);
_context.Items.Add(newItem);
return saveResult == 1;
Pruebaló
Ejecuta la aplicación y agrega algunas tareas a tu lista mediante el formulario. Debido a que
las tareas son almacenadas en la base de datos, estas estarán ahí incluso después de
detener e iniciar la aplicación otra vez.
Como un reto extra, intenta agregar un date picker usando HTML y Javascript y deja
que el usuario elija una fecha (opcional) para la propiedad DueAt . Entonces usa, esa
fecha en lugar de siempre hacer que una nueva tarea tenga fecha de entrega en 3
días.
Completa los elementos con una casilla de
verificación
Agregar tareas a tú lista de tareas es genial, pero eventualmente necesitaras también
completar las cosas. En la vista Views/Todo/Index.cshtml , una casilla de verificación es,
mostrada para cada tarea:
Presionando la casilla de verificación no hace nada aun. Al igual que en el capítulo anterior,
agregaras este comportamiento usando formularios y acciones. En este caso necesitaras un
pequeño código en Javascript.
Views/Todo/Index.cshtml
<td>
</form>
</td>
Cuando el bucle foreach se ejecuta en la vista e imprime una fila para cada tarea
pendiente, existirá una copia de este formulario en cada fila. La entrada oculta que contiene
el ID de la tarea a realizar permite que el código de su controlador indique qué casilla se
marcó. (Sin él, podría indicar que se marcó alguna casilla, pero no cuál.)
Si ejecutas la aplicación ahora mismo, las casillas de verificación aun no hacen nada, porque
no hay un botón para submit para decir al navegador para crear una solicitud POST con los
datos del formulario. Puedes agregar un botón de submit bajo cada casilla de verificación
pero esto seria una mala experiencia de usuario. Idealmente dando clic en una casilla de
verificación debería envía el formulario. Puedes lograrlo agregando algo de código de
JavaScript.
wwwroot/js/site.js
$(document).ready(function() {
$('.done-checkbox').on('click', function(e) {
markCompleted(e.target);
});
});
function markCompleted(checkbox) {
checkbox.disabled = true;
$(row).addClass('done');
form.submit();
Este código primero usa jQuery (a una librería de apoyo en JavaScript) para adjuntar algo de
código al evento click de todos las casillas de verificación sobre la página con la clase CSS
done-checkbox . Cuando una casilla de verificación es presionada, la función
markCompleted() es ejecutada.
Agrega el atributo disabled a las casillas de verificación así estas no pueden ser
selecionadas otra vez
Agrega la clase CSS done a la fila padre que contiene la casilla de verificación, la cual
cambia la forma que la final luce basada en las reglas CSS en el archivo style.css
Enviar el formulario
Esto toma responsabilidad del la vista y el código del lado del cliente. Ahora es tiempo de
agregar una nueva acción
if (id == Guid.Empty)
return RedirectToAction("Index");
if (!successful)
return RedirectToAction("Index");
Vayamos a través de cada línea de este método de acción. Primero, el método acepta un
parámetro Guid llamado id en la firma del método. A diferencia de la acción AddItem ,
que utiliza un modelo y un modelo de enlace / validación, el parámetro id es muy simple.
Si los datos de la solicitud entrante incluyen un campo llamado id , ASP.NET Core intentará
analizarlo como una guía. Esto funciona porque el elemento oculto que agregó al
formulario de casilla de verificación se llama id .
if (!successful)
Services/ITodoItemService.cs
Services/TodoItemService.cs
.SingleOrDefaultAsync();
item.IsDone = true;
Este método usa Entity Framework Core y Where() para encontrar una tarea por ID en la
base de datos. El método SingleOrDefaultAsync() regresara una tarea o null si esta no
es encontrada.
Una vez que estas seguro que el item no es nulo, es una simple cuestión de configurar la
propiedad IsDone :
item.IsDone = true;
Cambiando la propiedad solo afecta a la copia local de la tarea hasta que el método
SaveChangesAsync() es llamada para guardar el cambio en la base de datos.
SaveChangesAsync() regresa un numero que indica cuántas entidades fueron actualizas
durante la operación de guardar. En este caso, sera o 1 (la tarea fue actualizada) o (algo
malo sucedió).
Probando
Ejecuta la aplicación y checa algunas tareas de la lista. Refrescar la página y ellas
desaparecerán completamente, porque el filtro Where() aplicado en el método
GetIncompleteItemsAsync() .
Ahora mismo, la aplicación contiene una sola, lista de tareas
compartida. Seria mucho más util si mantuviera registros de una lista de tareas individual
para cada usuario. En el siguiente capítulo, agregarás inicio de sesión y características de
seguridad al proyecto.
Seguridad e identidad
La seguridad es una de las principales preocupaciones de cualquier aplicación web
moderna o API. Es importante mantener seguros los datos de sus usuarios o usuarios y
fuera del alcance de los atacantes. Este es un tema muy amplio, que involucra cosas como:
ASP.NET Core puede ayudar a que todo esto sea más fácil de implementar. Los dos
primeros (protección contra inyección de SQL y ataques de falsificación de petición en sitios
cruzados) ya están incorporados, y puede agregar algunas líneas de código para habilitar el
soporte de HTTPS. Este capítulo se centrará principalmente en los aspectos de identidad**
de seguridad: manejo de cuentas de usuario, autenticación (inicio de sesión) de sus
usuarios de forma segura y toma de decisiones de autorización una vez que se autentiquen.
La plantilla de autenticación individual de MVC + que usó para organizar el proyecto incluye
varias clases creadas sobre ASP.NET Core Identity, un sistema de autenticación e identidad
que forma parte de ASP.NET Core. Fuera de la caja, esto agrega la capacidad de iniciar
sesión con un correo electrónico y una contraseña.
Las vistas de registro e inicio de sesión que se suministran con la plantilla de MVC +
autenticación individual ya aprovechan la identidad central de ASP.NET, ¡y ya funcionan!
Intenta registrarte para obtener una cuenta e iniciar sesión.
Requerir autenticación
A menudo, deseará que el usuario inicie sesión antes de poder acceder a ciertas partes de
su aplicación. Por ejemplo, tiene sentido mostrar la página de inicio a todos (ya sea que
haya iniciado sesión o no), pero solo mostrar su lista de tareas después de haber iniciado
sesión.
Puede usar el atributo [Authorize] en ASP.NET Core para requerir que un usuario que
haya iniciado sesión para una acción particular, o un controlador completo. Para requerir
autenticación para todas las acciones del TodoController , agregue el atributo encima de la
primera línea del controlador:
Controllers/TodoController.cs
[Authorize]
// ...
using Microsoft.AspNetCore.Authorization;
Intenta ejecutar la aplicación y acceder a /todo sin iniciar sesión. Serás redirigido a la
página de inicio de sesión automáticamente.
Controllers/TodoController.cs
[Authorize]
UserManager<ApplicationUser> userManager)
_todoItemService = todoItemService;
_userManager = userManager;
// ...
using Microsoft.AspNetCore.Identity;
La clase UserManager es parte de ASP.NET Core Identity. Puedes usarlo para obtener al
usuario actual en la acción Index :
public async Task<IActionResult> Index()
.GetIncompleteItemsAsync(currentUser);
Items = items
};
return View(model);
El nuevo código en la parte superior del método de acción utiliza el UserManager para
buscar al usuario actual en la propiedad Usuario disponible en la acción:
Si hay un usuario que ha iniciado sesión, la propiedad User contiene un objeto ligero con
algo (pero no toda) la información del usuario. El UserManager usa esto para buscar los
detalles completos del usuario en la base de datos a través del método GetUserAsync() .
El valor de currentUser nunca debe ser nulo, porque el atributo [Authorize] está
presente en el controlador. Sin embargo, es una buena idea hacer un control de cordura,
por si acaso. Puede usar el método Challenge() para forzar al usuario a iniciar sesión
nuevamente si falta su información:
Services/ITodoItemService.cs
Task<TodoItem[]> GetIncompleteItemsAsync(
ApplicationUser user);
// ...
Services/TodoItemService
public async Task<TodoItem[]> GetIncompleteItemsAsync(
ApplicationUser user)
Deberá agregar una nueva propiedad al modelo de entidad TodoItem para que cada
elemento pueda "recordar" al usuario que lo posee:
Models/TodoItem.cs
Dado que ha actualizado el modelo de entidad utilizado por el contexto de la base de datos,
también debe migrar la base de datos. Crea una nueva migración usando dotnet ef en el
terminal:
Esto crea una nueva migración llamada AddItemUserId que agregará una nueva columna a
la tabla Items , reflejando el cambio realizado en el modelo TodoItem .
Con la base de datos y el contexto de la base de datos actualizados, ahora puede actualizar
el método GetIncompleteItemsAsync() en el TodoItemService y agregar otra cláusula a la
declaración Where :
Services/TodoItemService.cs
ApplicationUser user)
.ToArrayAsync();
Si ejecuta la aplicación y se registra o inicia sesión, verá una lista de tareas vacía una vez
más. Desafortunadamente, cualquier tarea que intentes agregar desaparece en el éter,
porque aún no has actualizado la acción AddItem para que el usuario la tenga en cuenta.
Deberá usar el UserManager para obtener el usuario actual en los métodos de acción
AddItem y MarkDone , tal como lo hizo en Index .
Controllers/TodoController.cs
[ValidateAntiForgeryToken]
if (!ModelState.IsValid)
return RedirectToAction("Index");
.AddItemAsync(newItem, currentUser);
if (!successful)
return RedirectToAction("Index");
[ValidateAntiForgeryToken]
if (id == Guid.Empty)
return RedirectToAction("Index");
.MarkDoneAsync(id, currentUser);
if (!successful)
return RedirectToAction("Index");
newItem.Id = Guid.NewGuid();
newItem.IsDone = false;
newItem.DueAt = DateTimeOffset.Now.AddDays(3);
newItem.UserId = user.Id;
// ...
.SingleOrDefaultAsync();
// ...
¡Todo listo! Intenta usar la aplicación con dos cuentas de usuario diferentes. Las tareas
pendientes se mantienen privadas para cada cuenta.
En este proyecto, agregará una página para Administrar usuarios que solo los
administradores pueden ver. Si los usuarios normales intentan acceder a él, verán un error.
Controllers/ManageUsersController.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using AspNetCoreTodo.Models;
using Microsoft.EntityFrameworkCore;
namespace AspNetCoreTodo.Controllers
[Authorize(Roles = "Administrator")]
_userManager;
public ManageUsersController(
UserManager<ApplicationUser> userManager)
_userManager = userManager;
.GetUsersInRoleAsync("Administrator"))
.ToArray();
.ToArrayAsync();
Administrators = admins,
Everyone = everyone
};
return View(model);
Models/ManageUsersViewModel.cs
using System.Collections.Generic;
namespace AspNetCoreTodo.Models
Finalmente, cree una carpeta Views/ManageUsers y una vista para la acción Index :
Views/ManageUsers/Index.cshtml
@model ManageUsersViewModel
@{
<h2>@ViewData["Title"]</h2>
<h3>Administrators</h3>
<table class="table">
<thead>
<tr>
<td>Id</td>
<td>Email</td>
</tr>
</thead>
<tr>
<td>@user.Id</td>
<td>@user.Email</td>
</tr>
</table>
<h3>Everyone</h3>
<table class="table">
<thead>
<tr>
<td>Id</td>
<td>Email</td>
</tr>
</thead>
<tr>
<td>@user.Id</td>
<td>@user.Email</td>
</tr>
</table>
Inicie la aplicación e intente acceder a la ruta /ManageUsers mientras esté conectado como
un usuario normal. Verás esta página de acceso denegado:
Eso es porque a los usuarios no se les asigna automáticamente el rol de Administrador.
Por razones obvias de seguridad, no es posible que nadie registre una nueva cuenta de
administrador. De hecho, el rol de administrador ni siquiera existe en la base de datos
todavía.
SeedData.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreTodo.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace AspNetCoreTodo
IServiceProvider services)
.GetRequiredService<RoleManager<IdentityRole>>();
await EnsureRolesAsync(roleManager);
.GetRequiredService<UserManager<ApplicationUser>>();
await EnsureTestAdminAsync(userManager);
Agregue dos métodos más debajo del método InitializeAsync() . Primero, el método
VerifyRolesAsync() :
RoleManager<IdentityRole> roleManager)
.RoleExistsAsync(Constants.AdministratorRole);
if (alreadyExists) return;
await roleManager.CreateAsync(
new IdentityRole(Constants.AdministratorRole));
Este método verifica si existe un rol de Administrador en la base de datos. Si no, crea uno.
En lugar de escribir repetidamente la cadena "Administrador" , cree una pequeña clase
llamada Constants para mantener el valor:
Constants.cs
namespace AspNetCoreTodo
SeedData.cs
UserManager<ApplicationUser> userManager)
.SingleOrDefaultAsync();
UserName = "[email protected]",
Email = "[email protected]"
};
await userManager.CreateAsync(
testAdmin, "NotSecure123!!");
await userManager.AddToRoleAsync(
testAdmin, Constants.AdministratorRole);
A continuación, debe indicar a su aplicación que ejecute esta lógica cuando se inicie.
Modifique Program.cs y actualice Main() para llamar a un nuevo método,
InitializeDatabase() :
Program.cs
public static void Main(string[] args)
InitializeDatabase(host);
host.Run();
try
SeedData.InitializeAsync(services).Wait();
.GetRequiredService<ILogger<Program>>();
using Microsoft.Extensions.DependencyInjection;
Puede inyectar el UserManager directamente en una vista para realizar estos tipos de
comprobaciones de autorización. Para mantener sus vistas limpias y organizadas, cree una
nueva vista parcial que agregará un elemento a la barra de navegación en el diseño:
Views/Shared/_AdminActionsPartial.cshtml
@using Microsoft.AspNetCore.Identity
@using AspNetCoreTodo.Models
@if (signInManager.IsSignedIn(User))
currentUser,
Constants.AdministratorRole);
if (isAdmin)
<li>
<a asp-controller="ManageUsers"
asp-action="Index">
Manage Users
</a>
</li>
</ul>
Es una convención nombrar vistas parciales compartidas que comienzan con un guión
bajo _ , pero no es obligatorio.
Views/Shared/_Layout.cshtml
</ul>
@await Html.PartialAsync("_LoginPartial")
@await Html.PartialAsync("_AdminActionsPartial")
</div>
Cuando inicie sesión con una cuenta de administrador, ahora verá un nuevo elemento en la
parte superior derecha:
Más recursos
ASP.NET Core Identity le ayuda a agregar las características de seguridad e identidad como
inicio de sesión y registro a su aplicación. Las plantillas dotnet new le brindan vistas y
controladores predefinidos que manejan estos escenarios comunes para que pueda
comenzar a trabajar rápidamente.
Hay mucho más que puede hacer ASP.NET Core Identity, como restablecer la contraseña y
el inicio de sesión social. La documentación disponible en http://docs.asp.net es un recurso
fantástico para aprender a agregar estas funciones.
Para este proyecto, ASP.NET Core Identity es una excelente opción. Para proyectos más
complejos, recomiendo investigar un poco y experimentar con ambas opciones para
comprender cuál es la mejor para su caso de uso.
Pruebas automatizadas
Escribir pruebas es una parte importante del desarrollo de cualquier aplicación. Probar su
código lo ayuda a encontrar y evitar errores, y posteriormente facilita la refactorización de
su código sin descomponer la funcionalidad o introducir nuevos problemas.
En este capítulo, aprenderá cómo escribir dos tipos de pruebas: pruebas de unitarias y
pruebas de integración que utilizan su aplicación de ASP.NET Core. Las pruebas unitarias
son pruebas pequeñas que prueban que un solo método o parte de la lógica funcione
correctamente. Las pruebas de integración (a veces llamadas pruebas funcionales) son
pruebas más grandes que simulan escenarios reales y prueban múltiples capas o partes de
su aplicación.
Pruebas unitarias
Las pruebas unitarias son pruebas pequeñas y cortas que verifican el comportamiento de
un solo método o clase. Cuando el código que está probando se basa en otros métodos o
clases, las pruebas unitarias se basan en simulardores de esas otras clases para que la
prueba solo se enfoque en una cosa a la vez.
Es una buena práctica crear un proyecto separado para sus pruebas, para que se
mantengan separados del código de su aplicación. El nuevo proyecto de prueba debe vivir
en un directorio que esté al lado (no dentro) del directorio de su proyecto principal.
xUnit.NET es un marco de prueba popular para el código .NET que se puede usar para
escribir pruebas de unitarias y de integración. Como todo lo demás, es un conjunto de
paquetes NuGet que se pueden instalar en cualquier proyecto. La plantilla dotnet new
xunit ya incluye todo lo que necesitas.
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc...)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
Como el proyecto de prueba utilizará las clases definidas en su proyecto principal, deberá
agregar una referencia al proyecto AspNetCoreTodo :
Elimine el archivo UnitTest1.cs que se crea automáticamente. Estás listo para escribir tu
primera prueba.
Si está utilizando Visual Studio Code, es posible que deba cerrar y volver a abrir la
ventana de Visual Studio Code para la compleción del código funcione en el nuevo
proyecto.
Escribe una prueba de servicio
newItem.Id = Guid.NewGuid();
newItem.IsDone = false;
newItem.DueAt = DateTimeOffset.Now.AddDays(3);
newItem.UserId = user.Id;
_context.Items.Add(newItem);
return saveResult == 1;
Este método toma una serie de decisiones o suposiciones sobre el nuevo elemento (en
otras palabras, realiza la lógica de negocios en el nuevo elemento) antes de que realmente
lo guarde en la base de datos:
Puede parecer poco probable ahora que pueda introducir un cambio en la lógica
empresarial sin darse cuenta, pero es mucho más difícil hacer un seguimiento de las
decisiones y suposiciones en un proyecto grande y complejo. Cuanto más grande sea
su proyecto, más importante es tener controles automáticos que aseguren que nada
haya cambiado.
Para escribir una prueba unitaria que verifique la lógica en TodoItemService , cree una
nueva clase en su proyecto de prueba:
AspNetCoreTodo.UnitTests/TodoItemServiceShould.cs
using System;
using System.Threading.Tasks;
using AspNetCoreTodo.Data;
using AspNetCoreTodo.Models;
using AspNetCoreTodo.Services;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace AspNetCoreTodo.UnitTests
[Fact]
// ...
El atributo [Fact] proviene del paquete xUnit.NET, y marca este método como un método
de prueba.
.UseInMemoryDatabase(databaseName: "Test_AddNewItem").Options;
Id = "fake-000",
UserName = "[email protected]"
};
Title = "Testing?"
}, fakeUser);
La última línea crea un tarea llamado "¿Pruebas?", Y le dice al servicio que lo guarde en la
base de datos (en memoria).
Para verificar que la lógica de negocio funcionó correctamente, escriba un código más
debajo del bloque using existente:
.Items.CountAsync();
Assert.Equal(1, itemsInDatabase);
Assert.Equal("Testing?", item.Title);
Assert.Equal(false, item.IsDone);
Tanto las pruebas de unitarias como las de integración generalmente siguen el patrón
AAA (Arrange-Act-Assert): los objetos y los datos se configuran primero, luego se
realiza alguna acción y, finalmente, la prueba verifica (Assert) que ocurrió el
comportamiento esperado.
Confirmar un valor de fecha y hora es un poco complicado, ya que la comparación de dos
fechas para la igualdad fallará incluso si los componentes de milisegundos son diferentes.
En su lugar, la prueba verifica que el valor DueAt esté a menos de un segundo del valor
esperado.
Ejecutar la prueba
dotnet test
El comando test escanea el proyecto actual en busca de pruebas (en este caso marcadas
con los atributos [Fact] ), y ejecuta todas las pruebas que encuentra. Verás una salida
similar a:
Discovering: AspNetCoreTodo.UnitTests
Discovered: AspNetCoreTodo.UnitTests
Starting: AspNetCoreTodo.UnitTests
Finished: AspNetCoreTodo.UnitTests
Ahora tiene una prueba que proporciona cobertura de prueba del TodoItemService . Como
desafío adicional, intente escribir pruebas unitarias que aseguren:
Pruebas de integración
En comparación con las pruebas unitarias, las pruebas de integración tienen un alcance
mucho mayor. Prueba toda la pila de aplicaciones. En lugar de aislar una clase o método, las
pruebas de integración aseguran que todos los componentes de su aplicación estén
funcionando juntos correctamente: enrutamiento, controladores, servicios, código de base
de datos, etc.
Las pruebas de integración son más lentas y más complejas que las pruebas de unitarias,
por lo que es común que un proyecto tenga muchas pruebas de unitarias pequeñas pero
solo un puñado de pruebas de integración.
Para probar toda la pila (incluido el enrutamiento del controlador), las pruebas de
integración normalmente hacen llamadas HTTP a su aplicación como lo haría un navegador
web.
Para realizar una prueba de integración, puede iniciar su aplicación y realizar solicitudes
manualmente a http://localhost:5000. Sin embargo, ASP.NET Core ofrece una mejor
alternativa: la clase TestServer . Esta clase puede alojar su aplicación durante la duración
de la prueba, y luego detenerla automáticamente cuando se completa la prueba.
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc...)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
AspNetCoreTodo.IntegrationTests/
AspNetCoreTodo.IntegrationTests.csproj
Dado que el proyecto de prueba utilizará las clases definidas en su proyecto principal,
deberá agregar una referencia al proyecto principal:
Elimine el archivo UnitTest1.cs creado por dotnet new . Estás listo para escribir una
prueba de integración.
Hay algunas cosas que deben configurarse en el servidor de prueba antes de cada prueba.
En lugar de abarrotar la prueba con este código de configuración, puede mantener esta
configuración en una clase separada. Crea una nueva clase llamada TestFixture :
AspNetCoreTodo.IntegrationTests/TestFixture.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
namespace AspNetCoreTodo.IntegrationTests
public TestFixture()
.UseStartup<AspNetCoreTodo.Startup>()
config.SetBasePath(Path.Combine(
Directory.GetCurrentDirectory(),
"..\\..\\..\\..\\AspNetCoreTodo"));
config.AddJsonFile("appsettings.json");
});
Client = _server.CreateClient();
Client.Dispose();
_server.Dispose();
Ahora estás (realmente) listo para escribir una prueba de integración. Crea una nueva clase
llamada TodoRouteShould :
AspNetCoreTodo.IntegrationTests/TodoRouteShould.cs
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace AspNetCoreTodo.IntegrationTests
_client = fixture.Client;
[Fact]
// Arrange
HttpMethod.Get, "/todo");
Assert.Equal(
HttpStatusCode.Redirect,
response.StatusCode);
Assert.Equal(
"http://localhost:8888/Account" +
"/Login?ReturnUrl=%2Ftodo",
response.Headers.Location.ToString());
Esta prueba realiza una solicitud anónima (sin iniciar sesión) a la ruta /todo y verifica que
el navegador se redirige a la página de inicio de sesión.
Este escenario es un buen candidato para una prueba de integración, ya que involucra
múltiples componentes de la aplicación: el sistema de enrutamiento, el controlador, el
hecho de que el controlador está marcado con [Authorize] , y así sucesivamente. También
es una buena prueba porque garantiza que nunca quitará accidentalmente el atributo
[Authorize] y hará que la vista de tareas sea accesible para todos.
Ejecutar la prueba
Ejecute la prueba en el terminal con dotnet test . Si todo funciona bien, verás un mensaje
de éxito:
Discovering: AspNetCoreTodo.IntegrationTests
Discovered: AspNetCoreTodo.IntegrationTests
Starting: AspNetCoreTodo.IntegrationTests
Finished: AspNetCoreTodo.IntegrationTests
Resumiendo
Las pruebas son un tema amplio y hay mucho más que aprender. Este capítulo no toca el
código de prueba de interfaz de usuario (UI) ni el código de prueba (JavaScript), que
probablemente merecen libros completos por su cuenta. Sin embargo, debe tener las
habilidades y el conocimiento básico que necesita para aprender más sobre las pruebas y
practicar la escritura de pruebas para sus propias aplicaciones.
Desplegar la aplicación
Has recorrido un largo camino, pero aún no has terminado. Una vez que has creado una
gran aplicación, ¡debes compartirla con el mundo!
Debido a que las aplicaciones ASP.NET Core pueden ejecutarse en Windows, Mac o Linux,
existen varias formas diferentes de implementar su aplicación. En este capítulo, te mostraré
las formas más comunes (y más fáciles) de estar en línea.
Opciones de implementación
Las aplicaciones de ASP.NET Core se implementan normalmente en uno de estos entornos:
Windows. Puede usar el servidor web IIS en Windows para alojar aplicaciones ASP.NET
Core. Por lo general, es más fácil (y más barato) implementarlo en Azure, pero si
prefiere administrar los servidores de Windows usted mismo, funcionará bien.
ASP.NET Core incluye un servidor web rápido y ligero llamado Kestrel. Es el servidor que ha
estado usando cada vez que ejecuta dotnet run y navega a http://localhost:5000 .
Cuando implementas tu aplicación en un entorno de producción, aún utilizará Kestrel
detrás de escena. Sin embargo, se recomienda que coloque un proxy inverso delante de
Kestrel, porque Kestrel aún no tiene balance de carga y otras características que tienen los
servidores web más maduros.
En Linux (y en los contenedores Docker), puede usar Nginx o el servidor web Apache para
recibir solicitudes entrantes de Internet y enrutarlas a su aplicación alojada con Kestrel. Si
estás en Windows, IIS hace lo mismo.
Si está utilizando Azure para alojar su aplicación, todo esto se hace automáticamente.
Cubriré la configuración de Nginx como un proxy inverso en la sección de Docker.
Desplegar en Azure
La implementación de la aplicación de ASP.NET Core en Azure solo lleva unos pocos pasos.
Puede hacerlo a través del portal web de Azure o en la línea de comandos utilizando la CLI
de Azure. Voy a cubrir este último.
Lo que necesitarás
Git (usa git --version para asegurarte de que esté instalado)
El CLI de Azure (siga las instrucciones de instalación en
https://github.com/Azure/azure-cli)
Una suscripción de Azure (con la suscripción gratuita es suficiente)
Un archivo de configuración de implementación en la raíz de su proyecto.
Como hay múltiples proyectos en la estructura de su directorio (la aplicación web y dos
proyectos de prueba), Azure no sabrá cuál publicar. Para solucionar este problema, cree un
archivo llamado .deployment en la parte superior de la estructura de su directorio:
.deployment
[config]
project = AspNetCoreTodo/AspNetCoreTodo.csproj
Asegúrese de guardar el archivo como .deployment sin otras partes en el nombre. (En
Windows, puede que tenga que poner comillas alrededor del nombre del archivo, como
".deployment" , para evitar que se agregue una extensión .txt .)
Si ejecuta el comando ls o dir en su directorio principal del proyecto, debería ver estos
elementos:
.deployment
AspNetCoreTodo
AspNetCoreTodo.IntegrationTests
AspNetCoreTodo.UnitTests
az login
y siga las instrucciones para iniciar sesión en su máquina. Luego, crea un nuevo grupo de
recursos para esta aplicación:
Esto crea un grupo de recursos en la región oeste de los Estados Unidos. Si está ubicado
lejos del oeste de los Estados Unidos, use az account list-locations para obtener una
lista de ubicaciones y encontrar una más cercana a usted.
El nombre de la aplicación ( MyTodoApp arriba) debe ser globalmente único en Azure. Una
vez que se crea la aplicación, tendrá una URL predeterminada en el formato:
http://mytodoapp.azurewebsites.net
git init
git add .
Siga las instrucciones para crear una contraseña. Luego usa config-local-git para
generar una URL de Git:
https://[email protected]/MyTodoApp.git
Solo necesitas hacer estos pasos una vez. Ahora, cuando quiera enviar sus archivos de
aplicaciones a Azure, verifíquelos con Git y ejecute
git push azure master
Docker también puede hacer que escalar su aplicación en múltiples servidores sea más
fácil. Una vez que tenga una imagen, usarla para crear 1 contenedor es el mismo proceso
que crear 100 contenedores.
docker version
Lo primero que necesitará es un Dockerfile, que es como una receta que le dice a Docker lo
que su aplicación necesita para compilar y ejecutar.
Cree un archivo llamado Dockerfile (sin extensión) en la carpeta raíz, nivel superior de
AspNetCoreTodo . Ábrelo en tu editor favorito. Escribe la siguiente línea:
WORKDIR /app/AspNetCoreTodo
Ejecutar el comando dotnet restore restaura los paquetes NuGet que necesita la
aplicación, definidos en el archivo .csproj . Al restaurar los paquetes dentro de la imagen
antes agregando el resto del código, Docker puede almacenar en caché los paquetes
restaurados. Luego, si realiza cambios de código (pero no cambia los paquetes definidos en
el archivo de proyecto), la reconstrucción de la imagen de Docker será súper rápida.
El comando dotnet publish compila el proyecto, y el indicador -o out coloca los archivos
compilados en un directorio llamado out .
Estos archivos compilados se utilizarán para ejecutar la aplicación con los últimos
comandos:
WORKDIR /app
El comando FROM se usa nuevamente para seleccionar una imagen más pequeña que solo
tiene las dependencias necesarias para ejecutar la aplicación. El comando ENV se usa para
establecer variables de entorno en el contenedor, y la variable de entorno
ASPNETCORE_URLS le dice a ASP.NET Core a qué interfaz de red y puerto debe enlazarse (en
este caso, el puerto 80).
El comando ENTRYPOINT permite a Docker saber que el contenedor debe iniciarse como un
ejecutable ejecutando dotnet AspNetCoreTodo.dll . Esto le dice a dotnet que inicie su
aplicación desde el archivo compilado creado por dotnet publish anteriormente. (Cuando
haces dotnet run durante el desarrollo, estás logrando lo mismo en un solo paso).
El Dockerfile completo se ve así:
Dockerfile
WORKDIR /app/AspNetCoreTodo
COPY AspNetCoreTodo/. ./
WORKDIR /app
Asegúrese de que Dockerfile esté guardado y luego use docker build para crear una
imagen:
¡No te olvides el punto al final! Eso le dice a Docker que busque un archivo Dockerfile en el
directorio actual.
Una vez creada la imagen, puede ejecutar docker images para listar todas las imágenes
disponibles en su máquina local. Para probarlo en un contenedor, ejecute
El indicador -it le dice a Docker que ejecute el contenedor en modo interactivo (dando
salida al terminal, en lugar de ejecutarse en segundo plano). Cuando quieras detener el
contenedor, presiona Control-C.
Configurar Nginx
Al principio de este capítulo, mencioné que debería usar un proxy inverso como Nginx para
enviar las solicitudes a Kestrel. También puedes usar Docker para esto.
La arquitectura general constará de dos contenedores: un contenedor Nginx que escucha
en el puerto 80 y reenvía las solicitudes al contenedor que acaba de construir y aloja su
aplicación con Kestrel.
El contenedor Nginx necesita su propio archivo Docker. Para evitar que entre en conflicto
con el Dockerfile que acaba de crear, cree un nuevo directorio en la raíz de la aplicación
web:
mkdir nginx
nginx/Dockerfile
FROM nginx
nginx/nginx.conf
http {
server {
listen 80;
location / {
proxy_pass http://kestrel:80;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
Este archivo de configuración le dice a Nginx que haga proxy de las solicitudes entrantes a
http://kestrel:80 . (Verás por qué kestrel funciona como nombre de host en un
momento).
https://github.com/aspnet/Announcements/issues/295
Set up Docker Compose
Aquí hay un archivo más para crear. Arriba en el directorio raíz, crea docker-compose.yml :
docker-compose.yml
nginx:
build: ./nginx
links:
- kestrel:kestrel
ports:
- "80:80"
kestrel:
build: .
ports:
- "80"
docker-compose up
Intente abrir un navegador y navegue a http://localhost (puerto 80, no 8080). Nginx escucha
en el puerto 80 (el puerto HTTP predeterminado) y envía solicitudes a su aplicación ASP.NET
Core alojada por Kestrel.
Las instrucciones de configuración específicas están fuera del alcance de este libro, pero se
puede usar cualquier versión moderna de Linux (como Ubuntu) para configurar un host
Docker. Por ejemplo, podría crear una máquina virtual con Amazon EC2 e instalar el servicio
Docker. Puede buscar (por ejemplo) "configurar docker amazon ec2" para obtener
instrucciones.
Me gusta usar DigitalOcean porque han hecho que sea muy fácil comenzar. DigitalOcean
tiene una máquina virtual Docker pre-construida y tutoriales detallados para que Docker
esté en funcionamiento (busque "docker digitalocean").
Conclusión
Gracias por llegar hasta el final de El pequeño libro de ASP.NET Core!. Si este fue útil (o no)
me encantaría escuchar tus comentarios. Enviame tus comentarios vía
Twitter:https://twitter.com/jbenjamincc
ASP.NET Core in Action. Este libro por Andrew Lock es un zambullida profunda y
completa en ASP.NET Core. Puedes comprarlo en Amazon o en un librería.
Happy coding!
0xNF
Matt Welke [welkie]
Raman Zhylich [zhilich]
sahinyanlik (Turco)
windsing, yuyi (Chino simplificado)
Registro de cambios
El registro completo y detallado esta siempre disponible aquí:
https://github.com/nbarbettini/little-aspnetcore-book/releases
1.0.4 (2018-01-15): Se agregó una explicación de los ciclos de vida del contenedor de
servicios, se aclararon los puertos del servidor y el indicador -o, y se eliminaron los puntos y
coma después de las directivas Razor. Crédito de autor corregido de la traducción china.
Corrección de otros errores tipográficos y fallas encontrados por los lectores.
1.0.3 (2017-11-13): Corrección de errores tipográficos y pequeñas mejoras sugeridas por los
lectores.