Esame dei metodi di azione di modifica e delle visualizzazioni per il controller di film

di Rick Anderson

Nota

Una versione aggiornata di questa esercitazione è disponibile qui usando la versione più recente di Visual Studio. La nuova esercitazione usa ASP.NET Core MVC, che offre molti miglioramenti in questa esercitazione.

Questa esercitazione illustra ASP.NET Core MVC con i controller e le viste. Razor Pages è una nuova alternativa in ASP.NET Core, un modello di programmazione basato su pagine che semplifica la creazione dell'interfaccia utente Web e una maggiore produttività. È consigliabile provare l'esercitazione sulle pagine Razor prima della versione MVC. L'esercitazione sulle pagine Razor:

  • È più semplice da seguire.
  • Riguarda più funzionalità.
  • È l'approccio preferito per lo sviluppo di nuove app.

In questa sezione verranno esaminati i metodi di azione generati Edit e le visualizzazioni per il controller di film. Tuttavia, prima di tutto, prenderemo una breve deviazione per migliorare la data di rilascio. Aprire il file Models\Movie.cs e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

È anche possibile specificare le impostazioni cultura della data come segue:

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

Gli attributi DataAnnotations verranno esaminati nell'esercitazione successiva. L'attributo Display specifica il testo da visualizzare per il nome di un campo, in questo caso "Release Date" anziché "ReleaseDate". L'attributo DataType specifica il tipo di dati, in questo caso è una data, quindi le informazioni sull'ora archiviate nel campo non vengono visualizzate. L'attributo DisplayFormat è necessario per un bug nel browser Chrome che esegue il rendering non corretto dei formati di data.

Eseguire l'applicazione e passare al Movies controller. Tenere premuto il puntatore del mouse su un collegamento Modifica per visualizzare l'URL a cui si collega.

EditLink_sm

Il collegamento Edit è stato generato dal Html.ActionLink metodo nella vista Views\Movies\Index.cshtml :

@Html.ActionLink("Edit", "Edit", new { id=item.ID })

Html.ActionLink

L'oggetto Html è un helper esposto usando una proprietà nella classe di base System.Web.Mvc.WebViewPage . Il ActionLink metodo dell'helper semplifica la generazione dinamica di collegamenti ipertestuali HTML che si collegano ai metodi di azione nei controller. Il primo argomento del metodo è il testo del collegamento di cui eseguire il ActionLink rendering, ad esempio <a>Edit Me</a>. Il secondo argomento è il nome del metodo di azione da richiamare (in questo caso, l'azione Edit ). L'argomento finale è un oggetto anonimo che genera i dati della route (in questo caso, l'ID di 4).

Il collegamento generato visualizzato nell'immagine precedente è http://localhost:1234/Movies/Edit/4. La route predefinita (stabilita in App_Start\RouteConfig.cs) accetta il modello {controller}/{action}/{id}url . Pertanto, ASP.NET si traduce http://localhost:1234/Movies/Edit/4 in una richiesta al Edit metodo di azione del Movies controller con il parametro ID uguale a 4. Esaminare il codice seguente dal file App_Start\RouteConfig.cs . Il metodo MapRoute viene usato per instradare le richieste HTTP al controller e al metodo di azione corretti e fornire il parametro ID facoltativo. Il metodo MapRoute viene usato anche dagli HtmlHelpers , ActionLink ad esempio per generare URL in base al controller, al metodo di azione e ai dati di route.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", 
            id = UrlParameter.Optional }
    );
}

È anche possibile passare parametri del metodo di azione usando una stringa di query. Ad esempio, l'URL http://localhost:1234/Movies/Edit?ID=3 passa anche il parametro ID di 3 al Edit metodo di azione del Movies controller.

EditQueryString

