Parte 5, atualizar as páginas geradas em um aplicativo ASP.NET CorePart 5, update the generated pages in an ASP.NET Core app

De Rick AndersonBy Rick Anderson

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal.The scaffolded movie app has a good start, but the presentation isn't ideal. Liberado deve ser de duas palavras, data de lançamento.ReleaseDate should be two words, Release Date.

Aplicativo de filme aberto no Chrome

Atualizar o código geradoUpdate the generated code

Abra o arquivo Models/Movie.cs e adicione as linhas realçadas mostradas no seguinte código:Open the Models/Movie.cs file and add the highlighted lines shown in the following code:

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

No código anterior:In the previous code:

  • A anotação de dados [Column(TypeName = "decimal(18, 2)")] permite que o Entity Framework Core mapeie o Price corretamente para a moeda no banco de dados.The [Column(TypeName = "decimal(18, 2)")] data annotation enables Entity Framework Core to correctly map Price to currency in the database. Para obter mais informações, veja Tipos de Dados.For more information, see Data Types.
  • O atributo [DISPLAY] especifica o nome de exibição de um campo.The [Display] attribute specifies the display name of a field. No código anterior, "data de lançamento" em vez de "liberado".In the preceding code, "Release Date" instead of "ReleaseDate".
  • O atributo [DataType] especifica o tipo dos dados ( Date ).The [DataType] attribute specifies the type of the data (Date). As informações de hora armazenadas no campo não são exibidas.The time information stored in the field isn't displayed.

DataAnnotations é abordado no próximo tutorial.DataAnnotations is covered in the next tutorial.

Navegue até páginas/filmes e passe o mouse sobre um link de edição para ver a URL de destino.Browse to Pages/Movies and hover over an Edit link to see the target URL.

São mostrados uma janela do navegador com o mouse sobre o link de edição e um link da URL https://localhost:1234/Movies/Edit/5

Os links Editar, detalhes e excluir são gerados pelo auxiliar de marca de âncora no arquivo pages/Movies/ Index . cshtml .The Edit, Details, and Delete links are generated by the Anchor Tag Helper in the Pages/Movies/Index.cshtml file.

@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>

Os auxiliares de marca permitem que o código do servidor participe da criação e renderização de elementos HTML em Razor arquivos.Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files.

No código anterior, o auxiliar de marca de âncora gera dinamicamente o href valor do atributo HTML da Razor página (a rota é relativa), o asp-page e o identificador de rota ( asp-route-id ).In the preceding code, the Anchor Tag Helper dynamically generates the HTML href attribute value from the Razor Page (the route is relative), the asp-page, and the route identifier (asp-route-id). Para obter mais informações, consulte geração de URL para páginas.For more information, see URL generation for Pages.

Use o modo de exibição de origem de um navegador para examinar a marcação gerada.Use View Source from a browser to examine the generated markup. Uma parte do HTML gerado é mostrada abaixo:A portion of the generated HTML is shown below:

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

Os links gerados dinamicamente passam a ID do filme com uma cadeia de caracteres de consulta.The dynamically generated links pass the movie ID with a query string. Por exemplo, o ?id=1 no https://localhost:5001/Movies/Details?id=1 .For example, the ?id=1 in https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rotaAdd route template

Atualize as páginas editar, detalhes e excluir Razor para usar o {id:int} modelo de rota.Update the Edit, Details, and Delete Razor Pages to use the {id:int} route template. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}".Change the page directive for each of these pages from @page to @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.Run the app and then view source.

O HTML gerado adiciona a ID à parte do caminho da URL:The generated HTML adds the ID to the path portion of the URL:

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

