Parte 3. Razor Pages con scaffolding en ASP.NET Core

Nota:

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

Importante

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

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

Por Rick Anderson

En este tutorial se examinan las instancias de Razor Pages creadas con la técnica scaffolding en el tutorial anterior.

Páginas de creación, eliminación, detalles y edición

Examine el modelo de Pages/Movies/Index.cshtml.cs Pages:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

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

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

    public IList<Movie> Movie { get;set; }  = default!;

    public async Task OnGetAsync()
    {
        if (_context.Movie != null)
        {
            Movie = await _context.Movie.ToListAsync();
        }
    }
}

Las instancias de Razor Pages derivan de PageModel. Por convención, la clase derivada de PageModel se denomina PageNameModel. Por ejemplo, la página Index se denomina IndexModel.

El constructor aplica la inserción de dependencias para agregar RazorPagesMovieContext a la página:

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

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

Vea Código asincrónico para obtener más información sobre programación asincrónica con Entity Framework.

Cuando se efectúa una solicitud GET de la página, el método OnGetAsync devuelve una lista de películas a la instancia Razor de Page. En la instancia de Razor Pages, se llama a OnGetAsync o OnGet para inicializar el estado de la página. En este caso, OnGetAsync obtiene una lista de películas y las muestra.

Cuando OnGet devuelve void o OnGetAsync devuelve Task, no se utiliza ninguna instrucción de devolución. Por ejemplo, examine la página Privacy:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesMovie.Pages
{
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Cuando el tipo de valor devuelto es IActionResult o Task<IActionResult>, se debe proporcionar una instrucción return. Por ejemplo, la llamada al método de Pages/Movies/Create.cshtml.cs OnPostAsync:

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

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Examine la instancia de Razor Pages Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@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>

Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va seguido de una palabra clave reservada de Razor, se realiza una transición a un marcado específico de Razor; en caso contrario, la transición se realiza a C#.

La directiva @page

La directiva @page de Razor convierte el archivo en una acción de MVC, lo que significa que puede administrar las solicitudes. @page debe ser la primera directiva de Razor de una página. @page y @model son ejemplos de la transición a un marcado específico de Razor. Vea Sintaxis de Razor para obtener más información.

La directiva @model

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

La directiva @model especifica el tipo del modelo que se pasa a una instancia de Razor Pages. En el ejemplo anterior, la línea @model permite que la clase derivada de PageModel esté disponible en la instancia de Razor Pages. El modelo se usa en los asistentes de HTML@Html.DisplayNameFor y @Html.DisplayFor de la página.

Examine la expresión lambda usada en el siguiente asistente de HTML:

@Html.DisplayNameFor(model => model.Movie[0].Title)

El asistente de HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto significa que no hay ninguna infracción de acceso si model, model.Movie o model.Movie[0] son null o están vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title)), se evalúan los valores de propiedad del modelo.

Página de diseño

Seleccione los vínculos de menú RazorPagesMovie, Home y Privacy. Cada página muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Pages/Shared/_Layout.cshtml.

Abra y examine el archivo Pages/Shared/_Layout.cshtml.

Las plantillas de diseño permiten que el diseño del contenedor HTML:

  • Esté especificado en un solo lugr.
  • Se aplique en varias páginas del sitio.

Busque la línea @RenderBody(). RenderBody es un marcador de posición donde se muestran todas las vistas específicas de página, encapsuladas en la página de diseño. Por ejemplo, seleccione el vínculo Privacy y la vista Pages/Privacy.cshtml se representa dentro del método RenderBody.

Propiedades ViewData y Layout

Tenga en cuenta el siguiente marcado del archivo Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

El marcado resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un bloque de código de C#.

La clase base PageModel contiene una propiedad de diccionario ViewData que se puede usar para pasar datos a una vista. Los objetos se agregan al diccionario ViewData con un patrón clave-valor. En el ejemplo anterior, la propiedad Title se agrega al diccionario ViewData.

La propiedad Title se usa en el archivo Pages/Shared/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo _Layout.cshtml.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorPagesMovie</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-version="true" />