Aprire il Movies controller. I due Edit metodi di azione sono illustrati di seguito.

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo HttpPost. Questo attributo specifica che l'overload del Edit metodo può essere richiamato solo per le richieste POST. È possibile applicare l'attributo HttpGet al primo metodo di modifica, ma non è necessario perché è l'impostazione predefinita. Si farà riferimento ai metodi di azione assegnati in modo implicito all'attributo HttpGet come HttpGet metodi. L'attributo Bind è un altro importante meccanismo di sicurezza che impedisce agli hacker di effettuare l'over-posting dei dati nel modello. È consigliabile includere solo le proprietà nell'attributo bind che si desidera modificare. È possibile leggere informazioni sull'overposting e sull'attributo bind nella nota di sicurezza di overposting. Nel modello semplice usato in questa esercitazione verrà eseguito il binding di tutti i dati nel modello. L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta ed è associato @Html.AntiForgeryToken() al file di visualizzazione di modifica (Views\Movies\Edit.cshtml), una parte è illustrata di seguito:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

@Html.AntiForgeryToken() genera un token anti-falso modulo nascosto che deve corrispondere al Edit metodo del Movies controller. Per altre informazioni sulla richiesta intersito, nota anche come XSRF o CSRF, vedere l'esercitazione sulla prevenzione XSRF/CSRF in MVC.

Il HttpGet Edit metodo accetta il parametro ID filmato, cerca il film usando il metodo Entity Framework Find e restituisce il filmato selezionato alla visualizzazione Modifica. Se non è possibile trovare un film, viene restituito HttpNotFound . Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e il codice creato per eseguire il rendering degli elementi <label> e <input> per ogni proprietà della classe. L'esempio seguente illustra la vista Edit (Modifica) generata dal sistema di scaffolding di Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Si noti che il modello di visualizzazione dispone di un'istruzione @model MvcMovie.Models.Movie all'inizio del file, che specifica che la vista prevede che il modello per il modello di visualizzazione sia di tipo Movie.

Il codice con scaffolding usa diversi metodi helper per semplificare il markup HTML. L'helper Html.LabelFor visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'helper esegue il Html.EditorFor rendering di un elemento HTML <input> . L'helper Html.ValidationMessageFor visualizza tutti i messaggi di convalida associati a tale proprietà.

Eseguire l'applicazione e passare all'URL /Movies . Fare clic su un collegamento Edit (Modifica). Nel browser visualizzare l'origine per la pagina. Di seguito è riportato il codice HTML per l'elemento del modulo.

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <legend>Movie</legend>

      <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

      <div class="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="7.99" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

Gli <input> elementi si trovano in un elemento HTML <form> il cui action attributo è impostato per pubblicare l'URL /Movies/Edit . I dati del modulo verranno inseriti nel server quando si fa clic sul pulsante Salva . La seconda riga mostra il token XSRF nascosto generato dalla @Html.AntiForgeryToken() chiamata.

Elaborazione della richiesta POST

Nell'elenco seguente viene indicata la versione HttpPost del metodo di azione Edit.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

L'attributo ValidateAntiForgeryToken convalida il token XSRF generato dalla @Html.AntiForgeryToken() chiamata nella visualizzazione.

Lo strumento di associazione di modelli MVC ASP.NET accetta i valori del modulo pubblicati e crea un Movie oggetto passato come movie parametro. ModelState.IsValid Verifica che i dati inviati nel modulo possano essere utilizzati per modificare (modificare o aggiornare) un Movie oggetto . Se i dati sono validi, i dati dei film vengono salvati nella Movies raccolta dell'istanza db(MovieDBContext ). I nuovi dati relativi ai film vengono salvati nel database chiamando il SaveChanges metodo di MovieDBContext. Dopo avere salvato i dati, il codice reindirizza l'utente al metodo di azione Index della classe MoviesController, che visualizza la raccolta di film, incluse le modifiche appena apportate.

Non appena la convalida lato client determina il valore di un campo non è valido, viene visualizzato un messaggio di errore. Se JavaScript è disabilitato, la convalida lato client è disabilitata. Tuttavia, il server rileva che i valori inviati non sono validi e i valori del modulo vengono riprodotti con messaggi di errore.

