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 10 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 soporte de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 10 de este artículo.

La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, ReleaseDate debería escribirse en tres palabras.

Vista de índice: la fecha de lanzamiento es una palabra (sin espacio).

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).

Con las anotaciones de datos anteriores, ejecute la aplicación y actualice la /Movies página. Dado que el marcado de vista usa los @Html.DisplayNameFor métodos y @Html.DisplayFor para representar el nombre y el valor de la propiedad, la vista actualizada Index muestra todos los campos con el formato correcto. Por ejemplo, Fecha de lanzamiento es ahora dos palabras.

Vista de índice: la fecha de lanzamiento es dos palabras.

La anotación de datos [Column(TypeName = "decimal(18, 2)")] es necesaria para que Entity Framework Core asigne correctamente Price al tipo de datos de 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 navegador con el ratón sobre el enlace Editar y la URL del enlace se muestra en la ventana.

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>

Recuerde el formato para el enrutamiento establecido en el Program.cs archivo:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();

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 Contenido relacionado.

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 incluirse en el atributo [Bind] las propiedades 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 Form Tag Helper.

<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 generada por el sistema de generación automática de código 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 la vista sea de tipo Movie.

El código con scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El Label Tag Helper muestra el nombre del campo ("Title", "ReleaseDate", "Genre" o "Price"). El asistente de etiquetas de entrada representa un elemento HTML <input>. El Tag Helper 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 el siguiente listado 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 realizar.

Antes de que el formulario se envíe al 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: una excepción para un valor incorrecto de Precio de abc indica que el campo Precio debe ser un número. Una excepción para un valor incorrecto de Fecha de Lanzamiento indica que debe introducir 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 ese 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.

Tenemos un buen comienzo para la aplicación de películas, pero la presentación no es ideal; por ejemplo, ReleaseDate debería escribirse en dos 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.

Con las anotaciones de datos anteriores, ejecute la aplicación y actualice la /Movies página. Dado que el marcado de vista usa los @Html.DisplayNameFor métodos y @Html.DisplayFor para representar el nombre y el valor de la propiedad, la vista actualizada Index muestra todos los campos con el formato correcto. Por ejemplo, La fecha de lanzamiento es ahora dos palabras y la información de hora ya no se muestra.

Vista del índice:

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 método de acción de controlador y del identificador de ruta. Use Ver código fuente en su navegador favorito o 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 Tag Helpers son una de las nuevas características más populares de ASP.NET Core. Para obtener más información, consulte Contenido relacionado.

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 obtiene la película y rellena el formulario de edición generado por el archivo Edit.cshtmlRazor.

// 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);
}

El siguiente código muestra el método HTTP POST Edit, que procesa los valores enviados de la película:

// 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 over-posting. 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 Form Tag Helper.

<form asp-action="Edit">

El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación de [ValidateAntiForgeryToken] generado 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 Label Tag Helper muestra el nombre del campo ("Title", "ReleaseDate", "Genre" o "Price"). El Input Tag Helper genera un elemento HTML <input>. El Validation Tag Helper 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 navegador, 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 envíe al 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 envía 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 Tag Helper 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: una excepción por un valor incorrecto de Price, abc, indica que el campo Price debe ser un número. Una excepción por un valor incorrecto de Release Date, xyz, indica: Introduzca 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 seguro, no tener efectos secundarios y no debería modificar los datos persistidos.

La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, ReleaseDate 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 datos (Date), así que la información de la 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 Tag Helper de anclaje de Core MVC 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 Contenido relacionado.

Abra el controlador Movies y examine los dos métodos de acción Edit. El siguiente código muestra el método HTTP GET Edit, que obtiene la película y rellena el formulario de edición generado por el archivo Edit.cshtmlRazor.

// 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 forma de protegerse frente al over-posting. Solo debe incluir las 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 Tag Helper 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 Tag Helper 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 recién realizados.

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 envía 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 Validation Tag Helper 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 seguro, no tener efectos secundarios y no modificar sus datos almacenados de forma persistente.