Actualizar el diseño

  1. Cambie el elemento <title> del archivo Pages/Shared/_Layout.cshtml para mostrar Movie en lugar de RazorPagesMovie.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>@ViewData["Title"] - Movie</title>
    
  2. Busque el siguiente elemento delimitador en el archivo Pages/Shared/_Layout.cshtml.

    <a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
    
  3. Reemplace el elemento anterior por el marcado siguiente:

    <a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
    

    El elemento delimitador anterior es un asistente de etiquetas. En este caso, se trata de el asistente de etiquetas Anchor. El atributo y valor del asistente de etiquetas asp-page="/Movies/Index" crea un vínculo a /Movies/Index en Razor Pages. El valor de atributo asp-area está vacío, por lo que no se usa el área del vínculo. Consulte Áreas para obtener más información.

  4. Guarde los cambios y pruebe la aplicación seleccionando el vínculo RpMovie (Película de RP). Si tiene cualquier problema, consulte el archivo _Layout.cshtml en GitHub.

  5. Pruebe los vínculos Home, RpMovie, Create, Edit y Delete. Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título para el marcador.

Nota

Es posible que no pueda escribir comas decimales en el campo Price. Para que la validación de jQuery sea compatible con configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un punto decimal y formatos de fecha distintos del de Estados Unidos, debe seguir unos pasos para globalizar la aplicación. Consulte este problema 4076 de GitHub para instrucciones sobre cómo agregar la coma decimal.

La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

El marcado anterior establece el archivo de diseño de todos los archivos de Razor de la carpeta Pages en Pages/Shared/_Layout.cshtml. Vea Layout (Diseño) para más información.

Modelo de página Crear

Examine el modelo de página Pages/Movies/Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

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

        public IActionResult OnGet()
        {
            return Page();
        }

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

        // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
        public async Task<IActionResult> OnPostAsync()
        {
          if (!ModelState.IsValid || _context.Movie == null || Movie == null)
            {
                return Page();
            }

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado que inicializar, de modo que se devuelve Page. Más adelante en el tutorial se muestra un ejemplo del estado de inicialización de OnGet. El método Page crea un objeto PageResult que representa la página Create.cshtml.

La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los valores publicados con el modelo Movie.

El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:

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

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en una fecha. Más adelante en el tutorial, hablaremos de la validación del lado cliente y de la validación de modelos.

Si no hay ningún error de modelo:

  • Los datos se guardan.
  • El explorador se redirige a la página Index.

La página de creación de instancias de Razor Pages

Examine el archivo de instancia de Razor Pages Pages/Movies/Create.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>
                <input asp-for="Movie.Title" class="form-control" />
                <span asp-validation-for="Movie.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.ReleaseDate" class="control-label"></label>
                <input asp-for="Movie.ReleaseDate" class="form-control" />
                <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Genre" class="control-label"></label>
                <input asp-for="Movie.Genre" class="form-control" />
                <span asp-validation-for="Movie.Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Price" class="control-label"></label>
                <input asp-for="Movie.Price" class="form-control" />
                <span asp-validation-for="Movie.Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio muestra las etiquetas siguientes con una fuente negrita diferenciada que se aplica a los asistentes de etiquetas:

  • <form method="post">
  • <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  • <label asp-for="Movie.Title" class="control-label"></label>
  • <input asp-for="Movie.Title" class="form-control" />
  • <span asp-validation-for="Movie.Title" class="text-danger"></span>

Vista de VS17 de la página Create.cshtml

El elemento <form method="post"> es un asistente de etiquetas de formulario. El asistente de etiquetas de formulario incluye automáticamente un token antifalsificación.

El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar al siguiente:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Los asistentes de etiquetas de validación (<div asp-validation-summary y <span asp-validation-for) muestran errores de validación. La validación se trata con más detalle en un punto posterior de esta serie.

El asistente de etiquetas (<label asp-for="Movie.Title" class="control-label"></label>) genera el título de la etiqueta y el atributo [for] para la propiedad Title.

El asistente de etiquetas de entrada (<input asp-for="Movie.Title" class="form-control">) usa los atributos DataAnnotations y genera los atributos HTML necesarios para la validación de jQuery en el lado del cliente.

Para obtener más información sobre los asistentes de etiquetas como <form method="post">, consulte Asistentes de etiquetas en ASP.NET Core.

Pasos siguientes

Páginas de creación, eliminación, detalles y edición

Examine el modelo de Pages/Movies/Index.cshtml.cs Pages:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

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

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

    public IList<Movie> Movie { get;set; }  = default!;

    public async Task OnGetAsync()
    {
        if (_context.Movie != null)
        {
            Movie = await _context.Movie.ToListAsync();
        }
    }
}

