En este taller vamos a crear un mantenedor de clientes que te permitirá consolidar todo lo aprendido en la teoría.
Las tecnologías y los temas que abarcaré son los siguientes:
- WEB APP .NET CORE
- Patrón MVC
- Patrón Inyección de dependencias
- Bootstrap
- Razor
- SQL Server
- Entity Framework Core
Configurando proyecto
Crear proyecto en Visual Studio, en mi caso utilizaré 2019
El tipo del proyecto es:
ASP.NET Core Web App (Model-View-Controller)
Yo le pondré de nombre MyCrud
El Target Framework a utilizar será .NET Core 3.1 y dejamos las demás configuraciones que Visual Studio trae por defecto.
Instalando dependencias
Todo proyecto es muy probable que no sólo utilice las librerías que por defecto nos trae Net Framework o Net Core, sino que también sea necesario utilizar librerías adicionales, que permitan agregarle más funcionalidades, en el caso de este proyecto utilizaremos algunas así que hay que instalarlas desde Nuget.
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
En este momento utilizaré para las tres la versión 5.0.10
En versiones previas de Net Core no hacía falta instalar el EntityFrameworkCore.SqlServer ni el EntityFrameworkCore.Tools ya que venían incluídos en el EntityFrameworkCore
Entidades
Utilizaremos Entity Framework así es que una entidad dará origen a una tabla en la base de datos debido a que utilizaré Entity Framework Core con el estilo Code First.
Crear una carpeta en el proyecto y le llamaremos Entities y nuestra primera entidad llamada Client
La forma más profesional y apropiada sería crear las entidades como una librería de clases y que sea llamada como una dependencia para hacer el código más modular y reutilizable, pero como en esta entrada la finalidad es ponernos en marcha rápidamente, no lo haremos así. De igual forma estaremos aplicando buenas prácticas usando MVC e Inyección de dependencias así que no te preocupes mucho 😉
public class Client
{
[Key] //esto hace que este campo sea primary key en la BD
public int Id { get; set; }
[Column(TypeName = "varchar(75)")]
[Display(Name = "Nombres")]
[Required(ErrorMessage = "El campo nombres es obligatorio")]
public string Nombres { get; set; }
[Column(TypeName = "varchar(75)")]
[Display(Name = "Apellidos")]
[Required(ErrorMessage = "El campo apellidos es obligatorio")]
public string Apellidos { get; set; }
[Column(TypeName = "varchar(50)")]
[Display(Name = "Departamento")]
public string Departamento { get; set; }
[Column(TypeName = "varchar(50)")]
[Display(Name = "Pais")]
public string Pais { get; set; }
[Column(TypeName = "datetime")]
[Display(Name = "Fecha de Ingreso")]
[Required(ErrorMessage = "El campo fecha de ingreso es obligatorio")]
public DateTime FechaIngreso { get; set; }
}
Estoy haciendo uso de las Data annotations, son aquellas que están entre corchetes, y nos sirven para poder ayudar a EntityFramework en el momento de crear las tablas en la base de datos cuando hagamos una migración y en el UI al momento de mostrar campos y validar campos.
Contexto y Cadena de conexión
Un contexto en EF es una clase especial, que nos proporcionará la conexión al origen de datos.
En este proyecto mi contexto se llamará ApplicationDBContext
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext(DbContextOptions options) : base(options)
{
}
public DbSet<Client> Clients { get; set; }
}
La cadena de conexión va en el archivo appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"default": "server=.;initial catalog=Test2509;integrated security=true"
}
}
Asegúrate que tus credenciales a tu Gestor de Base de datos sean correctos
Inyección de dependencias
Este tema es muy importante, te invito a leer este otro post que escribí hace un tiempito acerca de este patrón de diseño.
La forma de configurar la Inyección de dependencias es agregando una línea de código en la clase Startup.cs y el método ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDBContext>(options=>
options.UseSqlServer(Configuration.GetConnectionString("default"))
);
}
Estas líneas de código hacen posible que el contexto se inyecte en todos los controladores, ahora net core permite de forma nativa este patrón de diseño, antes era necesario utilizar librerías de terceros, hoy no 😊.
Migraciones
Las migraciones son los procedimientos mediante el cual EF costruye los objetos de base de datos a partir de nuestro código.
Para trabajar con migraciones debemos entrar en su consola llamada Package Manager console
y ejecutar los siguientes comandos, uno después del otro
add-migration Initial
update-database
Si revisamos la base de datos y no hemos tenido ningún error en la ejecución de los comandos, vamos a ver que la tabla Client fue creada según definimos nuestra entidad
Para futuras migraciones repetimos el proceso
Controladores
Crear controlador ClientsController e inyectar el contexto
public class ClientsController : Controller
{
private readonly ApplicationDBContext context;
public ClientsController(ApplicationDBContext context)
{
this.context = context;
}
public IActionResult Index()
{
return View();
}
}
Vistas
Crearemos la vista, dando clic derecho al controlador y Add view...
El nombre lo dejaremos por defecto en Index.cshtml
Sólo para testear, incluir un trozo de html en él
<h1>Hola mundo</h1>
Correr el proyecto con
CTRL + F5
Navegamos hacia la ruta clients
Añadiendo funcionalidades al controlador
Creamos los métodos AddEdit tanto para GET como para POST y el método Delete.
public class ClientsController : Controller
{
private readonly ApplicationDBContext context;
public ClientsController(ApplicationDBContext context)
{
this.context = context;
}
public IActionResult Index()
{
return View(context.Clients.ToList());
}
[HttpGet]
public async Task<IActionResult> AddEdit(int id = 0)
{
var cliente = await context.Clients.FindAsync(id);
return View(cliente);
}
[HttpPost]
public async Task<IActionResult> AddEdit(Client client)
{
if (ModelState.IsValid)
{
if (client.Id == 0)
context.Add(client);
else
context.Update(client);
await context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(client);
}
public async Task<IActionResult> Delete(int id)
{
var cliente = await context.Clients.FindAsync(id);
context.Clients.Remove(cliente);
await context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
Personalizando vistas
Vamos a hacer uso de font-awesome que es una librería de assets para íconos para embellecer los controles de la UI.
La agregamos al proyecto en el archivo _Layout.cshtml dentro de la carpeta Shared
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MyCrud</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
<script src="https://kit.fontawesome.com/e0012eeb51.js" crossorigin="anonymous"></script>
</head>
Creamos una vista al método AddEdit y la llamaremos AddEdit.cshtml
Vamos a personalizar Index.cshtml
@model IEnumerable<MyCrud.Entities.Client>
@{
ViewData["Title"] = "Inicio";
}
<h4>Listado de Clientes</h4>
<hr />
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Nombres)
</th>
<th>
@Html.DisplayNameFor(model => model.Apellidos)
</th>
<th>
@Html.DisplayNameFor(model => model.Departamento)
</th>
<th>
@Html.DisplayNameFor(model => model.FechaIngreso)
</th>
<th>
<a class="btn btn-outline-success" asp-action="AddEdit"><i class="fa fa-plus-square"></i> Cliente</a>
</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model)
{
<tr>
<td>
@Html.DisplayFor(model => item.Nombres)
</td>
<td>
@Html.DisplayFor(model => item.Apellidos)
</td>
<td>
@Html.DisplayFor(model => item.Departamento)
</td>
<td>
@Html.DisplayFor(model => item.FechaIngreso)
</td>
<td>
<a asp-action="AddEdit" asp-route-id="@item.Id"><i class="fa fa-marker fa-lg"></i></a>
<a asp-action="Delete" asp-route-id="@item.Id" class="text-danger ml-1"
onclick="return confirm('¿Está seguro que desea eliminar este registro?')"><i class="fa fa-trash-alt fa-lg"></i></a>
</td>
</tr>
}
</tbody>
</table>
Vamos a personalizar AddEdit.cshtml
@model MyCrud.Entities.Client
@{
ViewData["Title"] = "Mantenimiento";
}
<h4>Mantenedor de Clientes</h4>
<hr />
<div class="row">
<div class="col-md-6">
<form asp-action="AddEdit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-row">
<div class="form-group col-md-6">
<label asp-for="Nombres" class="control-label"></label>
<input asp-for="Nombres" class="form-control" />
<span asp-validation-for="Nombres" class="text-danger"></span>
</div>
<div class="form-group col-md-6">
<label asp-for="Apellidos" class="control-label"></label>
<input asp-for="Apellidos" class="form-control" />
<span asp-validation-for="Apellidos" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Pais" class="control-label"></label>
<input asp-for="Pais" class="form-control" />
<span asp-validation-for="Pais" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Departamento" class="control-label"></label>
<input asp-for="Departamento" class="form-control" />
<span asp-validation-for="Departamento" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FechaIngreso" class="control-label"></label>
<input asp-for="FechaIngreso" class="form-control" type="date" />
<span asp-validation-for="FechaIngreso" class="text-danger"></span>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input type="submit" value="Enviar" class="btn btn-primary btn-block" />
</div>
<div class="form-group col-md-6">
<a asp-action="Index" class="btn btn-secondary btn-block"><i class="fa fa-table"></i> Volver a listado</a>
</div>
</div>
</form>
</div>
</div>
@*Se agrega esta sección para que funcione la validación frontend*@
@section Scripts{
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Si ahora mismo corremos el proyecto, se nos lanzará el Index del controlador Home, para ver nuestra vista Clients podríamos escritir esta ruta en la barra de navegación, pero si queremos configurar la vista Clients por defecto modificaremos una línea de código en el método Configure de la clase Startup
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Clients}/{action=Index}/{id?}");
});
}
Al final el proyecto quedó con esta estructura:
Probando la web app
Ejecutamos como ya sabemos con
CTRL + F5
Agregaremos un cliente
Al guardar el registro seremos redirigidos al Index
Listo! Tenemos un mantenedor CRUD completamente funcional y lo más importante, hemos consolidado muchos conocimientos tales como el patrón MVC, entity framework, inyección de dependencias, bootstrap, razor, una línea de javascript y algo muy importante pero que no se habla mucho, el COMO TODAS ESTAS TECNOLOGÍAS SE USAN JUNTAS 🤯, ese es el camino crack, haz proyectos pequeños y anda agregándoles funcionalidad.
Para el presente proyecto me inspiré en algunos tutoriales que vi en Youtube por lo cual te recomiendo que complementes lo que aprendiste aquí con más retos de proyectos ya sea en plataformas como Youtube o cursos de Udemy.
Si esta entrada te ha encantado 😍 considera compartirla en tus redes sociales, de esa forma esparcirás el conocimiento y además despertará en ti ese bichito de compartir el conocimiento, hasta la vista cracks!
Seria perfecto si el ejemplo del crud lo hicieran con 2 DropDownList uno que elijas el Pais y el otro el Departamento. Donde Departamento depende de lo que selecciones en el Pais.
veo que solo hay cajas para digitar pero no para seleccionar como un par de DropDownList
Buena idea, lo tendré en cuenta.
Gran tutorial, muchas gracias!
Gracias por tu preferencia Andrés, siempre eres bienvenido a este rincón de la tecnología y si puedes comparte con tu equipo de tecnología así llega a muchas más personas dev! Que te vaya genial.
¡Me encanta este post!
Leo tu blog muy a menudo y siempre ofreces grandes cosas.
Lo compartí en Facebook y a mis seguidores les encantó.
¡Seguid con el buen trabajo!