La convalida viene esaminata in modo più dettagliato più avanti nell'esercitazione.

Gli Html.ValidationMessageFor helper nel modello di visualizzazione Edit.cshtml si occupano della visualizzazione dei messaggi di errore appropriati.

abcNotValid

Tutti i HttpGet metodi seguono un modello simile. Ottengono un oggetto filmato (o un elenco di oggetti, nel caso di Index) e passano il modello alla visualizzazione. Il Create metodo passa un oggetto filmato vuoto alla visualizzazione Create. Tutti i metodi che creano, modificano, eliminano o cambiano in altro modo i dati, eseguono questa operazione nell'overload HttpPost del metodo. La modifica dei dati in un metodo HTTP GET è un rischio per la sicurezza. La modifica dei dati in un metodo GET viola anche le procedure consigliate HTTP e il modello REST dell'architettura, che specifica che le richieste GET non devono modificare lo stato dell'applicazione. In altre parole, l'esecuzione di un'operazione GET deve essere sicura, senza effetti collaterali e non modificare dati persistenti.

Convalida di jQuery per impostazioni locali non in lingua inglese

Se si usa un computer statunitense, è possibile ignorare questa sezione e passare all'esercitazione successiva. È possibile scaricare la versione Globalize di questa esercitazione qui. Per un'eccellente esercitazione in due parti sull'internazionalizzazione, vedere l'ASP.NET MVC 5 Internationalization di Nadeem.

Nota

per supportare la convalida di jQuery per impostazioni locali non in lingua inglese che usano una virgola (",") per un separatore decimale e per i formati di data non inglese degli Stati Uniti, è necessario includere globalize.js e i file di impostazioni cultura/globalize.cultures.js specifici (da https://github.com/jquery/globalize ) e JavaScript per usare Globalize.parseFloat. È possibile ottenere la convalida jQuery non in lingua inglese da NuGet. Non installare Globalize se si usano impostazioni locali in inglese.

  1. Scegliere Gestione pacchetti NuGet dal menu Strumenti e quindi fare clic su Gestisci pacchetti NuGet per la soluzione.

    Screenshot del menu Strumenti per avviare la convalida di jQuery per impostazioni locali non in lingua inglese.

  2. Nel riquadro sinistro selezionare Sfoglia*.*(Vedere l'immagine seguente.

  3. Nella casella di input immettere Globalize*.

    Screenshot della casella di input per immettere Globalize.

    Scegliere jQuery.Validation.Globalize, scegliere MvcMovie e fare clic su Installa. Il file Scripts\jquery.globalize\globalize.js verrà aggiunto al progetto. La cartella *Scripts\jquery.globalize\culture* conterrà molti file JavaScript delle impostazioni cultura. Si noti che l'installazione del pacchetto può richiedere cinque minuti.

    Il codice seguente mostra le modifiche apportate al file Views\Movies\Edit.cshtml:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Use the Globalization plugin to parse the value
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "yyyy-MM-dd");
    }
</script>
}

Per evitare di ripetere questo codice in ogni visualizzazione Modifica, è possibile spostarlo nel file di layout. Per ottimizzare il download dello script, vedere l'esercitazione Bundling e Minification.

Per altre informazioni, vedere ASP.NET MVC 3 Internationalization and ASP.NET MVC 3 Internationalization - Part 2 (NerdDinner).For more information see ASP.NET MVC 3 Internationalization and ASP.NET MVC 3 Internationalization - Part 2 (NerdDinner).

Come correzione temporanea, se non è possibile ottenere la convalida nelle impostazioni locali, è possibile forzare il computer a usare la lingua inglese degli Stati Uniti oppure disabilitare JavaScript nel browser. Per forzare l'uso dell'inglese negli Stati Uniti, è possibile aggiungere l'elemento globalization al file web.config radice dei progetti. Il codice seguente mostra l'elemento globalization con le impostazioni cultura impostate su Stati Uniti inglese.

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

Nell'esercitazione successiva verrà implementata la funzionalità di ricerca.