Uma solicitação para a página com o {id:int} modelo de rota que não inclui o inteiro retornará um erro http 404 (não encontrado).A request to the page with the {id:int} route template that does not include the integer will return an HTTP 404 (not found) error. Por exemplo, https://localhost:5001/Movies/Details retornará um erro 404.For example, https://localhost:5001/Movies/Details will return a 404 error. Para tornar a ID opcional, acrescente ? à restrição de rota:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Testar o comportamento de @page "{id:int?}" :Test the behavior of @page "{id:int?}":

  1. defina a diretiva de página em Pages/Movies/Details.cshtml como @page "{id:int?}".Set the page directive in Pages/Movies/Details.cshtml to @page "{id:int?}".
  2. Defina um ponto de interrupção no public async Task<IActionResult> OnGetAsync(int? id) , em pages/Movies/details. cshtml. cs.Set a break point in public async Task<IActionResult> OnGetAsync(int? id), in Pages/Movies/Details.cshtml.cs.
  3. Navegue até https://localhost:5001/Movies/Details/.Navigate to https://localhost:5001/Movies/Details/.

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido.With the @page "{id:int}" directive, the break point is never hit. O mecanismo de roteamento retorna HTTP 404.The routing engine returns HTTP 404. Usando @page "{id:int?}" , o OnGetAsync método retorna NotFound (http 404):Using @page "{id:int?}", the OnGetAsync method returns 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();
}

Examinar o tratamento de exceção de simultaneidadeReview concurrency exception handling

Examine o método OnPostAsync no arquivo Pages/Movies/Edit.cshtml.cs:Review the OnPostAsync method in the Pages/Movies/Edit.cshtml.cs file:

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

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.The previous code detects concurrency exceptions when one client deletes the movie and the other client posts changes to the movie.

Para testar o bloco catch:To test the catch block:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException) .Set a breakpoint on catch (DbUpdateConcurrencyException).
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.Select Edit for a movie, make changes, but don't enter Save.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.In another browser window, select the Delete link for the same movie, and then delete the movie.
  4. Na janela do navegador anterior, poste as alterações no filme.In the previous browser window, post changes to the movie.

O código de produção talvez deseje detectar conflitos de simultaneidade.Production code may want to detect concurrency conflicts. Confira Lidar com conflitos de simultaneidade para obter mais informações.See Handle concurrency conflicts for more information.

Análise de postagem e associaçãoPosting and binding review

Examine o arquivo Pages/Movies/Edit.cshtml.cs: Examine the Pages/Movies/Edit.cshtml.cs file:

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

Quando uma solicitação HTTP GET é feita na página filmes/editar, por exemplo https://localhost:5001/Movies/Edit/3 :When an HTTP GET request is made to the Movies/Edit page, for example, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.The OnGetAsync method fetches the movie from the database and returns the Page method.
  • O Page método renderiza a página pages/Movies/Edit. cshtml Razor .The Page method renders the Pages/Movies/Edit.cshtml Razor Page. O arquivo pages/Movies/Edit. cshtml contém a diretiva Model @model RazorPagesMovie.Pages.Movies.EditModel , que torna o modelo de filme disponível na página.The Pages/Movies/Edit.cshtml file contains the model directive @model RazorPagesMovie.Pages.Movies.EditModel, which makes the movie model available on the page.
  • O formulário Editar é exibido com os valores do filme.The Edit form is displayed with the values from the movie.

Quando a página Movies/Edit é postada:When the Movies/Edit page is posted:

  • Os valores de formulário na página são associados à propriedade Movie.The form values on the page are bound to the Movie property. O atributo [BindProperty] habilita o Model binding.The [BindProperty] attribute enables Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo, por exemplo, ReleaseDate não puder ser convertido em uma data, o formulário será exibido novamente com os valores enviados.If there are errors in the model state, for example, ReleaseDate cannot be converted to a date, the form is redisplayed with the submitted values.

  • Se não houver erros do modelo, o filme será salvo.If there are no model errors, the movie is saved.

Os métodos GET HTTP nas Index páginas, criar e excluir Razor seguem um padrão semelhante.The HTTP GET methods in the Index, Create, and Delete Razor pages follow a similar pattern. O método HTTP POST OnPostAsync na página Criar Razor segue um padrão semelhante ao OnPostAsync método na Razor página Editar.The HTTP POST OnPostAsync method in the Create Razor Page follows a similar pattern to the OnPostAsync method in the Edit Razor Page.

Recursos adicionaisAdditional resources

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal.The scaffolded movie app has a good start, but the presentation isn't ideal. Liberado deve ser de duas palavras, data de lançamento.ReleaseDate should be two words, Release Date.

