Partilhar via


Implementando a funcionalidade CRUD básica com o Entity Framework em ASP.NET aplicativo MVC (2 de 10)

por Tom Dykstra

O aplicativo Web de exemplo da Contoso University demonstra como criar aplicativos MVC 4 ASP.NET usando o Entity Framework 5 Code First e o Visual Studio 2012. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial da série.

Observação

Se você tiver um problema que não consegue resolver, baixe o capítulo completo e tente reproduzir seu problema. Geralmente, você pode encontrar a solução para o problema comparando seu código com o código concluído. Para obter alguns erros comuns e como resolvê-los, consulte Erros e soluções alternativas.

No tutorial anterior, você criou um aplicativo MVC que armazena e exibe dados usando o Entity Framework e o SQL Server LocalDB. Neste tutorial, você examinará e personalizará o código CRUD (criar, ler, atualizar, excluir) que o scaffolding MVC cria automaticamente para você em controladores e exibições.

Observação

É uma prática comum implementar o padrão de repositório para criar uma camada de abstração entre o controlador e a camada de acesso a dados. Para manter esses tutoriais simples, você não implementará um repositório até um tutorial posterior desta série.

Neste tutorial, você criará as seguintes páginas da Web:

Captura de tela mostrando a página Detalhes do Aluno da Contoso University.

Captura de tela mostrando a página Edição de Estudante da Contoso University.

Captura de tela mostrando a página Criar Aluno da Contoso University.

Captura de tela que mostra a página Excluir aluno.

Criando uma página de detalhes

O código scaffolded para a página Alunos Index deixou de fora a Enrollments propriedade, porque essa propriedade contém uma coleção. Details Na página, você exibirá o conteúdo da coleção em uma tabela HTML.

Em Controladores\StudentController.cs, o método de ação para a Details exibição usa o Find método para recuperar uma única Student entidade.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

O valor da chave é passado para o método como o id parâmetro e vem dos dados de rota no hiperlink Detalhes na página Índice.

  1. Abra Views\Student\Details.cshtml. Cada campo é exibido usando um DisplayFor auxiliar, conforme mostrado no exemplo a seguir:

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. Após o EnrollmentDate campo e imediatamente antes da tag de fechamento fieldset , adicione o código para exibir uma lista de registros, conforme mostrado no exemplo a seguir:

    <div class="display-label">
            @Html.LabelFor(model => model.Enrollments)
        </div>
        <div class="display-field">
            <table>
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </div>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Esse código percorre as entidades na propriedade de navegação Enrollments. Para cada Enrollment entidade na propriedade, ele exibe o título do curso e a nota. O título do curso é recuperado da Course entidade armazenada na Course propriedade de navegação da Enrollments entidade. Todos esses dados são recuperados do banco de dados automaticamente quando necessário. (Em outras palavras, você está usando o carregamento lento aqui. Você não especificou o carregamento ansioso para a Courses propriedade de navegação, portanto, na primeira vez que você tentar acessar essa propriedade, uma consulta será enviada ao banco de dados para recuperar os dados. Você pode ler mais sobre carregamento lento e carregamento ansioso no tutorial Lendo dados relacionados mais adiante nesta série.)

  3. Execute a página selecionando a guia Alunos e clicando em um link Detalhes para Alexander Carson. Você verá a lista de cursos e notas do aluno selecionado:

    Student_Details_page

Atualizando a página Criar

  1. Em Controllers\StudentController.cs, substitua o HttpPost``Create método de ação pelo seguinte código para adicionar um try-catch bloco e o atributo Bind ao método scaffolded:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(
       [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
       Student student)
    {
       try
       {
          if (ModelState.IsValid)
          {
             db.Students.Add(student);
             db.SaveChanges();
             return RedirectToAction("Index");
          }
       }
       catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
          ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
       }
       return View(student);
    }
    

    Esse código adiciona a Student entidade criada pelo associador de modelo MVC ASP.NET ao Students conjunto de entidades e salva as alterações no banco de dados. (O associador de modelos refere-se à funcionalidade MVC ASP.NET que facilita o trabalho com dados enviados por um formulário; um associador de modelos converte valores de formulário postados em tipos CLR e os passa para o método de ação em parâmetros. Nesse caso, o associador de modelos instancia uma Student entidade para você usando valores de propriedade da Form coleção.)

    O ValidateAntiForgeryToken atributo ajuda a evitar ataques de falsificação de solicitação entre sites.

