Compartir vía


Parte 5. Actualización de las páginas generadas en una aplicación de ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

La aplicación de películas con scaffolding pinta bien, pero la presentación no es ideal. ReleaseDate debe tener dos palabras: Release Date (Fecha lanzamiento).

Aplicación Movie abierta en Chrome

Actualizar el modelo

Actualice Models/Movie.cs con el siguiente código resaltado:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

En el código anterior:

  • La anotación de datos [Column(TypeName = "decimal(18, 2)")] permite que Entity Framework Core asigne correctamente Price a la moneda en la base de datos. Para más información, vea Tipos de datos.
  • El atributo [Display] especifica el nombre para mostrar de un campo. En el código anterior, Release Date en lugar de ReleaseDate.
  • El atributo [DataType] especifica el tipo de datos (Date). No se muestra la información de hora almacenada en el campo.

En el próximo tutorial, hablaremos de DataAnnotations.

Vaya a Pages/Movies y mantenga el mouse sobre un vínculo de edición 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:1234/Movies/Edit/5

Los vínculos de edición, detalles y eliminación se generan mediante el Asistente de etiquetas delimitadoras en el archivo Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

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, el asistente de etiquetas de delimitador genera de forma dinámica el valor del atributo href HTML desde la página de Razor Pages (la ruta es relativa), el elemento asp-page y el identificador de ruta (asp-route-id). Vea Generación de direcciones URL para las páginas para obtener más información.

Use Ver código fuente en un explorador para examinar el marcado generado. A continuación se muestra una parte del HTML generado:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta. Por ejemplo, ?id=1 en https://localhost:5001/Movies/Details?id=1.

Adición de la plantilla de ruta

Actualice las páginas de Razor Edit, Details y Delete para usar la plantilla de ruta {id:int}. Cambie la directiva de página de cada una de estas páginas de @page a @page "{id:int}". Ejecute la aplicación y luego vea el origen.

El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Una solicitud a la página con la plantilla de ruta {id:int} que no incluya el entero devuelve un error HTTP 404 (no encontrado). Por ejemplo, https://localhost:5001/Movies/Details devuelve un error 404. Para que el identificador sea opcional, anexe ? a la restricción de ruta:

@page "{id:int?}"

Para probar el comportamiento de @page "{id:int?}":

  1. Establezca la directiva de página de Pages/Movies/Details.cshtml en @page "{id:int?}".
  2. Establezca un punto de interrupción en public async Task<IActionResult> OnGetAsync(int? id) (en Pages/Movies/Details.cshtml.cs).
  3. Navegue a https://localhost:5001/Movies/Details/.

Con la directiva @page "{id:int}", el punto de interrupción nunca se alcanza. El motor de enrutamiento devuelve HTTP 404. Con @page "{id:int?}", el método OnGetAsync devuelve NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Revisión del control de excepciones de simultaneidad

Revise el método OnPostAsync en el archivo Pages/Movies/Edit.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

El código anterior detecta las excepciones de simultaneidad cuando un cliente elimina la película y el otro cliente publica cambios en ella.

Para probar el bloque catch:

  1. establezca un punto de interrupción en catch (DbUpdateConcurrencyException).
  2. Seleccione Editar para una película y realice cambios, pero no seleccione Guardar.
  3. En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
  4. En la ventana anterior del explorador, publique los cambios en la película.

Es posible que el código de producción quiera detectar conflictos de simultaneidad. Vea Administración de conflictos de simultaneidad para más información.

Revisión de publicaciones y enlaces

Examine el archivo Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo, https://localhost:5001/Movies/Edit/3):

  • El método OnGetAsync obtiene la película en la base de datos y devuelve el método Page.
  • El método Page representa la página de Pages/Movies/Edit.cshtmlRazor. El archivo Pages/Movies/Edit.cshtml contiene la directiva de modelo (@model RazorPagesMovie.Pages.Movies.EditModel), que hace que el modelo de película esté disponible en la página.
  • Se abre el formulario de edición con los valores de la película.

Cuando se publica la página Movies/Edit:

  • Los valores del formulario de la página se enlazan a la propiedad Movie. El atributo [BindProperty] habilita el enlace de modelos.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el formulario se vuelve a mostrar con los valores enviados.

  • Si no hay ningún error en el modelo, se guarda la película.

Los métodos HTTP GET de las páginas de Razor Index, Create y Delete siguen un patrón similar. El método OnPostAsync HTTP POST de la página de Razor Create sigue un patrón similar al del método OnPostAsync de la página de Razor Edit.

Pasos siguientes

