Condividi tramite


Implementazione della funzionalità CRUD di base con Entity Framework in ASP.NET'applicazione MVC (2 di 10)

di Tom Dykstra

L'applicazione Web di esempio Contoso University illustra come creare ASP.NET applicazioni MVC 4 usando Entity Framework 5 Code First e Visual Studio 2012. Per informazioni sulla serie di esercitazioni, vedere la prima esercitazione della serie.

Nota

Se si verifica un problema che non è possibile risolvere, scaricare il capitolo completato e provare a riprodurre il problema. In genere è possibile trovare la soluzione al problema confrontando il codice con il codice completato. Per alcuni errori comuni e come risolverli, vedere Errori e soluzioni alternative.

Nell'esercitazione precedente è stata creata un'applicazione MVC che archivia e visualizza i dati usando Entity Framework e SQL Server LocalDB. In questa esercitazione verrà esaminato e personalizzato il codice CRUD (creare, leggere, aggiornare, eliminare) creato automaticamente dallo scaffolding MVC nei controller e nelle visualizzazioni.

Nota

È pratica comune implementare lo schema del repository per creare un livello di astrazione tra il controller e il livello di accesso ai dati. Per semplificare queste esercitazioni, non si implementerà un repository fino a quando non verrà eseguita un'esercitazione successiva in questa serie.

In questa esercitazione verranno create le pagine Web seguenti:

Screenshot che mostra la pagina Dettagli studenti Contoso University.

Screenshot che mostra la pagina Contoso University Student Edit .

Screenshot che mostra la pagina Contoso University Student Create .

Screenshot che mostra la pagina Student Delete.

Creazione di una pagina dettagli

Il codice sottoposto a scaffolding per la pagina Students Index ha lasciato la Enrollments proprietà , perché tale proprietà contiene un insieme. Details Nella pagina verrà visualizzato il contenuto della raccolta in una tabella HTML.

In Controllers\StudentController.cs il metodo di azione per la Details visualizzazione usa il Find metodo per recuperare una singola Student entità.

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

Il valore della chiave viene passato al metodo come id parametro e proviene dai dati di route nel collegamento ipertestuale Dettagli nella pagina Indice.

  1. Aprire Views\Student\Details.cshtml. Ogni campo viene visualizzato usando un DisplayFor helper, come illustrato nell'esempio seguente:

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. Dopo il EnrollmentDate campo e immediatamente prima del tag di chiusura fieldset , aggiungere il codice per visualizzare un elenco di registrazioni, come illustrato nell'esempio seguente:

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

    Il codice esegue il ciclo nelle entità nella proprietà di navigazione Enrollments. Per ogni Enrollment entità nella proprietà, visualizza il titolo del corso e il voto. Il titolo del corso viene recuperato dall'entità Course archiviata nella Course proprietà di navigazione dell'entità Enrollments . Tutti questi dati vengono recuperati automaticamente dal database quando sono necessari. (In altre parole, si usa il caricamento differita qui. Non è stato specificato il caricamento eager per la Courses proprietà di navigazione, quindi la prima volta che si tenta di accedere a tale proprietà, viene inviata una query al database per recuperare i dati. Altre informazioni sul caricamento differita e sul caricamento eager sono disponibili nell'esercitazione Lettura dei dati correlati più avanti in questa serie.

  3. Eseguire la pagina selezionando la scheda Studenti e facendo clic su un collegamento Dettagli per Alexander Carson. Viene visualizzato l'elenco dei corsi e dei voti dello studente selezionato:

    Student_Details_page

Aggiornamento della pagina Crea

  1. In Controllers\StudentController.cs sostituire il HttpPost``Create metodo action con il codice seguente per aggiungere un try-catch blocco e l'attributo Bind al metodo 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);
    }
    

    Questo codice aggiunge l'entità Student creata dal gestore di associazione di modelli MVC ASP.NET al Students set di entità e quindi salva le modifiche nel database. Il gestore di associazione di modelli fa riferimento alla funzionalità ASP.NET MVC che semplifica l'uso dei dati inviati da un modulo. Un gestore di associazione di modelli converte i valori del modulo inviati in tipi CLR e li passa al metodo action nei parametri. In questo caso, lo strumento di associazione di modelli crea un'istanza di un'entità Student usando i valori delle proprietà della Form raccolta.

    L'attributo ValidateAntiForgeryToken consente di evitare attacchi falsi alla richiesta intersito.