Aplicativo de filme aberto no Chrome

Atualizar o código geradoUpdate the generated code

Abra o arquivo Models/Movie.cs e adicione as linhas realçadas mostradas no seguinte código:Open the Models/Movie.cs file and add the highlighted lines shown in the following code:

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

A anotação de dados [Column(TypeName = "decimal(18, 2)")] permite que o Entity Framework Core mapeie o Price corretamente para a moeda no banco de dados.The [Column(TypeName = "decimal(18, 2)")] data annotation enables Entity Framework Core to correctly map Price to currency in the database. Para obter mais informações, veja Tipos de Dados.For more information, see Data Types.

DataAnnotations é abordado no próximo tutorial.DataAnnotations is covered in the next tutorial. O atributo [DISPLAY] especifica o que exibir para o nome de um campo, neste caso, "data de lançamento" em vez de "liberado".The [Display] attribute specifies what to display for the name of a field, in this case "Release Date" instead of "ReleaseDate". O atributo DataType especifica o tipo dos dados ( Date ), portanto, as informações de hora armazenadas no campo não são exibidas.The DataType attribute specifies the type of the data (Date), so the time information stored in the field isn't displayed.

Procure Pages/Movies e focalize um link Editar para ver a URL de destino.Browse to Pages/Movies and hover over an Edit link to see the target URL.

São mostrados uma janela do navegador com o mouse sobre o link de edição e um link da URL http://localhost:1234/Movies/Edit/5

Os links Editar, detalhes e excluir são gerados pelo auxiliar de marca de âncora no arquivo pages/Movies/ Index . cshtml .The Edit, Details, and Delete links are generated by the Anchor Tag Helper in the Pages/Movies/Index.cshtml file.

@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>

Os auxiliares de marca permitem que o código do servidor participe da criação e renderização de elementos HTML em Razor arquivos.Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files. No código anterior, o AnchorTagHelper gera dinamicamente o valor do href atributo HTML da Razor página (a rota é relativa), a asp-page e a ID da rota ( asp-route-id ).In the preceding code, the AnchorTagHelper dynamically generates the HTML href attribute value from the Razor Page (the route is relative), the asp-page, and the route id (asp-route-id). Consulte Geração de URL para Páginas para obter mais informações.See URL generation for Pages for more information.

Use o modo de exibição de origem de um navegador para examinar a marcação gerada.Use View Source from a browser to examine the generated markup. Uma parte do HTML gerado é mostrada abaixo:A portion of the generated HTML is shown below:

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

Os links gerados dinamicamente passam a ID do filme com uma cadeia de caracteres de consulta.The dynamically generated links pass the movie ID with a query string. Por exemplo, o ?id=1 no https://localhost:5001/Movies/Details?id=1 .For example, the ?id=1 in https://localhost:5001/Movies/Details?id=1.

Atualize as páginas editar, detalhes e excluir Razor para usar o modelo de rota "{ID: int}".Update the Edit, Details, and Delete Razor Pages to use the "{id:int}" route template. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}".Change the page directive for each of these pages from @page to @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.Run the app and then view source. O HTML gerado adiciona a ID à parte do caminho da URL:The generated HTML adds the ID to the path portion of the URL:

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

Uma solicitação para a página com o modelo de rota “{id:int}” que não inclui o inteiro retornará um erro HTTP 404 (não encontrado).A request to the page with the "{id:int}" route template that does not include the integer will return an HTTP 404 (not found) error. Por exemplo, https://localhost:5001/Movies/Details retornará um erro 404.For example, https://localhost:5001/Movies/Details will return a 404 error. Para tornar a ID opcional, acrescente ? à restrição de rota:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Para testar o comportamento de @page "{id:int?}":To test the behavior of @page "{id:int?}":

  • defina a diretiva de página em Pages/Movies/Details.cshtml como @page "{id:int?}".Set the page directive in Pages/Movies/Details.cshtml to @page "{id:int?}".
  • Defina um ponto de interrupção no public async Task<IActionResult> OnGetAsync(int? id) , em pages/Movies/details. cshtml. cs.Set a break point in public async Task<IActionResult> OnGetAsync(int? id), in Pages/Movies/Details.cshtml.cs.
  • Navegue até https://localhost:5001/Movies/Details/.Navigate to https://localhost:5001/Movies/Details/.

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido.With the @page "{id:int}" directive, the break point is never hit. O mecanismo de roteamento retorna HTTP 404.The routing engine returns HTTP 404. Usando @page "{id:int?}" , o OnGetAsync método retorna NotFound (http 404):Using @page "{id:int?}", the OnGetAsync method returns 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();
}