Las instancias de Razor Pages derivan de PageModel. Por convención, la clase derivada de PageModel se denomina PageNameModel. Por ejemplo, la página Index se denomina IndexModel.

El constructor aplica la inserción de dependencias para agregar RazorPagesMovieContext a la página:

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

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

Vea Código asincrónico para obtener más información sobre programación asincrónica con Entity Framework.

Cuando se efectúa una solicitud GET de la página, el método OnGetAsync devuelve una lista de películas a la instancia Razor de Page. En la instancia de Razor Pages, se llama a OnGetAsync o OnGet para inicializar el estado de la página. En este caso, OnGetAsync obtiene una lista de películas y las muestra.

Cuando OnGet devuelve void o OnGetAsync devuelve Task, no se utiliza ninguna instrucción de devolución. Por ejemplo, examine la página Privacy:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesMovie.Pages
{
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Cuando el tipo de valor devuelto es IActionResult o Task<IActionResult>, se debe proporcionar una instrucción return. Por ejemplo, la llamada al método de Pages/Movies/Create.cshtml.cs OnPostAsync:

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

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Examine la instancia de Razor Pages Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@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>

Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va seguido de una palabra clave reservada de Razor, se realiza una transición a un marcado específico de Razor; en caso contrario, la transición se realiza a C#.

La directiva @page

La directiva @page de Razor convierte el archivo en una acción de MVC, lo que significa que puede administrar las solicitudes. @page debe ser la primera directiva de Razor de una página. @page y @model son ejemplos de la transición a un marcado específico de Razor. Vea Sintaxis de Razor para obtener más información.

La directiva @model

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

La directiva @model especifica el tipo del modelo que se pasa a una instancia de Razor Pages. En el ejemplo anterior, la línea @model permite que la clase derivada de PageModel esté disponible en la instancia de Razor Pages. El modelo se usa en los asistentes de HTML@Html.DisplayNameFor y @Html.DisplayFor de la página.

Examine la expresión lambda usada en el siguiente asistente de HTML:

@Html.DisplayNameFor(model => model.Movie[0].Title)

El asistente de HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto significa que no hay ninguna infracción de acceso si model, model.Movie o model.Movie[0] son null o están vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title)), se evalúan los valores de propiedad del modelo.

Página de diseño

Seleccione los vínculos de menú RazorPagesMovie, Home y Privacy. Cada página muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Pages/Shared/_Layout.cshtml.

Abra y examine el archivo Pages/Shared/_Layout.cshtml.

Las plantillas de diseño permiten que el diseño del contenedor HTML:

  • Esté especificado en un solo lugr.
  • Se aplique en varias páginas del sitio.

Busque la línea @RenderBody(). RenderBody es un marcador de posición donde se muestran todas las vistas específicas de página, encapsuladas en la página de diseño. Por ejemplo, seleccione el vínculo Privacy y la vista Pages/Privacy.cshtml se representa dentro del método RenderBody.

Propiedades ViewData y Layout

Tenga en cuenta el siguiente marcado del archivo Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

El marcado resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un bloque de código de C#.

La clase base PageModel contiene una propiedad de diccionario ViewData que se puede usar para pasar datos a una vista. Los objetos se agregan al diccionario ViewData con un patrón clave-valor. En el ejemplo anterior, la propiedad Title se agrega al diccionario ViewData.

La propiedad Title se usa en el archivo Pages/Shared/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo _Layout.cshtml.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorPagesMovie</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-version="true" />

La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML <!-- -->, los comentarios de Razor no se envían al cliente. Consulte Documentación web de MDN: introducción a HTML para obtener más información.