> [!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. Eseguire la pagina selezionando la scheda Students (Studenti ) e facendo clic su Create New (Crea nuovo).

    Student_Create_page

    Per impostazione predefinita, la convalida dei dati funziona. Immettere i nomi e una data non valida e fare clic su Crea per visualizzare il messaggio di errore.

    Students_Create_page_error_message

    Il codice evidenziato seguente mostra il controllo di convalida del modello.

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

    Modificare la data in un valore valido, ad esempio 1/9/2005 e fare clic su Crea per visualizzare la visualizzazione del nuovo studente nella pagina Indice .

    Students_Index_page_with_new_student

Aggiornamento della pagina Modifica POST

In Controllers\StudentController.cs, il HttpGet Edit metodo (quello senza l'attributo HttpPost ) usa il Find metodo per recuperare l'entità selezionata Student , come si è visto nel Details metodo . Non è necessario modificare questo metodo.

Sostituire tuttavia il HttpPost Edit metodo action con il codice seguente per aggiungere un try-catch blocco e l'attributo 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);
}

Questo codice è simile a quello visualizzato nel HttpPost Create metodo . Tuttavia, invece di aggiungere l'entità creata dal gestore di associazione di modelli al set di entità, questo codice imposta un flag sull'entità che indica che è stato modificato. Quando viene chiamato il metodo SaveChanges , il flag Modified fa sì che Entity Framework crei istruzioni SQL per aggiornare la riga del database. Tutte le colonne della riga del database verranno aggiornate, incluse quelle che l'utente non ha modificato e i conflitti di concorrenza vengono ignorati. Si apprenderà come gestire la concorrenza in un'esercitazione successiva di questa serie.

Stati dell'entità e metodi Attach e SaveChanges

Il contesto del database rileva se le entità in memoria sono sincronizzate con le righe corrispondenti nel database e queste informazioni determinano le operazioni eseguite quando viene chiamato il metodo SaveChanges. Ad esempio, quando si passa una nuova entità al metodo Add , lo stato dell'entità viene impostato su Added. Quindi, quando si chiama il metodo SaveChanges , il contesto del database genera un comando SQL INSERT .

Un'entità può trovarsi in uno deglistati seguenti:

  • Added. L'entità non esiste ancora nel database. Il SaveChanges metodo deve emettere un'istruzione INSERT .
  • Unchanged. Il metodo SaveChanges non deve eseguire alcuna operazione con l'entità. Quando un'entità viene letta dal database, l'entità ha inizialmente questo stato.
  • Modified. Sono stati modificati alcuni o tutti i valori di proprietà dell'entità. Il SaveChanges metodo deve emettere un'istruzione UPDATE .
  • Deleted. L'entità è stata contrassegnata per l'eliminazione. Il SaveChanges metodo deve emettere un'istruzione DELETE .
  • Detached. L'entità non viene registrata dal contesto del database.

In un'applicazione desktop le modifiche dello stato vengono in genere impostate automaticamente. In un tipo di applicazione desktop si legge un'entità e si apportano modifiche ad alcuni dei relativi valori delle proprietà. In questo modo lo stato dell'entità viene modificato automaticamente in Modified. Quindi, quando si chiama SaveChanges, Entity Framework genera un'istruzione SQL UPDATE che aggiorna solo le proprietà effettive modificate.

La natura disconnessa delle app Web non consente questa sequenza continua. DbContext che legge un'entità viene eliminata dopo il rendering di una pagina. Quando viene chiamato il HttpPost Edit metodo di azione, viene effettuata una nuova richiesta e si dispone di una nuova istanza di DbContext, quindi è necessario impostare manualmente lo stato Modified. dell'entità su Then quando si chiama SaveChanges, Entity Framework aggiorna tutte le colonne della riga del database, perché il contesto non è in grado di conoscere le proprietà modificate.

Se si desidera che l'istruzione SQL Update aggiorni solo i campi effettivamente modificati dall'utente, è possibile salvare i valori originali in qualche modo (ad esempio i campi nascosti) in modo che siano disponibili quando viene chiamato il HttpPost Edit metodo . È quindi possibile creare un'entità Student usando i valori originali, chiamare il Attach metodo con la versione originale dell'entità, aggiornare i valori dell'entità ai nuovi valori e quindi chiamare SaveChanges. Per altre informazioni, vedere Stati entità e SaveChanges e Dati locali in MSDN Data Developer Center.

Il codice in Views\Student\Edit.cshtml è simile a quello visualizzato in Create.cshtml e non sono necessarie modifiche.

Eseguire la pagina selezionando la scheda Students (Studenti ) e quindi facendo clic su un collegamento ipertestuale Edit (Modifica ).

Student_Edit_page

Modificare alcuni dati e fare clic su Salva. I dati modificati sono visualizzati nella pagina Indice.

Students_Index_page_after_edit

Aggiornamento della pagina Elimina

In Controllers\StudentController.cs il codice del modello per il HttpGet Delete metodo usa il Find metodo per recuperare l'entità selezionata Student , come illustrato nei Details metodi e Edit . Tuttavia, per implementare un messaggio di errore personalizzato quando la chiamata di SaveChanges ha esito negativo, verrà aggiunta una funzionalità al metodo e alla visualizzazione corrispondente.

Analogamente alle operazioni di aggiornamento e creazione, le operazioni di eliminazione richiedono due metodi di azione. Il metodo chiamato in risposta a una richiesta GET visualizza una visualizzazione che consente all'utente di approvare o annullare l'operazione di eliminazione. Se l'utente approva, viene creata una richiesta POST. In questo caso, viene chiamato il HttpPost Delete metodo e quindi il metodo esegue effettivamente l'operazione di eliminazione.

Si aggiungerà un try-catch blocco al HttpPost Delete metodo per gestire eventuali errori che potrebbero verificarsi quando il database viene aggiornato. Se si verifica un errore, il HttpPost Delete metodo chiama il HttpGet Delete metodo , passandolo un parametro che indica che si è verificato un errore. Il HttpGet Delete metodo visualizza quindi nuovamente la pagina di conferma insieme al messaggio di errore, dando all'utente l'opportunità di annullare o riprovare.

  1. Sostituire il HttpGet Delete metodo action con il codice seguente, che gestisce la segnalazione degli errori:

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

    Questo codice accetta un parametro booleano facoltativo che indica se è stato chiamato dopo un errore di salvataggio delle modifiche. Questo parametro si verifica false quando il HttpGet Delete metodo viene chiamato senza un errore precedente. Quando viene chiamato dal HttpPost Delete metodo in risposta a un errore di aggiornamento del database, il parametro è true e viene passato un messaggio di errore alla vista.

  2. Sostituire il HttpPost Delete metodo di azione (denominato DeleteConfirmed) con il codice seguente, che esegue l'operazione di eliminazione effettiva e rileva eventuali errori di aggiornamento del database.

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

    Questo codice recupera l'entità selezionata, quindi chiama il metodo Remove per impostare lo stato dell'entità su Deleted. Quando SaveChanges viene chiamato, viene generato un comando SQL DELETE . Anche il nome del metodo di azione è stato modificato da DeleteConfirmed a Delete. Codice con scaffolding denominato il HttpPost Delete metodo per assegnare al HttpPost metodo DeleteConfirmed una firma univoca. ( CLR richiede metodi di overload per avere parametri di metodo diversi. Ora che le firme sono univoche, è possibile attenersi alla convenzione MVC e usare lo stesso nome per i HttpPost metodi ed HttpGet delete.

    Se si migliorano le prestazioni in un'applicazione con volumi elevati è una priorità, è possibile evitare una query SQL non necessaria per recuperare la riga sostituendo le righe di codice che chiamano i Find metodi e Remove con il codice seguente, come illustrato nell'evidenziazione gialla:

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

    Questo codice crea un'istanza di un'entità Student usando solo il valore della chiave primaria e quindi imposta lo stato dell'entità su Deleted. Questo è tutto ciò di cui Entity Framework necessita per eliminare l'entità.

    Come indicato, il HttpGet Delete metodo non elimina i dati. L'esecuzione di un'operazione di eliminazione in risposta a una richiesta GET (o per tale questione, l'esecuzione di qualsiasi operazione di modifica, l'operazione di creazione o qualsiasi altra operazione che modifica i dati) crea un rischio per la sicurezza. Per altre informazioni, vedere ASP.NET MVC Tip #46 — Don't use Delete Links because they create Security Holes on Stephen Walther's blog .Fori di sicurezza.

  3. In Views\Student\Delete.cshtml aggiungere un messaggio di errore tra l'intestazione h2 e l'intestazione h3 , come illustrato nell'esempio seguente:

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

    Eseguire la pagina selezionando la scheda Students (Studenti ) e facendo clic su un collegamento ipertestuale Delete (Elimina ):

    Student_Delete_page

  4. Fai clic su Elimina. Viene visualizzata la pagina Index senza lo studente eliminato. Verrà visualizzato un esempio del codice di gestione degli errori in azione in Gestione dell'esercitazione sulla concorrenza più avanti in questa serie.

Verifica che le connessioni di database non siano aperte

Per assicurarsi che le connessioni di database siano chiuse correttamente e che le risorse che contengono liberate, si noterà che l'istanza del contesto viene eliminata. Ecco perché il codice con scaffolding fornisce un metodo Dispose alla fine della StudentController classe in StudentController.cs, come illustrato nell'esempio seguente:

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

La classe base Controller implementa già l'interfaccia IDisposable , quindi questo codice aggiunge semplicemente un override al Dispose(bool) metodo per eliminare in modo esplicito l'istanza del contesto.

Riepilogo

È ora disponibile un set completo di pagine che eseguono semplici operazioni CRUD per Student le entità. Sono stati usati helper MVC per generare elementi dell'interfaccia utente per i campi dati. Per altre informazioni sugli helper MVC, vedere Rendering di un modulo con helper HTML (la pagina è relativa a MVC 3, ma è ancora rilevante per MVC 4).

Nell'esercitazione successiva si espanderanno le funzionalità della pagina Indice aggiungendo l'ordinamento e il paging.

I collegamenti ad altre risorse di Entity Framework sono disponibili nella mappa del contenuto di accesso ai dati ASP.NET.