Parte 5, atualizar as páginas geradas em um aplicativo ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Aviso

Não há mais suporte para essa versão do ASP.NET Core. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Aplicativo de filme aberto no Chrome

Atualizar o modelo

Atualize Models/Movie.cs com o seguinte código realçado:

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

No código anterior:

  • 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. Para obter mais informações, veja Tipos de Dados.
  • O atributo [Display] especifica o nome de exibição de um campo. No código anterior, Release Date em vez de ReleaseDate.
  • O atributo [DataType] especifica o tipo de dados (Date). As informações de hora armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de destino.

Uma janela do navegador com o mouse sobre o link Editar e um link da URL de https://localhost:1234/Movies/Edit/5 são exibidos

Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo 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>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos HTML em arquivos do Razor.

No código anterior, o Auxiliar de Marcação de Âncora gera dinamicamente o valor do atributo hrefdo HTML da Página do Razor (a rota é relativa), o asp-page e o identificador de rota (asp-route-id). Para obter mais informações, confira Geração de URL para Páginas.

Use Exibir Fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML gerado é mostrada abaixo:

<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 de filme com uma cadeia de caracteres de consulta. Por exemplo, o trecho ?id=1 em https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rota

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota {id:int}. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.

O HTML gerado adiciona a ID à parte do caminho da 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). Por exemplo, https://localhost:5001/Movies/Details retorna um erro 404. Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}":

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

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna HTTP 404. Usando @page "{id:int?}", o método OnGetAsync retornará 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 simultaneidade

Examine o método OnPostAsync no arquivo 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);
}

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.

Para testar o bloco catch:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
  4. Na janela do navegador anterior, poste as alterações no filme.

O código de produção talvez deseje detectar conflitos de simultaneidade. Confira Lidar com conflitos de simultaneidade para obter mais informações.

Análise de postagem e associação

Examine o arquivo 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);
    }

Quando uma solicitação HTTP GET for feita para a página Filmes/Editar, por exemplo, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.
  • O método Page renderiza a Página do Pages/Movies/Edit.cshtmlRazor. O arquivo Pages/Movies/Edit.cshtml contém a diretiva de modelo @model RazorPagesMovie.Pages.Movies.EditModel, que disponibiliza o modelo de filme na página.
  • O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:

  • Os valores de formulário na página são associados à propriedade Movie. O atributo [BindProperty] habilita o Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertido em uma data), o formulário é exibido novamente com os valores enviados.

  • Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método OnPostAsync do HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na página Editar do Razor.

Próximas etapas

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Aplicativo de filme aberto no Chrome

Atualizar o modelo

Atualize Models/Movie.cs com o seguinte código realçado:

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

No código anterior:

  • 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. Para obter mais informações, veja Tipos de Dados.
  • O atributo [Display] especifica o nome de exibição de um campo. No código anterior, Release Date em vez de ReleaseDate.
  • O atributo [DataType] especifica o tipo de dados (Date). As informações de hora armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de destino.

Uma janela do navegador com o mouse sobre o link Editar e um link da URL de https://localhost:1234/Movies/Edit/5 são exibidos

Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo 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>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos HTML em arquivos do Razor.

No código anterior, o Auxiliar de Marcação de Âncora gera dinamicamente o valor do atributo hrefdo HTML da Página do Razor (a rota é relativa), o asp-page e o identificador de rota (asp-route-id). Para obter mais informações, confira Geração de URL para Páginas.

Use Exibir Fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML gerado é mostrada abaixo:

<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 de filme com uma cadeia de caracteres de consulta. Por exemplo, o trecho ?id=1 em https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rota

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota {id:int}. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.

O HTML gerado adiciona a ID à parte do caminho da 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). Por exemplo, https://localhost:5001/Movies/Details retorna um erro 404. Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}":

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

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna HTTP 404. Usando @page "{id:int?}", o método OnGetAsync retornará 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 simultaneidade

Examine o método OnPostAsync no arquivo 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);
}

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.

Para testar o bloco catch:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
  4. Na janela do navegador anterior, poste as alterações no filme.

O código de produção talvez deseje detectar conflitos de simultaneidade. Confira Lidar com conflitos de simultaneidade para obter mais informações.

Análise de postagem e associação

Examine o arquivo 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);
    }

Quando uma solicitação HTTP GET for feita para a página Filmes/Editar, por exemplo, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.
  • O método Page renderiza a Página do Pages/Movies/Edit.cshtmlRazor. O arquivo Pages/Movies/Edit.cshtml contém a diretiva de modelo @model RazorPagesMovie.Pages.Movies.EditModel, que disponibiliza o modelo de filme na página.
  • O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:

  • Os valores de formulário na página são associados à propriedade Movie. O atributo [BindProperty] habilita o Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertido em uma data), o formulário é exibido novamente com os valores enviados.

  • Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método OnPostAsync do HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na página Editar do Razor.