La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, ReleaseDate debería escribirse en tres palabras.

Vista de índice: «Release Date» aparece como una sola palabra (sin espacio) y en todas las fechas de estreno de las películas se muestra la hora 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 Tag Helpers son una de las nuevas características más populares de ASP.NET Core. Para obtener más información, consulte Contenido relacionado.

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 forma de proteger frente a la sobreasignació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 ofrecen un enfoque alternativo para evitar la asignación masiva.

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 Edit método se puede invocar solamente para POST solicitudes. 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 evitar la falsificación de una petición y se combina con un token antifalsificación generado en el archivo de la vista de edición (Views/Movies/Edit.cshtml). El archivo de vista de edición genera el token antifalsificación con el Form Tag Helper.

<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 como argumento el parámetro ID, busca la película mediante el método FindAsync de Entity Framework y devuelve la película seleccionada a la vista Edit. 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 generado mediante scaffolding usa varios métodos del asistente de etiquetas para simplificar el marcado HTML. El Label Tag Helper muestra el nombre del campo ("Title", "ReleaseDate", "Genre" o "Price"). El Tag Helper Input genera 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 navegador, 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> están 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 enviarán al servidor cuando se haga clic en el Save botón. 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 envíe al 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 envía 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 Tag Helper 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: una excepción por un valor incorrecto de Price, abc, indica que el campo Price debe ser un número. Una excepción por un valor incorrecto de Release Date, xyz, indica: Introduzca 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. Modificar datos en un HTTP GET método 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 seguro, no tener efectos secundarios y no modificar sus datos almacenados de forma persistente.

La aplicación de películas tiene buena pinta, pero la presentación no es la ideal. Por ejemplo, ReleaseDate 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 datos (Date), así que la información de la 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 asocie correctamente Price con un tipo monetario 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 Tag Helper de anclaje de Core MVC 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).

Tag Helpers son una característica popular en ASP.NET Core. Para obtener más información sobre ellos, consulte Contenido relacionado.

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 obtiene la película y rellena el formulario de edición generado por el archivo Edit.cshtmlRazor.

// 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);
}

El siguiente código muestra el método HTTP POST Edit, que procesa los valores enviados de la película:

// 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 over-posting. 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 Form Tag Helper.

<form asp-action="Edit">

El asistente de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el token antifalsificación de [ValidateAntiForgeryToken] generado 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 Label Tag Helper muestra el nombre del campo ("Title", "ReleaseDate", "Genre" o "Price"). El Input Tag Helper genera un elemento HTML <input>. El Validation Tag Helper 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 navegador, 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 envíe al 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 envía 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 Tag Helper 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. Modificar datos en un método HTTP GET también vulnera las prácticas recomendadas de HTTP y el patrón arquitectónico 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, y no debería modificar sus datos persistentes.

Tenemos un buen comienzo para la aplicación de películas, pero la presentación no es ideal; por ejemplo, ReleaseDate debería escribirse en dos 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 añada 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 Tag Helper de anclaje de Core MVC 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>

Los Tag Helpers permiten que el código del servidor participe en la creación y representación de elementos HTML en archivos 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, vea Contenido relacionado.

Abra el Movies controlador y examine los dos Edit métodos de acción. En el código siguiente se muestra el método HTTP GET Edit, que recupera la película y rellena el formulario de edición generado por el archivo Edit.cshtmlRazor.

// 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 over-posting. 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 Edit método se puede invocar solamente para POST solicitudes. 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 Tag Helper Input genera 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 Form Tag Helper.

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 recién realizados.

Antes de que el formulario se envíe al servidor, la validación en el cliente comprueba las reglas de validación de 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 Tag Helper de validación de la plantilla de vista Views/Movies/Edit.cshtml se encarga de mostrar los mensajes de error adecuados.

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 arquitectónico 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.