Actualizar el diseño

  1. Cambie el elemento <title> del archivo Pages/Shared/_Layout.cshtml para mostrar Movie en lugar de RazorPagesMovie.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>@ViewData["Title"] - Movie</title>
    
  2. Busque el siguiente elemento delimitador en el archivo Pages/Shared/_Layout.cshtml.

    <a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
    
  3. Reemplace el elemento anterior por el marcado siguiente:

    <a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
    

    El elemento delimitador anterior es un asistente de etiquetas. En este caso, se trata de el asistente de etiquetas Anchor. El atributo y valor del asistente de etiquetas asp-page="/Movies/Index" crea un vínculo a /Movies/Index en Razor Pages. El valor de atributo asp-area está vacío, por lo que no se usa el área del vínculo. Consulte Áreas para obtener más información.

  4. Guarde los cambios y pruebe la aplicación seleccionando el vínculo RpMovie (Película de RP). Si tiene cualquier problema, consulte el archivo _Layout.cshtml en GitHub.

  5. Pruebe los vínculos Home, RpMovie, Create, Edit y Delete. Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título para el marcador.

Nota

Es posible que no pueda escribir comas decimales en el campo Price. Para que la validación de jQuery sea compatible con configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un punto decimal y formatos de fecha distintos del de Estados Unidos, debe seguir unos pasos para globalizar la aplicación. Consulte este problema 4076 de GitHub para instrucciones sobre cómo agregar la coma decimal.

La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

El marcado anterior establece el archivo de diseño de todos los archivos de Razor de la carpeta Pages en Pages/Shared/_Layout.cshtml. Vea Layout (Diseño) para más información.

Modelo de página Crear

Examine el modelo de página Pages/Movies/Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

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

        public IActionResult OnGet()
        {
            return Page();
        }

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

        // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
        public async Task<IActionResult> OnPostAsync()
        {
          if (!ModelState.IsValid || _context.Movie == null || Movie == null)
            {
                return Page();
            }

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado que inicializar, de modo que se devuelve Page. Más adelante en el tutorial se muestra un ejemplo del estado de inicialización de OnGet. El método Page crea un objeto PageResult que representa la página Create.cshtml.

La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los valores publicados con el modelo Movie.

El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:

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

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en una fecha. Más adelante en el tutorial, hablaremos de la validación del lado cliente y de la validación de modelos.

Si no hay ningún error de modelo:

  • Los datos se guardan.
  • El explorador se redirige a la página Index.

La página de creación de instancias de Razor Pages

Examine el archivo de instancia de Razor Pages Pages/Movies/Create.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>
                <input asp-for="Movie.Title" class="form-control" />
                <span asp-validation-for="Movie.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.ReleaseDate" class="control-label"></label>
                <input asp-for="Movie.ReleaseDate" class="form-control" />
                <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Genre" class="control-label"></label>
                <input asp-for="Movie.Genre" class="form-control" />
                <span asp-validation-for="Movie.Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Price" class="control-label"></label>
                <input asp-for="Movie.Price" class="form-control" />
                <span asp-validation-for="Movie.Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio muestra las etiquetas siguientes con una fuente negrita diferenciada que se aplica a los asistentes de etiquetas:

  • <form method="post">
  • <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  • <label asp-for="Movie.Title" class="control-label"></label>
  • <input asp-for="Movie.Title" class="form-control" />
  • <span asp-validation-for="Movie.Title" class="text-danger"></span>

Vista de VS17 de la página Create.cshtml

El elemento <form method="post"> es un asistente de etiquetas de formulario. El asistente de etiquetas de formulario incluye automáticamente un token antifalsificación.

El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar al siguiente:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Los asistentes de etiquetas de validación (<div asp-validation-summary y <span asp-validation-for) muestran errores de validación. La validación se trata con más detalle en un punto posterior de esta serie.

El asistente de etiquetas (<label asp-for="Movie.Title" class="control-label"></label>) genera el título de la etiqueta y el atributo [for] para la propiedad Title.

El asistente de etiquetas de entrada (<input asp-for="Movie.Title" class="form-control">) usa los atributos DataAnnotations y genera los atributos HTML necesarios para la validación de jQuery en el lado del cliente.

Para obtener más información sobre los asistentes de etiquetas como <form method="post">, consulte Asistentes de etiquetas en ASP.NET Core.

Pasos siguientes

Páginas de creación, eliminación, detalles y edición

Examine el modelo de Pages/Movies/Index.cshtml.cs Pages:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

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

        public IList<Movie> Movie { get;set; } = default!;

        public async Task OnGetAsync()
        {
            if (_context.Movie != null)
            {
                Movie = await _context.Movie.ToListAsync();
            }
        }
    }
}