> [!WARNING]
    > Security - The `Bind` attribute is added to protect against *over-posting*. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to update.
    > 
    > [!code-csharp[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample5.cs?highlight=7)]
    > 
    > Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as [fiddler](http://fiddler2.com/home), or write some JavaScript, to post a `Secret` form value. Without the [Bind](https://msdn.microsoft.com/library/system.web.mvc.bindattribute(v=vs.108).aspx) attribute limiting the fields that the model binder uses when it creates a `Student` instance*,* the model binder would pick up that `Secret` form value and use it to update the `Student` entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
    > 
    > ![](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/_static/image6.png)  
    > 
    > The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to update that property.
    > 
    > It's a security best practice to use the `Include` parameter with the `Bind` attribute to *allowed attributes* fields. It's also possible to use the `Exclude` parameter to *blocked attributes* fields you want to exclude. The reason `Include` is more secure is that when you add a new property to the entity, the new field is not automatically protected by an `Exclude` list.
    > 
    > Another alternative approach, and one preferred by many, is to use only view models with model binding. The view model contains only the properties you want to bind. Once the MVC model binder has finished, you copy the view model properties to the entity instance.

    Other than the `Bind` attribute, the `try-catch` block is the only change you've made to the scaffolded code. If an exception that derives from [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) is caught while the changes are being saved, a generic error message is displayed. [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception (and non-null inner exceptions ) with a logging mechanism such as [ELMAH](https://code.google.com/p/elmah/).

    The code in *Views\Student\Create.cshtml* is similar to what you saw in *Details.cshtml*, except that `EditorFor` and `ValidationMessageFor` helpers are used for each field instead of `DisplayFor`. The following example shows the relevant code:

    [!code-cshtml[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample6.cshtml)]

    *Create.cshtml* also includes `@Html.AntiForgeryToken()`, which works with the `ValidateAntiForgeryToken` attribute in the controller to help prevent [cross-site request forgery](../../security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages.md) attacks.

    No changes are required in *Create.cshtml*.
  1. Execute a página selecionando a guia Alunos e clicando em Criar novo.

    Student_Create_page

    Algumas validações de dados funcionam por padrão. Insira nomes e uma data inválida e clique em Criar para ver a mensagem de erro.

    Students_Create_page_error_message

    O código realçado a seguir mostra a verificação de validação do modelo.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            db.Students.Add(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(student);
    }
    

    Altere a data para um valor válido, como 01/09/2005, e clique em Criar para ver o novo aluno aparecer na página Índice .

    Students_Index_page_with_new_student

Atualizando a página Editar POST

Em Controladores\StudentController.cs, o HttpGet Edit método (aquele sem o HttpPost atributo) usa o Find método para recuperar a entidade selecionada Student , como você viu no Details método. Não é necessário alterar esse método.

No entanto, substitua o HttpPost Edit método de ação pelo seguinte código para adicionar um try-catch bloco e o atributo Bind:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
   }
   return View(student);
}

Esse código é semelhante ao que você viu no HttpPost Create método. No entanto, em vez de adicionar a entidade criada pelo associador de modelos ao conjunto de entidades, esse código define um sinalizador na entidade indicando que ela foi alterada. Quando o método SaveChanges é chamado, o sinalizador Modified faz com que o Entity Framework crie instruções SQL para atualizar a linha do banco de dados. Todas as colunas da linha do banco de dados serão atualizadas, incluindo aquelas que o usuário não alterou, e os conflitos de simultaneidade serão ignorados. (Você aprenderá a lidar com a simultaneidade em um tutorial posterior desta série.)

Estados de entidade e os métodos Attach e SaveChanges