Próximas etapas

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Aplicativo de filme aberto no Chrome

Atualizar o modelo

Atualize Models/Movie.cs com o seguinte código realçado:

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

No código anterior:

  • 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. Para obter mais informações, veja Tipos de Dados.
  • O atributo [Display] especifica o nome de exibição de um campo. No código anterior, Release Date em vez de ReleaseDate.
  • O atributo [DataType] especifica o tipo de dados (Date). As informações de hora armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de destino.

Uma janela do navegador com o mouse sobre o link Editar e um link da URL de https://localhost:1234/Movies/Edit/5 são exibidos

Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo 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>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos HTML em arquivos do Razor.

No código anterior, o Auxiliar de Marcação de Âncora gera dinamicamente o valor do atributo hrefdo HTML da Página do Razor (a rota é relativa), o asp-page e o identificador de rota (asp-route-id). Para obter mais informações, confira Geração de URL para Páginas.

Use Exibir Fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML gerado é mostrada abaixo:

<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 de filme com uma cadeia de caracteres de consulta. Por exemplo, o trecho ?id=1 em https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rota

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota {id:int}. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.

O HTML gerado adiciona a ID à parte do caminho da 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). Por exemplo, https://localhost:5001/Movies/Details retorna um erro 404. Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}":

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

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna HTTP 404. Usando @page "{id:int?}", o método OnGetAsync retornará 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 simultaneidade

Examine o método OnPostAsync no arquivo 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);
}

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.

Para testar o bloco catch:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
  4. Na janela do navegador anterior, poste as alterações no filme.

O código de produção talvez deseje detectar conflitos de simultaneidade. Confira Lidar com conflitos de simultaneidade para obter mais informações.

Análise de postagem e associação

Examine o arquivo 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);
    }

Quando uma solicitação HTTP GET for feita para a página Filmes/Editar, por exemplo, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.
  • O método Page renderiza a Página do Pages/Movies/Edit.cshtmlRazor. O arquivo Pages/Movies/Edit.cshtml contém a diretiva de modelo @model RazorPagesMovie.Pages.Movies.EditModel, que disponibiliza o modelo de filme na página.
  • O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:

  • Os valores de formulário na página são associados à propriedade Movie. O atributo [BindProperty] habilita o Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertido em uma data), o formulário é exibido novamente com os valores enviados.

  • Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método OnPostAsync do HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na página Editar do Razor.

Próximas etapas

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Aplicativo de filme aberto no Chrome

Atualizar o código gerado

Atualize Models/Movie.cs com o seguinte código realçado:

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

No código anterior:

  • 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. Para obter mais informações, veja Tipos de Dados.
  • O atributo [Display] especifica o nome de exibição de um campo. No código anterior, "Data de Lançamento" em vez de "ReleaseDate".
  • O atributo [DataType] especifica o tipo de dados (Date). As informações de hora armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de destino.

Uma janela do navegador com o mouse sobre o link Editar e um link da URL de https://localhost:1234/Movies/Edit/5 são exibidos

Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo 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>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos HTML em arquivos do Razor.

No código anterior, o Auxiliar de Marcação de Âncora gera dinamicamente o valor do atributo hrefdo HTML da Página do Razor (a rota é relativa), o asp-page e o identificador de rota (asp-route-id). Para obter mais informações, confira Geração de URL para Páginas.

Use Exibir Fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML gerado é mostrada abaixo:

<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 de filme com uma cadeia de caracteres de consulta. Por exemplo, o trecho ?id=1 em https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rota

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota {id:int}. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.

O HTML gerado adiciona a ID à parte do caminho da 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). Por exemplo, https://localhost:5001/Movies/Details retornará um erro 404. Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}":

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

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna HTTP 404. Usando @page "{id:int?}", o método OnGetAsync retornará 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 simultaneidade

Examine o método OnPostAsync no arquivo 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();
}

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme. O código anterior não detecta os conflitos que ocorrem devido a dois ou mais clientes estarem editando o mesmo filme simultaneamente. Nesse caso, as edições de vários clientes são aplicadas na ordem em que SaveChanges é chamada, e as edições aplicadas posteriormente podem substituir edições anteriores por valores obsoletos.

Para testar o bloco catch:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
  4. Na janela do navegador anterior, poste as alterações no filme.

Talvez o código de produção deseje detectar conflitos de simultaneidade adicionais, como vários clientes editando uma entidade ao mesmo tempo. Confira Lidar com conflitos de simultaneidade para obter mais informações.

Análise de postagem e associação

Examine o arquivo 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();
    }

Quando uma solicitação HTTP GET for feita para a página Filmes/Editar, por exemplo, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.
  • O método Page renderiza a Página do Pages/Movies/Edit.cshtmlRazor. O arquivo Pages/Movies/Edit.cshtml contém a diretiva de modelo @model RazorPagesMovie.Pages.Movies.EditModel, que disponibiliza o modelo de filme na página.
  • O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:

  • Os valores de formulário na página são associados à propriedade Movie. O atributo [BindProperty] habilita o Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertido em uma data), o formulário é exibido novamente com os valores enviados.

  • Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método OnPostAsync do HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na página Editar do Razor.

