Agregando un modelo a nuestra aplicación ASP.NET Core MVC
En esta parte del tutorial, vamos a agregar algunas clases para administrar películas en una base de datos. Estas clases formaran parte del “Modelo” de la aplicación MVC.
Vamos a usar estas clases con Entity Framework Core (EF Core) para trabajar con la base de datos. Entity Framework Core es un framework de mapeo de objetos a relacional (ORM) que simplifica el acceso a datos haciendo que tengamos que escribir mucho menos código. EF Core soporta muchos motores distintos de base de datos
Las clases que vamos a crear se suelen llamar clases POCO (del acrónimo Plain Old CLR Object, los viejos y simples objetos CLR ) porque no tienen ninguna dependencia en EF Core. Esto es solo una forma de nombrarlos que van a encontrar por ahí, solo significa eso, que no dependen de EF Core. Solo definen las propiedades en los datos que se van a guardar en la base de datos.
En este tutorial vamos a escribir las clases de modelo primero, y después van a crear la base de datos. Una alternativa a esto es [utilizar una base de datos existente] (https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db) aunque utilizando una base de datos existente, se dificulta trabajar con migraciones de una versión de la base de datos a otra.
Agregar una clase de datos de modelo
Hagamos click derecho en la carpeta modelo y elijamos Agregar > Clase. Le pongamos de nombre a la clase Película y agreguemos las siguientes propiedades:
using System;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
El campo Id
es requerido para que la base de datos lo use
como clave primaria.
Compilemos el proyecto para verificar que no hemos cometido ningun error. Ahora tenemos un Modelo en nuestra aplicación MVC.
Haciendo Scaffolding de un controlador
En el Explorador de Soluciones, hagamos click derecho en la carpeta Controllers y elijamos Agregar>Controlador. (Si estamos usando la 2.1 va a decir Nuevo item de Scaffolding pero es lo mismo)
Si nos sale algun cuadro de dialogo de Agregar dependencias MVC:
- Actualicemos Visual Studio a la última versión . Las versiones anteriores a la 15.5 mostraban este cuadro.
- Si no podemos actualizar, elijamos Agregar y intentemos los pasos nuevamente.
En el cuadro de dialogo Agregar Scaffold, toquemos Controlador de MVC con Vistas, usando Entity Framework y después en Agregar.
Completemos el cuadro de dialogo Agregar controlador:
- Clase de Modelo: Movie (MvcMovie.Models)
- Clase de DataContext: Selecciona el ícono m y agreguemos el valor por defecto MvcMovie.Models.MvcMovieContext
- Vistas: Elijamos las opciones por defecto chequeadas.
- Nombre de controlador: Elijamos el por defecto MoviesController.
- Elijamos Agregar
Visual Studio crea:
- Una clase de contexto de base de datos o DbContext (Data/MvcMovieContext.cs)
- Un controlador para peliculas (Controller/MoviesController)
- Archivos de vista para las páginas Crear (Create), Borrar (Delete), Edit(Editar) e Index (Views/Movies/*.cshtml)
La creación automática del DbContext y los métodos y vistas del ABMC (alta, baja, modificación y consulta) se conoce como scaffolding. Pronto vamos a tener una aplicación completamente funcional que nos deja administrar una base de datos de películas.
Si ejecutamos la aplicación y hacemos click en el enlace Mvc Movie veremos el siguiente error:
An unhandled exception occurred while processing the request.
SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.
System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString
Necesitamos crear la base de datos y utilizar las migraciones de EF Core.
Las migraciones nos dejan crear una base de datos que va con nuestro modelo de datos y actualizar el esquema de base de datos cuando nuestro modelo de datos vaya cambiando.
Agregar las herramientas de EF y la migración inicial
En esta seccion vamos a utilizar la Consola del Administrador de Paquetes para:
- Agregar el paquete Entity Framework Core Tools. Este paquete se requiere para agregar las migraciones y actualizar la base de datos.
- Agregar la migración inicial.
- Actualizar la base de datos con la migración inicial.
Desde el menú Herramientas, seleccionemos Administrador de paquetes Nuget > Consola del Administrador de Paquetes
En la consola que se va a abrir, ingresemos los siguientes comandos:
Add-Migration Initial
Update-Database
Ignoremos el siguiente mensaje de error, lo vamos a arreglar en la siguiente parte del tutorial:
Microsoft.EntityFrameworkCore.Model.Validation[30000]
No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'ForHasColumnType()'.
El comando Add-Migration
crea código que se va a usar para crear el esquema inicial de la
base de datos. El esquema está basado en el modelo especificado en el DbContext
(En el archivo Data/MvcMovieContext.cs). El argumento Initial
es el nombre que
le damos a la primera migración. Puedes utilizar cualquier nombre pero la convención
es usar uno que describa la migración.
Ver Introduction to Migrations
para más información.
El comando Update-Database
ejecuta el método Up en el archivo de la migración
Migrations/_Initial.cs lo cual crea la base de datos.
Se pueden hacer los mismos pasos utilizando la interfaz de línea de comandos (cli) en lugar de usar la consola del administrador de paquetes (pmc).
- Agrega las herramientas de EF Core al archivo csproj.
- Ejecuta los siguientes comandos desde la consola
dotnet ef migrations add Initial
dotnet ef database update
Si ejecutamos la app y nos da el error:
SqlException: Cannot open database "Movie" requested by the login.
The login failed.
Login failed for user 'user name'.
Es porque es probable que no hayamos ejecutado el comando
dotnet ef database update
.
Probemos la aplicación
- Ejecutemos la aplicación y toquemos el enlace MVC Movie.
- Toquemos en el enlace Create New y creemos una película.
[Imagen del formulario pelicula lleno]
-
Puede que no podamos entrar entrar puntos decimales o coma en el campo
Price
. Para soportar la validación de jquery en sistemas que no utilicen el idioma inglés, que usan la coma para el punto decimal, y todos los formatos de fecha distintos al de estados unidos debemos hacer los pasos para globalizar nuestra aplicación. Ver https://github.com/aspnet/Docs/issues/4076 y los recursos adicionales para más información. Por ahora solo pongamos el número 10. -
En algunas configuraciones regionales necesitamos especificar el formato de fecha. Mira el siguiente código:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Vamos a hablar sobre las DataAnnotations
después en el tutorial.
Tocar en Create causa que el formulario se postee al servidor, donde la información de las películas se guarda en la base de datos. La aplicación redirecciona a la URL /Movies, donde la información se nos muestra.
Creemos un par de películas más. Probemos los enlaces Edit, Details y Delete que todos funcionan.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}
El código de arriba muestra al contexto de base de datos (dbContext) agregado al contenedor de Inyección de dependencias en el archivo (Startup.cs).
services.AddDbContext<MvcMovieContext>(options =>
especifica que
base de datos usar.
=> es un operador lambda
Abramos el archivo Controllers/MoviesController.cs y examinemos el constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
El constructor utiliza la Inyección de Dependencias
para inyectar el dbContext (MvcMovieContext
) en el controlador.
El dbContext se usa para cada uno de los métodos
ABMC del constructor.
Modelos fuertemente tipeados con la palabra clave @model
Antes en este tutorial, vimos como un controlador puede pasar objetos a la vista usando
el diccionario ViewData
. El diccionario ViewData
es un objeto dinámico
que provee una forma conveniente de pasar información sin controlar los tipos a la vista.
MVC también nos permite pasar modelos fuertemente tipados a la vista. Esto quiere decir que la vista va a conocer que propiedades tiene nuestro modelo. Este enfoque fuertemente tipado nos permite tener mejores chequeos en tiempo de compilación. El mecanismo de scaffolding utiliza este enfoque (es decir, pasa un modelo fuertemente tipado) con el controlador de Movies cuando crea los métodos y las vistas.
Examinemos el método generado Details
en el archivo Controllers/MoviesController.cs:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
El parámetro id
generalmente se pasa como datos de ruta. Por ejemplo:
http://localhost:5000/movies/details/1
setea:
- Como controlador al parámetro
movies
(primer segmento de la URL) - Como acción a
details
(el segundo segmento de la URL) - El id como 1 (el último segmento de la url)
También podemos pasar el id con una query string de la siguiente manera:
http://localhost:1234/movies/details?id=1
El parámetro id
se define como
tipo nullable
(?int) en caso de que el valor de ID no sea provisto.
Una expresión lambda
se pasa al método FirstOrDefaultAsync
para seleccionar las entidades de películas
que unan a los datos de ruta con el valor de la cadena de conexión.
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.ID == id);
Si encontramos una película, la instancia del modelo Movie
es pasada a la vista
Details
:
return View(movie);
Examinemos el contenido del archivo Views/Movies/Details.cshtml:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Incluyendo una sentencia @model
al principio del archivo de vista,
estamos especificando el tipo de objeto que la vista espera.
Cuando creamos el controlador de películas, Visual Studio
incluyó automáticamente la siguiente sentencia @model
al principio del archivo Details.cshtml:
@model MvcMovie.Models.Movie
Esta directiva @model
nos permite acceder a la película que el controlador le
pasó a la vista usando un objeto Modelo
que es fuertemente tipado.
Por ejemplo, en la vista Details.cshtml, el código para cada campo
de la película a los métodos HTML Helpers DisplayNameFor
y DisplayFor
con el objeto Modelo
fuertemente tipado. Los métodos Create
y Edit
también pasan un objeto de modelo.
Examinemos la vista Index.cshtml y el método Index
del
controlador Movies
. Notemos como el código obtiene
una lista del DbContext y luego le pasa este objeto de lista
a la vista.
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
Cuando creamos el controlador de películas, de manera automática usando el scaffolding, se incluyó la siguiente sentencia en la parte de arriba del archivo Index.cshtml.
@model IEnumerable<MvcMovie.Models.Movie>
La directiva @model
nos permite acceder a la lista de películas
que el controlador le paso a la vista usando un objeto de Model
que es fuertemente tipado. Por ejemplo,
en la vista Index.cshtml, el código va recorriendo
a través de las películas con una sentencia foreach
a través
del objeto Modelo
fuertemente tipado.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Porque el objeto Modelo
es fuertemente tipado (como un objeto
IEnumerable<Movie>
), cada item en el ciclo es de tipo Movie
.
Entre otros beneficios esto significa que obtenemos chequeo
en tiempo de compilación del código (nos autocompleta los nombres de
las propiedades):
Vamos a ver como trabajar con la base datos y como poner datos de inicialización en la siguiente parte del tutorial.