Esame dei metodi e delle visualizzazioni delle azioni di modifica 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 fornisce molti miglioramenti su 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 del film. Ma prima di tutto si otterrà una breve deviazione per rendere la data di rilascio migliore. Aprire il file Models\Movie.cs e aggiungere le righe evidenziate riportate 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 rendere specifiche le impostazioni cultura date 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 dei 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 in modo errato 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 visualizzazione 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 dei collegamenti ipertestuali HTML che si collegano ai metodi di azione nei controller. Il primo argomento del ActionLink metodo è il testo del collegamento da eseguire per il 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 di route (in questo caso l'ID 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 converte http://localhost:1234/Movies/Edit/4 in una richiesta il 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 metodo controller e azione corretto e specificare il parametro ID facoltativo. Il metodo MapRoute viene usato anche dai HtmlHelpers , ad esempio ActionLink per generare URL specificati dal controller, dal metodo di azione e da qualsiasi 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 i 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 3 al Edit metodo di azione del Movies controller.

EditQueryString

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

// 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é è il valore predefinito. Si farà riferimento a metodi di azione assegnati implicitamente all'attributo HttpGet come HttpGet metodi. L'attributo Bind è un altro meccanismo di sicurezza importante che impedisce agli hacker di eseguire la sovra-registrazione dei dati al modello. È consigliabile includere solo le proprietà nell'attributo di associazione che si desidera modificare. È possibile leggere l'overposting e l'attributo bind nella nota di sicurezza sovraposto. Nel modello semplice usato in questa esercitazione verranno associati tutti i dati nel modello. L'attributo ValidateAntiForgeryToken viene usato per impedire la maschera di una richiesta e viene associato @Html.AntiForgeryToken() al file di visualizzazione di modifica (Views\Movies\Edit.cshtml), viene illustrata una parte seguente:

@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-forgery nascosto che deve corrispondere al Edit metodo del Movies controller. È possibile leggere altre informazioni sulla richiesta tra siti (nota anche come XSRF o CSRF) nell'esercitazione XSRF/CSRF Prevention in MVC.

Il HttpGetEdit metodo accetta il parametro ID filmato, cerca il film usando il metodo Entity Framework Find e restituisce il filmato selezionato nella visualizzazione Modifica. Se non è possibile trovare un film, HttpNotFound viene restituito. 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 come il modello di visualizzazione dispone di un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file, che specifica che la visualizzazione prevede che il modello di visualizzazione sia di tipo Movie.

Il codice scaffolded 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 Modifica . Nel browser visualizzare l'origine per la pagina. Di seguito è riportato il codice HTML per l'elemento form.

<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 su post sull'URL /Movies/Edit . I dati del modulo verranno pubblicati nel server quando viene fatto 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.

Il binding del modello 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 usati per modificare (modificare o aggiornare) un Movie oggetto. Se i dati sono validi, i dati del film vengono salvati nella Movies raccolta dell'istanza db(MovieDBContext ). I nuovi dati del 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 pubblicati non sono validi e i valori del modulo vengono rieseguiti con i 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. Ottiene un oggetto film (o un elenco di oggetti, nel caso di Index) e passa il modello alla visualizzazione. Il Create metodo passa un oggetto film 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 di sicurezza, come descritto nella voce del post di blog ASP.NET MVC Tip #46 - Non usare i collegamenti di eliminazione perché creano fori di 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 jQuery per le impostazioni locali non inglesi

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

Nota

per supportare la convalida jQuery per le impostazioni locali non inglesi che usano una virgola (",") per un punto decimale e non US-English formati di data, è necessario includere globalize.js e i file di impostazioni cultura /globalize.cultures.js specifici(da https://github.com/jquery/globalize ) e JavaScript da usare Globalize.parseFloat. È possibile ottenere la convalida jQuery non inglese da NuGet. Non installare Globalize se si usa un'impostazione locale inglese.

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

    Screenshot del menu Strumenti per avviare la convalida jQuery per impostazioni locali non inglesi.

  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 , scegliere jQuery.Validation.GlobalizeMvcMovie e fare clic su Installa. Il file Scripts\jquery.globalize\globalize.js verrà aggiunto al progetto. La cartella *Script\jquery.globalize\culture* conterrà molti file JavaScript cultura. Nota, potrebbe richiedere cinque minuti per installare questo pacchetto.

    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 di 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 e ASP.NET MVC 3 Internationalization - Parte 2 (NerdDinner).

Come correzione temporanea, se non è possibile ottenere la convalida nelle impostazioni locali, è possibile forzare il computer a usare l'inglese US oppure disabilitare JavaScript nel browser. Per forzare l'uso dell'inglese statunitense, è possibile aggiungere l'elemento di globalizzazione al file radice del progettoweb.config . Il codice seguente mostra l'elemento di globalizzazione 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.