Próximas etapas

O aplicativo de filme gerado por scaffolding tem um bom começo, mas a apresentação não é ideal. ReleaseDate deve ser duas palavras, Data de Lançamento.

Aplicativo de filme aberto no Chrome

Atualizar o código gerado

Abra o arquivo Models/Movie.cs e adicione as linhas realçadas mostradas no seguinte código:

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:

  • 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. Para obter mais informações, veja Tipos de Dados.
  • O atributo [Display] especifica o nome de exibição de um campo. No código anterior, "Data de Lançamento" em vez de "ReleaseDate".
  • O atributo [DataType] especifica o tipo de dados (Date). As informações de hora armazenadas no campo não são exibidas.

DataAnnotations é abordado no próximo tutorial.

Navegue até Páginas/Filmes e passe o mouse sobre um link Editar para ver a URL de destino.

Uma janela do navegador com o mouse sobre o link Editar e um link da URL de https://localhost:1234/Movies/Edit/5 são exibidos

Os links Editar, Detalhes e Excluir são gerados pelo Auxiliar de Marcação de Âncora no arquivo 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>

Os Auxiliares de Marcação permitem que o código do servidor participe da criação e renderização de elementos HTML em arquivos do Razor.

No código anterior, o Auxiliar de Marcação de Âncora gera dinamicamente o valor do atributo hrefdo HTML da Página do Razor (a rota é relativa), o asp-page e o identificador de rota (asp-route-id). Para obter mais informações, confira Geração de URL para Páginas.

Use Exibir Fonte em seu navegador favorito para examinar a marcação gerada. Uma parte do HTML gerado é mostrada abaixo:

<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 de filme com uma cadeia de caracteres de consulta. Por exemplo, o trecho ?id=1 em https://localhost:5001/Movies/Details?id=1.

Adicionar modelo de rota

Atualize as Páginas Editar, Detalhes e Excluir do Razor para que elas usem o modelo de rota {id:int}. Altere a diretiva de página de cada uma dessas páginas de @page para @page "{id:int}". Execute o aplicativo e, em seguida, exiba o código-fonte.

O HTML gerado adiciona a ID à parte do caminho da 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). Por exemplo, https://localhost:5001/Movies/Details retornará um erro 404. Para tornar a ID opcional, acrescente ? à restrição de rota:

@page "{id:int?}"

Teste o comportamento de @page "{id:int?}":

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

Com a diretiva @page "{id:int}", o ponto de interrupção nunca é atingido. O mecanismo de roteamento retorna HTTP 404. Usando @page "{id:int?}", o método OnGetAsync retornará 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 simultaneidade

Examine o método OnPostAsync no arquivo 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);
}

O código anterior detecta exceções de simultaneidade quando um cliente exclui o filme e o outro cliente posta alterações no filme.

Para testar o bloco catch:

  1. Defina um ponto de interrupção em catch (DbUpdateConcurrencyException)
  2. Selecione Editar para um filme, faça alterações, mas não insira Salvar.
  3. Em outra janela do navegador, selecione o link Excluir do mesmo filme e, em seguida, exclua o filme.
  4. Na janela do navegador anterior, poste as alterações no filme.

O código de produção talvez deseje detectar conflitos de simultaneidade. Confira Lidar com conflitos de simultaneidade para obter mais informações.

Análise de postagem e associação

Examine o arquivo 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);
    }

Quando uma solicitação HTTP GET for feita para a página Filmes/Editar, por exemplo, https://localhost:5001/Movies/Edit/3:

  • O método OnGetAsync busca o filme do banco de dados e retorna o método Page.
  • O método Page renderiza a Página do Pages/Movies/Edit.cshtmlRazor. O arquivo Pages/Movies/Edit.cshtml contém a diretiva de modelo @model RazorPagesMovie.Pages.Movies.EditModel, que disponibiliza o modelo de filme na página.
  • O formulário Editar é exibido com os valores do filme.

Quando a página Movies/Edit é postada:

  • Os valores de formulário na página são associados à propriedade Movie. O atributo [BindProperty] habilita o Model binding.

    [BindProperty]
    public Movie Movie { get; set; }
    
  • Se houver erros no estado do modelo (por exemplo, ReleaseDate não pode ser convertido em uma data), o formulário é exibido novamente com os valores enviados.

  • Se não houver erros do modelo, o filme será salvo.

Os métodos HTTP GET nas páginas Índice, Criar e Excluir do Razor seguem um padrão semelhante. O método OnPostAsync do HTTP POST na página Criar do Razor segue um padrão semelhante ao método OnPostAsync na página Editar do Razor.

Próximas etapas