Las instancias de Razor Pages derivan de PageModel. Por convención, la clase derivada de PageModel se denomina PageNameModel. Por ejemplo, la página Index se denomina IndexModel.

El constructor aplica la inserción de dependencias para agregar RazorPagesMovieContext a la página:

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

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

Vea Código asincrónico para obtener más información sobre programación asincrónica con Entity Framework.

Cuando se efectúa una solicitud de la página, el método OnGetAsync devuelve una lista de películas a la instancia de Razor Pages. En la instancia de Razor Pages, se llama a OnGetAsync o OnGet para inicializar el estado de la página. En este caso, OnGetAsync obtiene una lista de películas y las muestra.

Cuando OnGet devuelve void o OnGetAsync devuelve Task, no se utiliza ninguna instrucción de devolución. Por ejemplo, examine la página Privacy:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesMovie.Pages
{
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Cuando el tipo de valor devuelto es IActionResult o Task<IActionResult>, se debe proporcionar una instrucción return. Por ejemplo, la llamada al método OnPostAsync de Pages/Movies/Create.cshtml.cs:

public async Task<IActionResult> OnPostAsync()
{
  if (!ModelState.IsValid || _context.Movie == null || Movie == null)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Examine la instancia de Razor Pages Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@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>

Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va seguido de una palabra clave reservada de Razor, se realiza una transición a un marcado específico de Razor; en caso contrario, la transición se realiza a C#.

La directiva @page

La directiva @page de Razor convierte el archivo en una acción de MVC, lo que significa que puede administrar las solicitudes. @page debe ser la primera directiva de Razor de una página. @page y @model son ejemplos de la transición a un marcado específico de Razor. Vea Sintaxis de Razor para obtener más información.

La directiva @model

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

La directiva @model especifica el tipo del modelo que se pasa a una instancia de Razor Pages. En el ejemplo anterior, la línea @model permite que la clase derivada de PageModel esté disponible en la instancia de Razor Pages. El modelo se usa en los asistentes de HTML@Html.DisplayNameFor y @Html.DisplayFor de la página.

Examine la expresión lambda usada en el siguiente asistente de HTML:

@Html.DisplayNameFor(model => model.Movie[0].Title)

El asistente de HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto significa que no hay ninguna infracción de acceso si model, model.Movie o model.Movie[0] son null o están vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title)), se evalúan los valores de propiedad del modelo.

Página de diseño

Seleccione los vínculos de menú RazorPagesMovie, Home y Privacy. Cada página muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Pages/Shared/_Layout.cshtml.

Abra y examine el archivo Pages/Shared/_Layout.cshtml.

Las plantillas de diseño permiten que el diseño del contenedor HTML:

  • Esté especificado en un solo lugr.
  • Se aplique en varias páginas del sitio.

Busque la línea @RenderBody(). RenderBody es un marcador de posición donde se muestran todas las vistas específicas de página, encapsuladas en la página de diseño. Por ejemplo, seleccione el vínculo Privacy y la vista Pages/Privacy.cshtml se representa dentro del método RenderBody.

Propiedades ViewData y Layout

Tenga en cuenta el siguiente marcado del archivo Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

El marcado resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un bloque de código de C#.

La clase base PageModel contiene una propiedad de diccionario ViewData que se puede usar para pasar datos a una vista. Los objetos se agregan al diccionario ViewData con un patrón clave-valor. En el ejemplo anterior, la propiedad Title se agrega al diccionario ViewData.

La propiedad Title se usa en el archivo Pages/Shared/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo _Layout.cshtml.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorPagesMovie</title>

     @*Markup removed for brevity.*@
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />

La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML <!-- -->, los comentarios de Razor no se envían al cliente. Consulte Documentación web de MDN: introducción a HTML para obtener más información.