La aplicación de películas con scaffolding pinta bien, pero la presentación no es ideal. ReleaseDate debe tener dos palabras: Release Date (Fecha lanzamiento).

Aplicación Movie abierta en Chrome

Actualizar el modelo

Actualice Models/Movie.cs con el siguiente código resaltado:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

En el código anterior:

  • La anotación de datos [Column(TypeName = "decimal(18, 2)")] permite que Entity Framework Core asigne correctamente Price a la moneda en la base de datos. Para más información, vea Tipos de datos.
  • El atributo [Display] especifica el nombre para mostrar de un campo. En el código anterior, Release Date en lugar de ReleaseDate.
  • El atributo [DataType] especifica el tipo de datos (Date). No se muestra la información de hora almacenada en el campo.

En el próximo tutorial, hablaremos de DataAnnotations.

Vaya a Pages/Movies y mantenga el mouse sobre un vínculo de edición 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:1234/Movies/Edit/5

Los vínculos de edición, detalles y eliminación se generan mediante el Asistente de etiquetas delimitadoras en el archivo Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

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, el asistente de etiquetas de delimitador genera de forma dinámica el valor del atributo href HTML desde la página de Razor Pages (la ruta es relativa), el elemento asp-page y el identificador de ruta (asp-route-id). Vea Generación de direcciones URL para las páginas para obtener más información.

Use Ver código fuente en un explorador para examinar el marcado generado. A continuación se muestra una parte del HTML generado:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta. Por ejemplo, ?id=1 en https://localhost:5001/Movies/Details?id=1.

Adición de la plantilla de ruta

Actualice las páginas de Razor Edit, Details y Delete para usar la plantilla de ruta {id:int}. Cambie la directiva de página de cada una de estas páginas de @page a @page "{id:int}". Ejecute la aplicación y luego vea el origen.

El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Una solicitud a la página con la plantilla de ruta {id:int} que no incluya el entero devuelve un error HTTP 404 (no encontrado). Por ejemplo, https://localhost:5001/Movies/Details devuelve un error 404. Para que el identificador sea opcional, anexe ? a la restricción de ruta:

@page "{id:int?}"

Para probar el comportamiento de @page "{id:int?}":

  1. Establezca la directiva de página de Pages/Movies/Details.cshtml en @page "{id:int?}".
  2. Establezca un punto de interrupción en public async Task<IActionResult> OnGetAsync(int? id) (en Pages/Movies/Details.cshtml.cs).
  3. Navegue a https://localhost:5001/Movies/Details/.

Con la directiva @page "{id:int}", el punto de interrupción nunca se alcanza. El motor de enrutamiento devuelve HTTP 404. Con @page "{id:int?}", el método OnGetAsync devuelve NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Revisión del control de excepciones de simultaneidad

Revise el método OnPostAsync en el archivo Pages/Movies/Edit.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

El código anterior detecta las excepciones de simultaneidad cuando un cliente elimina la película y el otro cliente publica cambios en ella.

Para probar el bloque catch:

  1. establezca un punto de interrupción en catch (DbUpdateConcurrencyException).
  2. Seleccione Editar para una película y realice cambios, pero no seleccione Guardar.
  3. En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
  4. En la ventana anterior del explorador, publique los cambios en la película.

Es posible que el código de producción quiera detectar conflictos de simultaneidad. Vea Administración de conflictos de simultaneidad para más información.

Revisión de publicaciones y enlaces

Examine el archivo Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo, https://localhost:5001/Movies/Edit/3):

  • El método OnGetAsync obtiene la película en la base de datos y devuelve el método Page.
  • El método Page representa la página de Pages/Movies/Edit.cshtmlRazor. El archivo Pages/Movies/Edit.cshtml contiene la directiva de modelo (@model RazorPagesMovie.Pages.Movies.EditModel), que hace que el modelo de película esté disponible en la página.
  • Se abre el formulario de edición con los valores de la película.

Cuando se publica la página Movies/Edit:

  • Los valores del formulario de la página se enlazan a la propiedad Movie. El atributo [BindProperty] habilita el enlace de modelos.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el formulario se vuelve a mostrar con los valores enviados.

  • Si no hay ningún error en el modelo, se guarda la película.

Los métodos HTTP GET de las páginas de Razor Index, Create y Delete siguen un patrón similar. El método OnPostAsync HTTP POST de la página de Razor Create sigue un patrón similar al del método OnPostAsync de la página de Razor Edit.

Pasos siguientes

La aplicación de películas con scaffolding pinta bien, pero la presentación no es ideal. ReleaseDate debe tener dos palabras: Release Date (Fecha lanzamiento).

Aplicación Movie abierta en Chrome

