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 .NET 8 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 .NET 8 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.

Vista de índice: ReleaseDate es una sola palabra (sin espacios) y en todas las fechas de lanzamiento de películas se muestra una hora de 12 a. m.

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.

Ventana del explorador con el mouse sobre el vínculo Editar donde se muestra una dirección URL de vínculo https://localhost:5001/Movies/Edit/5

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.

Vista de edición: excepción de un valor de precio incorrecto de los estados abc que indica que el campo de precio debe ser un número. Excepción de un valor incorrecto de fecha de lanzamiento de los estados xyz. Indique una fecha válida.

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

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.

Vista de índice: ReleaseDate es una sola palabra (sin espacios) y en todas las fechas de lanzamiento de películas se muestra una hora de 12 a. m.

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.

Ventana del explorador con el mouse sobre el vínculo Editar donde se muestra una dirección URL de vínculo https://localhost:5001/Movies/Edit/5

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.

Vista de edición: excepción de un valor de precio incorrecto de los estados abc que indica que el campo de precio debe ser un número. Excepción de un valor incorrecto de fecha de lanzamiento de los estados xyz. Indique una fecha válida.

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

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.

Vista de índice: ReleaseDate es una sola palabra (sin espacios) y en todas las fechas de lanzamiento de películas se muestra una hora de 12 a. m.

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.

Ventana del explorador con el mouse sobre el vínculo Editar donde se muestra una dirección URL de vínculo https://localhost:5001/Movies/Edit/5

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.

Vista de edición: excepción de un valor de precio incorrecto de los estados abc que indica que el campo de precio debe ser un número. Excepción de un valor incorrecto de fecha de lanzamiento de los estados xyz. Indique una fecha válida.

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

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.

Vista de índice: ReleaseDate es una sola palabra (sin espacios) y en todas las fechas de lanzamiento de películas se muestra una hora de 12 a. m.

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.

Ventana del explorador con el mouse sobre el vínculo Editar donde se muestra una dirección URL de vínculo https://localhost:5001/Movies/Edit/5

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.

Vista de edición: excepción de un valor de precio incorrecto de los estados abc que indica que el campo de precio debe ser un número. Excepción de un valor incorrecto de fecha de lanzamiento de los estados xyz. Indique una fecha válida.

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