Actualizar el diseño

  1. Cambie el elemento <title> del archivo Pages/Shared/_Layout.cshtml para mostrar Movie en lugar de RazorPagesMovie.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>@ViewData["Title"] - Movie</title>
    
  2. Busque el siguiente elemento delimitador en el archivo Pages/Shared/_Layout.cshtml.

    <a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
    
  3. Reemplace el elemento anterior por el marcado siguiente:

    <a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
    

    El elemento delimitador anterior es un asistente de etiquetas. En este caso, se trata de el asistente de etiquetas Anchor. El atributo y valor del asistente de etiquetas asp-page="/Movies/Index" crea un vínculo a /Movies/Index en Razor Pages. El valor de atributo asp-area está vacío, por lo que no se usa el área del vínculo. Consulte Áreas para obtener más información.

  4. Guarde los cambios y pruebe la aplicación seleccionando el vínculo RpMovie (Película de RP). Si tiene cualquier problema, consulte el archivo _Layout.cshtml en GitHub.

  5. Pruebe los vínculos Home, RpMovie, Create, Edit y Delete. Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título para el marcador.

Nota

Es posible que no pueda escribir comas decimales en el campo Price. Para que la validación de jQuery sea compatible con configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un punto decimal y formatos de fecha distintos del de Estados Unidos, debe seguir unos pasos para globalizar la aplicación. Consulte este problema 4076 de GitHub para instrucciones sobre cómo agregar la coma decimal.

La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

El marcado anterior establece el archivo de diseño de todos los archivos de Razor de la carpeta Pages en Pages/Shared/_Layout.cshtml. Vea Layout (Diseño) para más información.

Modelo de página Crear

Examine el modelo de página Pages/Movies/Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

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

        public IActionResult OnGet()
        {
            return Page();
        }

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

        // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
        public async Task<IActionResult> OnPostAsync()
        {
          if (!ModelState.IsValid || _context.Movie == null || Movie == null)
            {
                return Page();
            }

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado que inicializar, de modo que se devuelve Page. Más adelante en el tutorial se muestra un ejemplo del estado de inicialización de OnGet. El método Page crea un objeto PageResult que representa la página Create.cshtml.

La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los valores publicados con el modelo Movie.

El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:

public async Task<IActionResult> OnPostAsync()
{
  if (!ModelState.IsValid || _context.Movie == null || Movie == null)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en una fecha. Más adelante en el tutorial, hablaremos de la validación del lado cliente y de la validación de modelos.

Si no hay ningún error de modelo:

  • Los datos se guardan.
  • El explorador se redirige a la página Index.

La página de creación de instancias de Razor Pages

Examine el archivo de instancia de Razor Pages Pages/Movies/Create.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>
                <input asp-for="Movie.Title" class="form-control" />
                <span asp-validation-for="Movie.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.ReleaseDate" class="control-label"></label>
                <input asp-for="Movie.ReleaseDate" class="form-control" />
                <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Genre" class="control-label"></label>
                <input asp-for="Movie.Genre" class="form-control" />
                <span asp-validation-for="Movie.Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Price" class="control-label"></label>
                <input asp-for="Movie.Price" class="form-control" />
                <span asp-validation-for="Movie.Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio muestra las etiquetas siguientes con una fuente negrita diferenciada que se aplica a los asistentes de etiquetas:

  • <form method="post">
  • <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  • <label asp-for="Movie.Title" class="control-label"></label>
  • <input asp-for="Movie.Title" class="form-control" />
  • <span asp-validation-for="Movie.Title" class="text-danger"></span>

Vista de VS17 de la página Create.cshtml

El elemento <form method="post"> es un asistente de etiquetas de formulario. El asistente de etiquetas de formulario incluye automáticamente un token antifalsificación.

El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar al siguiente:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Los asistentes de etiquetas de validación (<div asp-validation-summary y <span asp-validation-for) muestran errores de validación. La validación se trata con más detalle en un punto posterior de esta serie.

El asistente de etiquetas (<label asp-for="Movie.Title" class="control-label"></label>) genera el título de la etiqueta y el atributo [for] para la propiedad Title.

El asistente de etiquetas de entrada (<input asp-for="Movie.Title" class="form-control">) usa los atributos DataAnnotations y genera los atributos HTML necesarios para la validación de jQuery en el lado del cliente.

Para obtener más información sobre los asistentes de etiquetas como <form method="post">, consulte Asistentes de etiquetas en ASP.NET Core.

Pasos siguientes

Páginas de creación, eliminación, detalles y edición

Examine el modelo de Pages/Movies/Index.cshtml.cs Pages:

// Unused usings removed.
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

        public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
        {
            _context = context;
        }
        public IList<Movie> Movie { get;set; }

        public async Task OnGetAsync()
        {
            Movie = await _context.Movie.ToListAsync();
        }
    }
}