Actualizar el modelo

Actualice Models/Movie.cs con el siguiente código resaltado:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

En el código anterior:

  • La anotación de datos [Column(TypeName = "decimal(18, 2)")] permite que Entity Framework Core asigne correctamente Price a la moneda en la base de datos. Para más información, vea Tipos de datos.
  • El atributo [Display] especifica el nombre para mostrar de un campo. En el código anterior, Release Date en lugar de ReleaseDate.
  • El atributo [DataType] especifica el tipo de datos (Date). No se muestra la información de hora almacenada en el campo.

En el próximo tutorial, hablaremos de DataAnnotations.

Vaya a Pages/Movies y mantenga el mouse sobre un vínculo de edición 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:1234/Movies/Edit/5

Los vínculos de edición, detalles y eliminación se generan mediante el Asistente de etiquetas delimitadoras en el archivo Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

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, el asistente de etiquetas de delimitador genera de forma dinámica el valor del atributo href HTML desde la página de Razor Pages (la ruta es relativa), el elemento asp-page y el identificador de ruta (asp-route-id). Vea Generación de direcciones URL para las páginas para obtener más información.

Use Ver código fuente en un explorador para examinar el marcado generado. A continuación se muestra una parte del HTML generado:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta. Por ejemplo, ?id=1 en https://localhost:5001/Movies/Details?id=1.

Adición de la plantilla de ruta

Actualice las páginas de Razor Edit, Details y Delete para usar la plantilla de ruta {id:int}. Cambie la directiva de página de cada una de estas páginas de @page a @page "{id:int}". Ejecute la aplicación y luego vea el origen.

El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Una solicitud a la página con la plantilla de ruta {id:int} que no incluya el entero devuelve un error HTTP 404 (no encontrado). Por ejemplo, https://localhost:5001/Movies/Details devuelve un error 404. Para que el identificador sea opcional, anexe ? a la restricción de ruta:

@page "{id:int?}"

Para probar el comportamiento de @page "{id:int?}":

  1. Establezca la directiva de página de Pages/Movies/Details.cshtml en @page "{id:int?}".
  2. Establezca un punto de interrupción en public async Task<IActionResult> OnGetAsync(int? id) (en Pages/Movies/Details.cshtml.cs).
  3. Navegue a https://localhost:5001/Movies/Details/.

Con la directiva @page "{id:int}", el punto de interrupción nunca se alcanza. El motor de enrutamiento devuelve HTTP 404. Con @page "{id:int?}", el método OnGetAsync devuelve NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Revisión del control de excepciones de simultaneidad

Revise el método OnPostAsync en el archivo Pages/Movies/Edit.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

El código anterior detecta las excepciones de simultaneidad cuando un cliente elimina la película y el otro cliente publica cambios en ella.

Para probar el bloque catch:

  1. establezca un punto de interrupción en catch (DbUpdateConcurrencyException).
  2. Seleccione Editar para una película y realice cambios, pero no seleccione Guardar.
  3. En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
  4. En la ventana anterior del explorador, publique los cambios en la película.

Es posible que el código de producción quiera detectar conflictos de simultaneidad. Vea Administración de conflictos de simultaneidad para más información.

Revisión de publicaciones y enlaces

Examine el archivo Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo, https://localhost:5001/Movies/Edit/3):

  • El método OnGetAsync obtiene la película en la base de datos y devuelve el método Page.
  • El método Page representa la página de Pages/Movies/Edit.cshtmlRazor. El archivo Pages/Movies/Edit.cshtml contiene la directiva de modelo (@model RazorPagesMovie.Pages.Movies.EditModel), que hace que el modelo de película esté disponible en la página.
  • Se abre el formulario de edición con los valores de la película.

Cuando se publica la página Movies/Edit:

  • Los valores del formulario de la página se enlazan a la propiedad Movie. El atributo [BindProperty] habilita el enlace de modelos.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el formulario se vuelve a mostrar con los valores enviados.

  • Si no hay ningún error en el modelo, se guarda la película.

Los métodos HTTP GET de las páginas de Razor Index, Create y Delete siguen un patrón similar. El método OnPostAsync HTTP POST de la página de Razor Create sigue un patrón similar al del método OnPostAsync de la página de Razor Edit.

Pasos siguientes

La aplicación de películas con scaffolding pinta bien, pero la presentación no es ideal. ReleaseDate debe tener dos palabras: Release Date (Fecha lanzamiento).

Aplicación Movie abierta en Chrome

Actualización del código generado

Actualice Models/Movie.cs con el siguiente código resaltado:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; } = string.Empty;

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; } = string.Empty;

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