O contexto de banco de dados controla se as entidades em memória estão em sincronia com suas linhas correspondentes no banco de dados, e essas informações determinam o que acontece quando você chama o método SaveChanges. Por exemplo, quando você passa uma nova entidade para o método Add , o estado dessa entidade é definido como Added. Em seguida, quando você chama o método SaveChanges , o contexto do banco de dados emite um comando SQL INSERT .

Uma entidade pode estar em um dosseguintes estados:

  • Added. A entidade ainda não existe no banco de dados. O SaveChanges método deve emitir uma INSERT instrução.
  • Unchanged. Nada precisa ser feito com essa entidade pelo método SaveChanges. Ao ler uma entidade do banco de dados, a entidade começa com esse status.
  • Modified. Alguns ou todos os valores de propriedade da entidade foram modificados. O SaveChanges método deve emitir uma UPDATE instrução.
  • Deleted. A entidade foi marcada para exclusão. O SaveChanges método deve emitir uma DELETE instrução.
  • Detached. A entidade não está sendo controlada pelo contexto de banco de dados.

Em um aplicativo da área de trabalho, em geral, as alterações de estado são definidas automaticamente. Em um tipo de aplicativo de área de trabalho, você lê uma entidade e faz alterações em alguns de seus valores de propriedade. Isso faz com que seu estado da entidade seja alterado automaticamente para Modified. Em seguida, quando você chama SaveChangeso , o Entity Framework gera uma instrução SQL UPDATE que atualiza apenas as propriedades reais que você alterou.

A natureza desconectada dos aplicativos Web não permite essa sequência contínua. O DbContext que lê uma entidade é descartado depois que uma página é renderizada. Quando o HttpPost Edit método de ação é chamado, uma nova solicitação é feita e você tem uma nova instância do DbContext, portanto, você precisa definir manualmente o estado da entidade como Modified. Em seguida, quando você chama SaveChanges, o Entity Framework atualiza todas as colunas da linha do banco de dados, pois o contexto não tem como saber quais propriedades você alterou.

Se você quiser que a instrução SQL Update atualize apenas os campos que o usuário realmente alterou, você pode salvar os valores originais de alguma forma (como campos ocultos) para que eles estejam disponíveis quando o HttpPost Edit método for chamado. Em seguida, você pode criar uma Student entidade usando os valores originais, chamar o Attach método com essa versão original da entidade, atualizar os valores da entidade para os novos valores e chamar SaveChanges. Para obter mais informações, consulte Estados da entidade e SaveChanges e Dados Locais no MSDN Data Developer Center.

O código em Views\Student\Edit.cshtml é semelhante ao que você viu em Create.cshtml e nenhuma alteração é necessária.

Execute a página selecionando a guia Alunos e clicando em um hiperlink Editar .

Student_Edit_page

Altere alguns dos dados e clique em Salvar. Você verá os dados alterados na página Índice.

Students_Index_page_after_edit

Atualizando a página de exclusão

Em Controllers\StudentController.cs, o código de modelo do HttpGet Delete método usa o Find método para recuperar a entidade selecionada Student , como você viu nos Details métodos e Edit . No entanto, para implementar uma mensagem de erro personalizada quando a chamada a SaveChanges falhar, você adicionará uma funcionalidade a esse método e à sua exibição correspondente.

Como você viu para operações de atualização e criação, as operações de exclusão exigem dois métodos de ação. O método chamado em resposta a uma solicitação GET exibe uma exibição que dá ao usuário a chance de aprovar ou cancelar a operação de exclusão. Se o usuário aprová-la, uma solicitação POST será criada. Quando isso acontece, o HttpPost Delete método é chamado e, em seguida, esse método realmente executa a operação de exclusão.