Las instancias de Razor Pages derivan de PageModel. Por convención, la clase derivada de PageModel se denomina <PageName>Model. El constructor aplica la inserción de dependencias para agregar RazorPagesMovieContext a la página:

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

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

Vea Código asincrónico para obtener más información sobre programación asincrónica con Entity Framework.

Cuando se efectúa una solicitud de la página, el método OnGetAsync devuelve una lista de películas a la instancia de Razor Pages. En la instancia de Razor Pages, se llama a OnGetAsync o OnGet para inicializar el estado de la página. En este caso, OnGetAsync obtiene una lista de películas y las muestra.

Cuando OnGet devuelve void o OnGetAsync devuelve Task, no se utiliza ninguna instrucción de devolución. Por ejemplo, la página Privacy:

public class PrivacyModel : PageModel
{
    private readonly ILogger<PrivacyModel> _logger;

    public PrivacyModel(ILogger<PrivacyModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
    }
}

Cuando el tipo de valor devuelto es IActionResult o Task<IActionResult>, se debe proporcionar una instrucción return. Por ejemplo, la llamada al método OnPostAsync de Pages/Movies/Create.cshtml.cs:

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

        _context.Movie.Add(Movie);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Examine la instancia de Razor Pages Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@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>

Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va seguido de una palabra clave reservada de Razor, se realiza una transición a un marcado específico de Razor; en caso contrario, la transición se realiza a C#.

La directiva @page

La directiva @page de Razor convierte el archivo en una acción de MVC, lo que significa que puede administrar las solicitudes. @page debe ser la primera directiva de Razor de una página. @page y @model son ejemplos de la transición a un marcado específico de Razor. Vea Sintaxis de Razor para obtener más información.

La directiva @model

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

La directiva @model especifica el tipo del modelo que se pasa a una instancia de Razor Pages. En el ejemplo anterior, la línea @model permite que la clase derivada de PageModel esté disponible en la instancia de Razor Pages. El modelo se usa en los asistentes de HTML@Html.DisplayNameFor y @Html.DisplayFor de la página.

Examine la expresión lambda usada en el siguiente asistente de HTML:

@Html.DisplayNameFor(model => model.Movie[0].Title)

El asistente de HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto significa que no hay ninguna infracción de acceso si model, model.Movie o model.Movie[0] son null o están vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title)), se evalúan los valores de propiedad del modelo.

Página de diseño

Seleccione los vínculos de menú RazorPagesMovie, Home y Privacy. Cada página muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Pages/Shared/_Layout.cshtml.

Abra y examine el archivo Pages/Shared/_Layout.cshtml.

Las plantillas de diseño permiten que el diseño del contenedor HTML:

  • Esté especificado en un solo lugr.
  • Se aplique en varias páginas del sitio.

Busque la línea @RenderBody(). RenderBody es un marcador de posición donde se muestran todas las vistas específicas de página, encapsuladas en la página de diseño. Por ejemplo, seleccione el vínculo Privacy y la vista Pages/Privacy.cshtml se representa dentro del método RenderBody.

Propiedades ViewData y Layout

Tenga en cuenta el siguiente marcado del archivo Pages/Movies/Index.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

El marcado resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un bloque de código de C#.

La clase base PageModel contiene una propiedad de diccionario ViewData que se puede usar para pasar datos a una vista. Los objetos se agregan al diccionario ViewData con un patrón clave-valor. En el ejemplo anterior, la propiedad Title se agrega al diccionario ViewData.

La propiedad Title se usa en el archivo Pages/Shared/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo _Layout.cshtml.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorPagesMovie</title>

    @*Markup removed for brevity.*@

La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML <!-- -->, los comentarios de Razor no se envían al cliente. Consulte Documentación web de MDN: introducción a HTML para obtener más información.

