Parte 6. Vistas y métodos de controlador en ASP.NET Core
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
Por Rick Anderson
La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, FechaDeLanzamiento debería escribirse en tres palabras.
Abra el archivo Models/Movie.cs
y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
se explican en el tutorial siguiente. El atributo Display especifica qué se muestra como nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
La anotación de datos [Column(TypeName = "decimal(18, 2)")]
es necesaria para que Entity Framework Core asigne correctamente Price
a la moneda en la base de datos. Para más información, vea Tipos de datos.
Vaya al controlador Movies
y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante el asistente de etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml
.
<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>
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper
genera dinámicamente el valor del atributo HTML href
a partir del identificador de ruta y el método de acción del controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Recupere el formato para el enrutamiento establecido en el archivo Program.cs
:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core traduce https://localhost:5001/Movies/Edit/4
en una solicitud al método de acción Edit
del controlador Movies
con el parámetro Id
de 4. (Los métodos de controlador también se denominan métodos de acción).
Los asistentes de etiquetas son una de las nuevas características más populares de ASP.NET Core. Para obtener más información, consulte Recursos adicionales.
Abra el controlador Movies
y examine los dos métodos de acción Edit
. En el código siguiente se muestra el método HTTP GET Edit
, que captura la película y rellena el formulario de edición generado por el archivo de RazorEdit.cshtml
.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
En el código siguiente se muestra el método HTTP POST Edit
, que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [Bind]
es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el atributo [Bind]
que quiera cambiar. Para más información, consulte Protección del controlador frente al exceso de publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit
va precedido del atributo [HttpPost]
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo HttpPost
especifica que este método Edit
se puede invocar solamente para solicitudes POST
. Podría aplicar el atributo [HttpGet]
al primer método de edición, pero no es necesario hacerlo porque [HttpGet]
es el valor predeterminado.
El atributo ValidateAntiForgeryToken
se usa para impedir la falsificación de una solicitud y se empareja con un token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml
). El archivo de vista de edición genera el token antifalsificación con el asistente de etiquetas de formulario.
<form asp-action="Edit">
El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación generado por [ValidateAntiForgeryToken]
en el método Edit
del controlador Movies. Para obtener más información, vea Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core.
El método HttpGet Edit
toma el parámetro ID
de la película, busca la película con el método FindAsync
de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se encuentra una película, se devuelve NotFound
(HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie
y creó código para representar los elementos <label>
y <input>
para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie
en la parte superior del archivo. @model MvcMovie.Models.Movie
especifica que la vista espera que el modelo de la plantilla de vista sea del tipo Movie
.
El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El asistente de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente de etiquetas de entrada representa un elemento HTML <input>
. El asistente de etiquetas de validación muestra cualquier mensaje de validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies
. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML generado para el elemento <form>
se muestra abajo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Los elementos <input>
se muestran en un elemento HTML <form>
cuyo atributo action
se establece para publicar en la dirección URL /Movies/Edit/id
. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Save
. La última línea antes del cierre del elemento </form>
muestra el token XSRF oculto generado por el asistente de etiquetas de formulario.
Procesamiento de la solicitud POST
En la siguiente lista se muestra la versión [HttpPost]
del método de acción Edit
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken]
valida el token XSRF oculto generado por el generador de tokens antifalsificación en el asistente de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie
que se pasa como el parámetro movie
. La propiedad ModelState.IsValid
comprueba que los datos enviados en el formulario pueden usarse para modificar (editar o actualizar) un objeto Movie
. Si los datos son válidos, se guardan. Los datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método SaveChangesAsync
del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index
de la clase MoviesController
, que muestra la colección de películas, incluidos los cambios que se acaban de hacer.
Antes de que el formulario se publique en el servidor, la validación del lado cliente comprueba las reglas de validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. El asistente de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml
se encarga de mostrar los mensajes de error correspondientes.
Todos los métodos HttpGet
del controlador de películas siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index
) y pasan el objeto (modelo) a la vista. El método Create
pasa un objeto de película vacío a la vista Create
. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga [HttpPost]
del método. La modificación de datos en un método HTTP GET
supone un riesgo de seguridad. La modificación de datos en un método HTTP GET
también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
- Globalización y localización
- Introducción a los asistentes de etiquetas
- Creación de asistentes de etiquetas
- Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core
- Protección del controlador frente al exceso de publicación
- ViewModels
- Asistente de etiquetas de formulario
- Asistente de etiquetas de entrada
- Asistente de etiquetas de elementos de etiqueta
- Asistente de etiquetas de selección
- Asistente de etiquetas de validación
La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, FechaDeLanzamiento debería escribirse en tres palabras.
Abra el archivo Models/Movie.cs
y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
se explican en el tutorial siguiente. El atributo Display especifica qué se muestra como nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
La anotación de datos [Column(TypeName = "decimal(18, 2)")]
es necesaria para que Entity Framework Core asigne correctamente Price
a la moneda en la base de datos. Para más información, vea Tipos de datos.
Vaya al controlador Movies
y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante el asistente de etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml
.
<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>
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper
genera dinámicamente el valor del atributo HTML href
a partir del identificador de ruta y el método de acción del controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Recupere el formato para el enrutamiento establecido en el archivo Program.cs
:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core traduce https://localhost:5001/Movies/Edit/4
en una solicitud al método de acción Edit
del controlador Movies
con el parámetro Id
de 4. (Los métodos de controlador también se denominan métodos de acción).
Los asistentes de etiquetas son una de las nuevas características más populares de ASP.NET Core. Para obtener más información, consulte Recursos adicionales.
Abra el controlador Movies
y examine los dos métodos de acción Edit
. En el código siguiente se muestra el método HTTP GET Edit
, que captura la película y rellena el formulario de edición generado por el archivo de RazorEdit.cshtml
.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
En el código siguiente se muestra el método HTTP POST Edit
, que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [Bind]
es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el atributo [Bind]
que quiera cambiar. Para más información, consulte Protección del controlador frente al exceso de publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit
va precedido del atributo [HttpPost]
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo HttpPost
especifica que este método Edit
se puede invocar solamente para solicitudes POST
. Podría aplicar el atributo [HttpGet]
al primer método de edición, pero no es necesario hacerlo porque [HttpGet]
es el valor predeterminado.
El atributo ValidateAntiForgeryToken
se usa para impedir la falsificación de una solicitud y se empareja con un token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml
). El archivo de vista de edición genera el token antifalsificación con el asistente de etiquetas de formulario.
<form asp-action="Edit">
El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación generado por [ValidateAntiForgeryToken]
en el método Edit
del controlador Movies. Para obtener más información, vea Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core.
El método HttpGet Edit
toma el parámetro ID
de la película, busca la película con el método FindAsync
de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se encuentra una película, se devuelve NotFound
(HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie
y creó código para representar los elementos <label>
y <input>
para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie
en la parte superior del archivo. @model MvcMovie.Models.Movie
especifica que la vista espera que el modelo de la plantilla de vista sea del tipo Movie
.
El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El asistente de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente de etiquetas de entrada representa un elemento HTML <input>
. El asistente de etiquetas de validación muestra cualquier mensaje de validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies
. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML generado para el elemento <form>
se muestra abajo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Los elementos <input>
se muestran en un elemento HTML <form>
cuyo atributo action
se establece para publicar en la dirección URL /Movies/Edit/id
. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Save
. La última línea antes del cierre del elemento </form>
muestra el token XSRF oculto generado por el asistente de etiquetas de formulario.
Procesamiento de la solicitud POST
En la siguiente lista se muestra la versión [HttpPost]
del método de acción Edit
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken]
valida el token XSRF oculto generado por el generador de tokens antifalsificación en el asistente de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie
que se pasa como el parámetro movie
. La propiedad ModelState.IsValid
comprueba que los datos enviados en el formulario pueden usarse para modificar (editar o actualizar) un objeto Movie
. Si los datos son válidos, se guardan. Los datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método SaveChangesAsync
del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index
de la clase MoviesController
, que muestra la colección de películas, incluidos los cambios que se acaban de hacer.
Antes de que el formulario se publique en el servidor, la validación del lado cliente comprueba las reglas de validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. El asistente de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml
se encarga de mostrar los mensajes de error correspondientes.
Todos los métodos HttpGet
del controlador de películas siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index
) y pasan el objeto (modelo) a la vista. El método Create
pasa un objeto de película vacío a la vista Create
. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga [HttpPost]
del método. La modificación de datos en un método HTTP GET
supone un riesgo de seguridad. La modificación de datos en un método HTTP GET
también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
- Globalización y localización
- Introducción a los asistentes de etiquetas
- Creación de asistentes de etiquetas
- Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core
- Protección del controlador frente al exceso de publicación
- ViewModels
- Asistente de etiquetas de formulario
- Asistente de etiquetas de entrada
- Asistente de etiquetas de elementos de etiqueta
- Asistente de etiquetas de selección
- Asistente de etiquetas de validación
La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, FechaDeLanzamiento debería escribirse en tres palabras.
Abra el archivo Models/Movie.cs
y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
se explican en el tutorial siguiente. El atributo Display especifica qué se muestra como nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
La anotación de datos [Column(TypeName = "decimal(18, 2)")]
es necesaria para que Entity Framework Core asigne correctamente Price
a la moneda en la base de datos. Para más información, vea Tipos de datos.
Vaya al controlador Movies
y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante el asistente de etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml
.
<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>
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper
genera dinámicamente el valor del atributo HTML href
a partir del identificador de ruta y el método de acción del controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Recupere el formato para el enrutamiento establecido en el archivo Program.cs
:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core traduce https://localhost:5001/Movies/Edit/4
en una solicitud al método de acción Edit
del controlador Movies
con el parámetro Id
de 4. (Los métodos de controlador también se denominan métodos de acción).
Los asistentes de etiquetas son una de las nuevas características más populares de ASP.NET Core. Para obtener más información, consulte Recursos adicionales.
Abra el controlador Movies
y examine los dos métodos de acción Edit
. En el código siguiente se muestra el método HTTP GET Edit
, que captura la película y rellena el formulario de edición generado por el archivo de RazorEdit.cshtml
.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
En el código siguiente se muestra el método HTTP POST Edit
, que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [Bind]
es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el atributo [Bind]
que quiera cambiar. Para más información, consulte Protección del controlador frente al exceso de publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit
va precedido del atributo [HttpPost]
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo HttpPost
especifica que este método Edit
se puede invocar solamente para solicitudes POST
. Podría aplicar el atributo [HttpGet]
al primer método de edición, pero no es necesario hacerlo porque [HttpGet]
es el valor predeterminado.
El atributo ValidateAntiForgeryToken
se usa para impedir la falsificación de una solicitud y se empareja con un token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml
). El archivo de vista de edición genera el token antifalsificación con el asistente de etiquetas de formulario.
<form asp-action="Edit">
El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación generado por [ValidateAntiForgeryToken]
en el método Edit
del controlador Movies. Para obtener más información, vea Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core.
El método HttpGet Edit
toma el parámetro ID
de la película, busca la película con el método FindAsync
de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se encuentra una película, se devuelve NotFound
(HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie
y creó código para representar los elementos <label>
y <input>
para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie
en la parte superior del archivo. @model MvcMovie.Models.Movie
especifica que la vista espera que el modelo de la plantilla de vista sea del tipo Movie
.
El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El asistente de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente de etiquetas de entrada representa un elemento HTML <input>
. El asistente de etiquetas de validación muestra cualquier mensaje de validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies
. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML generado para el elemento <form>
se muestra abajo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Los elementos <input>
se muestran en un elemento HTML <form>
cuyo atributo action
se establece para publicar en la dirección URL /Movies/Edit/id
. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Save
. La última línea antes del cierre del elemento </form>
muestra el token XSRF oculto generado por el asistente de etiquetas de formulario.
Procesamiento de la solicitud POST
En la siguiente lista se muestra la versión [HttpPost]
del método de acción Edit
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken]
valida el token XSRF oculto generado por el generador de tokens antifalsificación en el asistente de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie
que se pasa como el parámetro movie
. La propiedad ModelState.IsValid
comprueba que los datos enviados en el formulario pueden usarse para modificar (editar o actualizar) un objeto Movie
. Si los datos son válidos, se guardan. Los datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método SaveChangesAsync
del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index
de la clase MoviesController
, que muestra la colección de películas, incluidos los cambios que se acaban de hacer.
Antes de que el formulario se publique en el servidor, la validación del lado cliente comprueba las reglas de validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. El asistente de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml
se encarga de mostrar los mensajes de error correspondientes.
Todos los métodos HttpGet
del controlador de películas siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index
) y pasan el objeto (modelo) a la vista. El método Create
pasa un objeto de película vacío a la vista Create
. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga [HttpPost]
del método. La modificación de datos en un método HTTP GET
supone un riesgo de seguridad. La modificación de datos en un método HTTP GET
también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
- Globalización y localización
- Introducción a los asistentes de etiquetas
- Creación de asistentes de etiquetas
- Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core
- Protección del controlador frente al exceso de publicación
- ViewModels
- Asistente de etiquetas de formulario
- Asistente de etiquetas de entrada
- Asistente de etiquetas de elementos de etiqueta
- Asistente de etiquetas de selección
- Asistente de etiquetas de validación
La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, FechaDeLanzamiento debería escribirse en tres palabras.
Abra el archivo Models/Movie.cs
y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
DataAnnotations
se explican en el tutorial siguiente. El atributo Display especifica qué se muestra como nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
La anotación de datos [Column(TypeName = "decimal(18, 2)")]
es necesaria para que Entity Framework Core asigne correctamente Price
a la moneda en la base de datos. Para más información, vea Tipos de datos.
Vaya al controlador Movies
y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante el asistente de etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml
.
<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>
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper
genera dinámicamente el valor del atributo HTML href
a partir del identificador de ruta y el método de acción del controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Recupere el formato para el enrutamiento establecido en el archivo Program.cs
:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core traduce https://localhost:5001/Movies/Edit/4
en una solicitud al método de acción Edit
del controlador Movies
con el parámetro Id
de 4. (Los métodos de controlador también se denominan métodos de acción).
Los asistentes de etiquetas son una característica popular en ASP.NET Core. Para obtener más información, consulte: Recursos adicionales.
Abra el controlador Movies
y examine los dos métodos de acción Edit
. En el código siguiente se muestra el método HTTP GET Edit
, que captura la película y rellena el formulario de edición generado por el archivo de RazorEdit.cshtml
.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
En el código siguiente se muestra el método HTTP POST Edit
, que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [Bind]
es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el atributo [Bind]
que quiera cambiar. Para más información, consulte Protección del controlador frente al exceso de publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit
va precedido del atributo [HttpPost]
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo HttpPost
especifica que este método Edit
se puede invocar solamente para solicitudes POST
. Podría aplicar el atributo [HttpGet]
al primer método de edición, pero no es necesario hacerlo porque [HttpGet]
es el valor predeterminado.
El atributo ValidateAntiForgeryToken
se usa para impedir la falsificación de una solicitud y se empareja con un token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml
). El archivo de vista de edición genera el token antifalsificación con el asistente de etiquetas de formulario.
<form asp-action="Edit">
El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación generado por [ValidateAntiForgeryToken]
en el método Edit
del controlador Movies. Para obtener más información, vea Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core.
El método HttpGet Edit
toma el parámetro ID
de la película, busca la película con el método FindAsync
de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se encuentra una película, se devuelve NotFound
(HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie
y creó código para representar los elementos <label>
y <input>
para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie
en la parte superior del archivo. @model MvcMovie.Models.Movie
especifica que la vista espera que el modelo de la plantilla de vista sea del tipo Movie
.
El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El asistente de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente de etiquetas de entrada representa un elemento HTML <input>
. El asistente de etiquetas de validación muestra cualquier mensaje de validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies
. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML generado para el elemento <form>
se muestra abajo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Los elementos <input>
se muestran en un elemento HTML <form>
cuyo atributo action
se establece para publicar en la dirección URL /Movies/Edit/id
. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Save
. La última línea antes del cierre del elemento </form>
muestra el token XSRF oculto generado por el asistente de etiquetas de formulario.
Procesamiento de la solicitud POST
En la siguiente lista se muestra la versión [HttpPost]
del método de acción Edit
.
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken]
valida el token XSRF oculto generado por el generador de tokens antifalsificación en el asistente de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie
que se pasa como el parámetro movie
. La propiedad ModelState.IsValid
comprueba que los datos enviados en el formulario pueden usarse para modificar (editar o actualizar) un objeto Movie
. Si los datos son válidos, se guardan. Los datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método SaveChangesAsync
del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index
de la clase MoviesController
, que muestra la colección de películas, incluidos los cambios que se acaban de hacer.
Antes de que el formulario se publique en el servidor, la validación del lado cliente comprueba las reglas de validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. El asistente de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml
se encarga de mostrar los mensajes de error correspondientes.
Todos los métodos HttpGet
del controlador de películas siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index
) y pasan el objeto (modelo) a la vista. El método Create
pasa un objeto de película vacío a la vista Create
. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga [HttpPost]
del método. La modificación de datos en un método HTTP GET
supone un riesgo de seguridad. La modificación de datos en un método HTTP GET
también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
- Globalización y localización
- Introducción a los asistentes de etiquetas
- Creación de asistentes de etiquetas
- Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core
- Protección del controlador frente al exceso de publicación
- ViewModels
- Asistente de etiquetas de formulario
- Asistente de etiquetas de entrada
- Asistente de etiquetas de elementos de etiqueta
- Asistente de etiquetas de selección
- Asistente de etiquetas de validación
La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, FechaDeLanzamiento debería escribirse en tres palabras.
Abra el archivo Models/Movie.cs
y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
En el próximo tutorial hablaremos de DataAnnotations. El atributo Display especifica qué se muestra como nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
La anotación de datos [Column(TypeName = "decimal(18, 2)")]
es necesaria para que Entity Framework Core asigne correctamente Price
a la moneda en la base de datos. Para más información, vea Tipos de datos.
Vaya al controlador Movies
y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante el asistente de etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml
.
<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>
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper
genera dinámicamente el valor del atributo HTML href
a partir del identificador de ruta y el método de acción del controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Recupere el formato para el enrutamiento establecido en el archivo Startup.cs
:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core traduce https://localhost:5001/Movies/Edit/4
en una solicitud al método de acción Edit
del controlador Movies
con el parámetro Id
de 4. (Los métodos de controlador también se denominan métodos de acción).
Para obtener más información sobre los asistentes de etiquetas, consulte: Recursos adicionales.
Abra el controlador Movies
y examine los dos métodos de acción Edit
. En el código siguiente se muestra el método HTTP GET Edit
, que captura la película y rellena el formulario de edición generado por el archivo de RazorEdit.cshtml
.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
En el código siguiente se muestra el método HTTP POST Edit
, que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [Bind]
es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el atributo [Bind]
que quiera cambiar. Para más información, consulte Protección del controlador frente al exceso de publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit
va precedido del atributo [HttpPost]
.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo HttpPost
especifica que este método Edit
se puede invocar solamente para solicitudes POST
. Podría aplicar el atributo [HttpGet]
al primer método de edición, pero no es necesario hacerlo porque [HttpGet]
es el valor predeterminado.
El atributo ValidateAntiForgeryToken
se usa para impedir la falsificación de una solicitud y se empareja con un token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml
). El archivo de vista de edición genera el token antifalsificación con el asistente de etiquetas de formulario.
<form asp-action="Edit">
El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación generado por [ValidateAntiForgeryToken]
en el método Edit
del controlador Movies. Para obtener más información, vea Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core.
El método HttpGet Edit
toma el parámetro ID
de la película, busca la película con el método FindAsync
de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se encuentra una película, se devuelve NotFound
(HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie
y creó código para representar los elementos <label>
y <input>
para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie
en la parte superior del archivo. @model MvcMovie.Models.Movie
especifica que la vista espera que el modelo de la plantilla de vista sea del tipo Movie
.
El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El asistente de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente de etiquetas de entrada representa un elemento HTML <input>
. El asistente de etiquetas de validación muestra cualquier mensaje de validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies
. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML generado para el elemento <form>
se muestra abajo.
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Los elementos <input>
se muestran en un elemento HTML <form>
cuyo atributo action
se establece para publicar en la dirección URL /Movies/Edit/id
. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Save
. La última línea antes del cierre del elemento </form>
muestra el token XSRF oculto generado por el asistente de etiquetas de formulario.
Procesamiento de la solicitud POST
En la siguiente lista se muestra la versión [HttpPost]
del método de acción Edit
.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken]
valida el token XSRF oculto generado por el generador de tokens antifalsificación en el asistente de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie
que se pasa como el parámetro movie
. La propiedad ModelState.IsValid
comprueba que los datos enviados en el formulario pueden usarse para modificar (editar o actualizar) un objeto Movie
. Si los datos son válidos, se guardan. Los datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método SaveChangesAsync
del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index
de la clase MoviesController
, que muestra la colección de películas, incluidos los cambios que se acaban de hacer.
Antes de que el formulario se publique en el servidor, la validación del lado cliente comprueba las reglas de validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. El asistente de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml
se encarga de mostrar los mensajes de error correspondientes.
Todos los métodos HttpGet
del controlador de películas siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index
) y pasan el objeto (modelo) a la vista. El método Create
pasa un objeto de película vacío a la vista Create
. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga [HttpPost]
del método. La modificación de datos en un método HTTP GET
supone un riesgo de seguridad. La modificación de datos en un método HTTP GET
también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
- Globalización y localización
- Introducción a los asistentes de etiquetas
- Creación de asistentes de etiquetas
- Prevención de ataques de falsificación de solicitud entre sitios (XSRF/CSRF) en ASP.NET Core
- Protección del controlador frente al exceso de publicación
- ViewModels
- Asistente de etiquetas de formulario
- Asistente de etiquetas de entrada
- Asistente de etiquetas de elementos de etiqueta
- Asistente de etiquetas de selección
- Asistente de etiquetas de validación