Você adicionará um try-catch bloco ao HttpPost Delete método para lidar com quaisquer erros que possam ocorrer quando o banco de dados for atualizado. Se ocorrer um erro, o HttpPost Delete método chamará o HttpGet Delete método, passando a ele um parâmetro que indica que ocorreu um erro. Em seguida, o HttpGet Delete método exibe novamente a página de confirmação junto com a mensagem de erro, dando ao usuário a oportunidade de cancelar ou tentar novamente.

  1. Substitua o método de HttpGet Delete ação pelo seguinte código, que gerencia o relatório de erros:

    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

    Esse código aceita um parâmetro booleano opcional que indica se ele foi chamado após uma falha ao salvar as alterações. Esse parâmetro é false quando o HttpGet Delete método é chamado sem uma falha anterior. Quando ele é chamado pelo HttpPost Delete método em resposta a um erro de atualização do banco de dados, o parâmetro é true e uma mensagem de erro é passada para a exibição.

  2. Substitua o método de HttpPost Delete ação (chamado DeleteConfirmed) pelo código a seguir, que executa a operação de exclusão real e captura todos os erros de atualização do banco de dados.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Esse código recupera a entidade selecionada e, em seguida, chama o método Remove para definir o status da entidade como Deleted. Quando SaveChanges é chamado, um comando SQL DELETE é gerado. Você também alterou o nome do método de ação de DeleteConfirmed para Delete. O código scaffolded nomeou o HttpPost Delete método DeleteConfirmed para dar ao HttpPost método uma assinatura exclusiva. (O CLR requer que os métodos sobrecarregados tenham parâmetros de método diferentes.) Agora que as assinaturas são exclusivas, você pode manter a convenção MVC e usar o mesmo nome para os HttpPost métodos e HttpGet delete.

    Se melhorar o desempenho em um aplicativo de alto volume for uma prioridade, você poderá evitar uma consulta SQL desnecessária para recuperar a linha substituindo as linhas de código que chamam os Find métodos and Remove pelo seguinte código, conforme mostrado no realce amarelo:

    Student studentToDelete = new Student() { StudentID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Esse código instancia uma Student entidade usando apenas o valor da chave primária e, em seguida, define o estado da entidade como Deleted. Isso é tudo o que o Entity Framework precisa para excluir a entidade.

    Conforme observado, o HttpGet Delete método não exclui os dados. Executar uma operação de exclusão em resposta a uma solicitação GET (ou, nesse caso, executar qualquer operação de edição, operação de criação ou qualquer outra operação que altere dados) cria um risco de segurança. Para obter mais informações, consulte ASP.NET Dica MVC #46 — Não use links de exclusão porque eles criam falhas de segurança no blog de Stephen Walther.

  3. Em Views\Student\Delete.cshtml, adicione uma mensagem de erro entre o h2 título e o h3 título, conforme mostrado no exemplo a seguir:

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    

    Execute a página selecionando a guia Alunos e clicando em um hiperlink Excluir :

    Student_Delete_page

  4. Clique em Excluir. A página Índice será exibida sem o aluno excluído. (Você verá um exemplo do código de tratamento de erros em ação no Tutorial de manipulação de simultaneidade mais adiante nesta série.)

Garantir que as conexões de banco de dados não sejam deixadas abertas

Para garantir que as conexões de banco de dados sejam fechadas corretamente e que os recursos que elas contêm sejam liberados, você deve garantir que a instância de contexto seja descartada. É por isso que o código scaffolded fornece um método Dispose no final da StudentController classe em StudentController.cs, conforme mostrado no exemplo a seguir:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

A classe base Controller já implementa a IDisposable interface, portanto, esse código simplesmente adiciona uma substituição ao Dispose(bool) método para descartar explicitamente a instância de contexto.

Resumo

Agora você tem um conjunto completo de páginas que executam operações CRUD simples para Student entidades. Você usou auxiliares MVC para gerar elementos de interface do usuário para campos de dados. Para obter mais informações sobre auxiliares MVC, consulte Renderizando um formulário usando auxiliares HTML (a página é para MVC 3, mas ainda é relevante para MVC 4).

No próximo tutorial, você expandirá a funcionalidade da página Índice adicionando classificação e paginação.

Links para outros recursos do Entity Framework podem ser encontrados no Mapa de Conteúdo de Acesso a Dados do ASP.NET.