Actualizar el diseño

  1. Cambie el elemento <title> del archivo Pages/Shared/_Layout.cshtml para mostrar Movie en lugar de RazorPagesMovie.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>@ViewData["Title"] - Movie</title>
    
  2. Busque el siguiente elemento delimitador en el archivo Pages/Shared/_Layout.cshtml.

    <a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
    
  3. Reemplace el elemento anterior por el marcado siguiente:

    <a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
    

    El elemento delimitador anterior es un asistente de etiquetas. En este caso, se trata de el asistente de etiquetas Anchor. El atributo y valor del asistente de etiquetas asp-page="/Movies/Index" crea un vínculo a /Movies/Index en Razor Pages. El valor de atributo asp-area está vacío, por lo que no se usa el área del vínculo. Consulte Áreas para obtener más información.

  4. Guarde los cambios y pruebe la aplicación seleccionando el vínculo RpMovie (Película de RP). Si tiene cualquier problema, consulte el archivo _Layout.cshtml en GitHub.

  5. Pruebe los vínculos Home, RpMovie, Create, Edit y Delete. Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título para el marcador.

Nota

Es posible que no pueda escribir comas decimales en el campo Price. Para que la validación de jQuery sea compatible con configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un punto decimal y formatos de fecha distintos del de Estados Unidos, debe seguir unos pasos para globalizar la aplicación. Consulte este problema 4076 de GitHub para instrucciones sobre cómo agregar la coma decimal.

La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:

@{
    Layout = "_Layout";
}

El marcado anterior establece el archivo de diseño de todos los archivos de Razor de la carpeta Pages en Pages/Shared/_Layout.cshtml. Vea Layout (Diseño) para más información.

Modelo de página Crear

Examine el modelo de página Pages/Movies/Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

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

        public IActionResult OnGet()
        {
            return Page();
        }

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

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

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado que inicializar, de modo que se devuelve Page. Más adelante en el tutorial se muestra un ejemplo del estado de inicialización de OnGet. El método Page crea un objeto PageResult que representa la página Create.cshtml.

La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los valores publicados con el modelo Movie.

El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:

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

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en una fecha. Más adelante en el tutorial, hablaremos de la validación del lado cliente y de la validación de modelos.

Si no hay ningún error de modelo:

  • Los datos se guardan.
  • El explorador se redirige a la página Index.

La página de creación de instancias de Razor Pages

Examine el archivo de instancia de Razor Pages Pages/Movies/Create.cshtml:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>
                <input asp-for="Movie.Title" class="form-control" />
                <span asp-validation-for="Movie.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.ReleaseDate" class="control-label"></label>
                <input asp-for="Movie.ReleaseDate" class="form-control" />
                <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Genre" class="control-label"></label>
                <input asp-for="Movie.Genre" class="form-control" />
                <span asp-validation-for="Movie.Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Price" class="control-label"></label>
                <input asp-for="Movie.Price" class="form-control" />
                <span asp-validation-for="Movie.Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio muestra las etiquetas siguientes con una fuente negrita diferenciada que se aplica a los asistentes de etiquetas:

  • <form method="post">
  • <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  • <label asp-for="Movie.Title" class="control-label"></label>
  • <input asp-for="Movie.Title" class="form-control" />
  • <span asp-validation-for="Movie.Title" class="text-danger"></span>

Vista de VS17 de la página Create.cshtml

El elemento <form method="post"> es un asistente de etiquetas de formulario. El asistente de etiquetas de formulario incluye automáticamente un token antifalsificación.

El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar al siguiente:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Los asistentes de etiquetas de validación (<div asp-validation-summary y <span asp-validation-for) muestran errores de validación. La validación se trata con más detalle en un punto posterior de esta serie.

El asistente de etiquetas (<label asp-for="Movie.Title" class="control-label"></label>) genera el título de la etiqueta y el atributo [for] para la propiedad Title.

El asistente de etiquetas de entrada (<input asp-for="Movie.Title" class="form-control">) usa los atributos DataAnnotations y genera los atributos HTML necesarios para la validación de jQuery en el lado del cliente.

Para obtener más información sobre los asistentes de etiquetas como <form method="post">, consulte Asistentes de etiquetas en ASP.NET Core.

Pasos siguientes