En el código anterior:

  • La anotación de datos [Column(TypeName = "decimal(18, 2)")] permite que Entity Framework Core asigne correctamente Price a la moneda en la base de datos. Para más información, vea Tipos de datos.
  • El atributo [Display] especifica el nombre para mostrar de un campo. En el código anterior, "Release Date" en lugar de "ReleaseDate".
  • El atributo [DataType] especifica el tipo de datos (Date). No se muestra la información de hora almacenada en el campo.

En el próximo tutorial, hablaremos de DataAnnotations.

Vaya a Pages/Movies y mantenga el mouse sobre un vínculo de edición 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:1234/Movies/Edit/5

Los vínculos de edición, detalles y eliminación se generan mediante el Asistente de etiquetas delimitadoras en el archivo Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

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, el asistente de etiquetas de delimitador genera de forma dinámica el valor del atributo href HTML desde la página de Razor Pages (la ruta es relativa), el elemento asp-page y el identificador de ruta (asp-route-id). Vea Generación de direcciones URL para las páginas para obtener más información.

Use Ver código fuente en un explorador para examinar el marcado generado. A continuación se muestra una parte del HTML generado:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta. Por ejemplo, ?id=1 en https://localhost:5001/Movies/Details?id=1.

Adición de la plantilla de ruta

Actualice las páginas de Razor Edit, Details y Delete para usar la plantilla de ruta {id:int}. Cambie la directiva de página de cada una de estas páginas de @page a @page "{id:int}". Ejecute la aplicación y luego vea el origen.

El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Una solicitud a la página con la plantilla de ruta {id:int} que no incluya el entero devolverá un error HTTP 404 (no encontrado). Por ejemplo, https://localhost:5001/Movies/Details devolverá un error 404. Para que el identificador sea opcional, anexe ? a la restricción de ruta:

@page "{id:int?}"

Para probar el comportamiento de @page "{id:int?}":

  1. Establezca la directiva de página de Pages/Movies/Details.cshtml en @page "{id:int?}".
  2. Establezca un punto de interrupción en public async Task<IActionResult> OnGetAsync(int? id) (en Pages/Movies/Details.cshtml.cs).
  3. Navegue a https://localhost:5001/Movies/Details/.

Con la directiva @page "{id:int}", el punto de interrupción nunca se alcanza. El motor de enrutamiento devuelve HTTP 404. Con @page "{id:int?}", el método OnGetAsync devuelve NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Revisión del control de excepciones de simultaneidad

Revise el método OnPostAsync en el archivo Pages/Movies/Edit.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}

El código anterior detecta las excepciones de simultaneidad cuando un cliente elimina la película y el otro cliente publica cambios en ella. El código anterior no detecta los conflictos que se producen debido a que dos o más clientes editan la misma película simultáneamente. En este caso, las ediciones de varios clientes se aplican en el orden en que se llama a SaveChanges y las ediciones que se aplican más adelante pueden sobrescribir las ediciones anteriores con valores obsoletos.

Para probar el bloque catch:

  1. establezca un punto de interrupción en catch (DbUpdateConcurrencyException).
  2. Seleccione Editar para una película y realice cambios, pero no seleccione Guardar.
  3. En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
  4. En la ventana anterior del explorador, publique los cambios en la película.

Es posible que el código de producción quiera detectar conflictos de simultaneidad adicionales, como varios clientes que editan una entidad al mismo tiempo. Vea Administración de conflictos de simultaneidad para más información.

Revisión de publicaciones y enlaces

Examine el archivo Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
    }

Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo, https://localhost:5001/Movies/Edit/3):

  • El método OnGetAsync obtiene la película en la base de datos y devuelve el método Page.
  • El método Page representa la página de Pages/Movies/Edit.cshtmlRazor. El archivo Pages/Movies/Edit.cshtml contiene la directiva de modelo (@model RazorPagesMovie.Pages.Movies.EditModel), que hace que el modelo de película esté disponible en la página.
  • Se abre el formulario de edición con los valores de la película.

Cuando se publica la página Movies/Edit:

  • Los valores del formulario de la página se enlazan a la propiedad Movie. El atributo [BindProperty] habilita el enlace de modelos.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el formulario se vuelve a mostrar con los valores enviados.

  • Si no hay ningún error en el modelo, se guarda la película.

Los métodos HTTP GET de las páginas de Razor Index, Create y Delete siguen un patrón similar. El método OnPostAsync HTTP POST de la página de Razor Create sigue un patrón similar al del método OnPostAsync de la página de Razor Edit.

Pasos siguientes