Examinar o tratamento de exceção de simultaneidadeReview concurrency exception handling

Examine o método OnPostAsync no arquivo Pages/Movies/Edit.cshtml.cs:Review the OnPostAsync method in the Pages/Movies/Edit.cshtml.cs file:


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

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.The previous code detects concurrency exceptions when the one client deletes the movie and the other client posts changes to the movie.

Para testar o bloco catch:To test the catch block:

  • Definir um ponto de interrupção em catch (DbUpdateConcurrencyException)Set a breakpoint on catch (DbUpdateConcurrencyException)
  • Selecione Editar para um filme, faça alterações, mas não insira Salvar.Select Edit for a movie, make changes, but don't enter Save.
  • Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.In another browser window, select the Delete link for the same movie, and then delete the movie.
  • Na janela do navegador anterior, poste as alterações no filme.In the previous browser window, post changes to the movie.

O código de produção talvez deseje detectar conflitos de simultaneidade.Production code may want to detect concurrency conflicts. Confira Lidar com conflitos de simultaneidade para obter mais informações.See Handle concurrency conflicts for more information.

Análise de postagem e associaçãoPosting and binding review

Examine o arquivo Pages/Movies/Edit.cshtml.cs: Examine the Pages/Movies/Edit.cshtml.cs file:

public class EditModel : PageModel
{
    private readonly RazorPagesMovieContext _context;

    public EditModel(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.SingleOrDefaultAsync(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 (!_context.Movie.Any(e => e.ID == Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

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

Quando uma solicitação HTTP GET é feita na página filmes/editar, por exemplo https://localhost:5001/Movies/Edit/3 :When an HTTP GET request is made to the Movies/Edit page, for example, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.The OnGetAsync method fetches the movie from the database and returns the Page method.
  • O Page método renderiza a página pages/Movies/Edit. cshtml Razor .The Page method renders the Pages/Movies/Edit.cshtml Razor Page. O arquivo pages/Movies/Edit. cshtml contém a diretiva Model @model RazorPagesMovie.Pages.Movies.EditModel , que torna o modelo de filme disponível na página.The Pages/Movies/Edit.cshtml file contains the model directive @model RazorPagesMovie.Pages.Movies.EditModel, which makes the movie model available on the page.
  • O formulário Editar é exibido com os valores do filme.The Edit form is displayed with the values from the movie.

Quando a página Movies/Edit é postada:When the Movies/Edit page is posted:

  • Os valores de formulário na página são associados à propriedade Movie.The form values on the page are bound to the Movie property. O atributo [BindProperty] habilita o Model binding.The [BindProperty] attribute enables Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo, por exemplo, ReleaseDate não puder ser convertido em uma data, o formulário será exibido com os valores enviados.If there are errors in the model state, for example, ReleaseDate cannot be converted to a date, the form is displayed with the submitted values.

  • Se não houver erros do modelo, o filme será salvo.If there are no model errors, the movie is saved.

Os métodos GET HTTP nas Index páginas, criar e excluir Razor seguem um padrão semelhante.The HTTP GET methods in the Index, Create, and Delete Razor pages follow a similar pattern. O método HTTP POST OnPostAsync na página Criar Razor segue um padrão semelhante ao OnPostAsync método na Razor página Editar.The HTTP POST OnPostAsync method in the Create Razor Page follows a similar pattern to the OnPostAsync method in the Edit Razor Page.

A pesquisa é adicionada no próximo tutorial.Search is added in the next tutorial.

Recursos adicionaisAdditional resources