La aplicación de películas con scaffolding pinta bien, pero la presentación no es ideal. ReleaseDate debe tener dos palabras: Release Date (Fecha lanzamiento).

Aplicación Movie abierta en Chrome

Actualización del código generado

Abra el archivo Models/Movie.cs y agregue las líneas resaltadas mostradas en el código siguiente:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.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 código anterior:

  • La anotación de datos [Column(TypeName = "decimal(18, 2)")] permite que Entity Framework Core asigne correctamente Price a la moneda en la base de datos. Para más información, vea Tipos de datos.
  • El atributo [Display] especifica el nombre para mostrar de un campo. En el código anterior, "Release Date" en lugar de "ReleaseDate".
  • El atributo [DataType] especifica el tipo de datos (Date). No se muestra la información de hora almacenada en el campo.

En el próximo tutorial, hablaremos de DataAnnotations.

Vaya a Pages/Movies y mantenga el mouse sobre un vínculo de edición 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:1234/Movies/Edit/5

Los vínculos de edición, detalles y eliminación se generan mediante el Asistente de etiquetas delimitadoras en el archivo Pages/Movies/Index.cshtml.

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

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, el asistente de etiquetas de delimitador genera de forma dinámica el valor del atributo href HTML desde la página de Razor Pages (la ruta es relativa), el elemento asp-page y el identificador de ruta (asp-route-id). Vea Generación de direcciones URL para las páginas para obtener más información.

Use Ver código fuente en un explorador para examinar el marcado generado. A continuación se muestra una parte del HTML generado:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta. Por ejemplo, ?id=1 en https://localhost:5001/Movies/Details?id=1.

Adición de la plantilla de ruta

Actualice las páginas de Razor Edit, Details y Delete para usar la plantilla de ruta {id:int}. Cambie la directiva de página de cada una de estas páginas de @page a @page "{id:int}". Ejecute la aplicación y luego vea el origen.

El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

Una solicitud a la página con la plantilla de ruta {id:int} que no incluya el entero devolverá un error HTTP 404 (no encontrado). Por ejemplo, https://localhost:5001/Movies/Details devolverá un error 404. Para que el identificador sea opcional, anexe ? a la restricción de ruta:

@page "{id:int?}"

Para probar el comportamiento de @page "{id:int?}":

  1. Establezca la directiva de página de Pages/Movies/Details.cshtml en @page "{id:int?}".
  2. Establezca un punto de interrupción en public async Task<IActionResult> OnGetAsync(int? id) (en Pages/Movies/Details.cshtml.cs).
  3. Navegue a https://localhost:5001/Movies/Details/.

Con la directiva @page "{id:int}", el punto de interrupción nunca se alcanza. El motor de enrutamiento devuelve HTTP 404. Con @page "{id:int?}", el método OnGetAsync devuelve NotFound (HTTP 404):

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

Revisión del control de excepciones de simultaneidad

Revise el método OnPostAsync en el archivo Pages/Movies/Edit.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.ID == id);
}

El código anterior detecta las excepciones de simultaneidad cuando un cliente elimina la película y el otro cliente publica cambios en ella.

Para probar el bloque catch:

  1. establezca un punto de interrupción en catch (DbUpdateConcurrencyException).
  2. Seleccione Editar para una película y realice cambios, pero no seleccione Guardar.
  3. En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
  4. En la ventana anterior del explorador, publique los cambios en la película.

Es posible que el código de producción quiera detectar conflictos de simultaneidad. Vea Administración de conflictos de simultaneidad para más información.

Revisión de publicaciones y enlaces

Examine el archivo Pages/Movies/Edit.cshtml.cs:

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.ID == id);
    }

Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo, https://localhost:5001/Movies/Edit/3):

  • El método OnGetAsync obtiene la película en la base de datos y devuelve el método Page.
  • El método Page representa la página de Pages/Movies/Edit.cshtmlRazor. El archivo Pages/Movies/Edit.cshtml contiene la directiva de modelo (@model RazorPagesMovie.Pages.Movies.EditModel), que hace que el modelo de película esté disponible en la página.
  • Se abre el formulario de edición con los valores de la película.

Cuando se publica la página Movies/Edit:

  • Los valores del formulario de la página se enlazan a la propiedad Movie. El atributo [BindProperty] habilita el enlace de modelos.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el formulario se vuelve a mostrar con los valores enviados.

  • Si no hay ningún error en el modelo, se guarda la película.

Los métodos HTTP GET de las páginas de Razor Index, Create y Delete siguen un patrón similar. El método OnPostAsync HTTP POST de la página de Razor Create sigue un patrón similar al del método OnPostAsync de la página de Razor Edit.

Pasos siguientes