Parte 6, metodi e visualizzazioni del controller in ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Visualizzazione indice: la data di rilascio è una parola (senza spazio).

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
}

DataAnnotations sono spiegate 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 (Date).

Usando le annotazioni dei dati precedenti, eseguire l'applicazione e aggiornare la pagina /Movies. Poiché il markup della visualizzazione usa i metodi @Html.DisplayNameFor e @Html.DisplayFor per eseguire il rendering del nome e del valore della proprietà, la Index visualizzazione aggiornata mostra tutti i campi formattati correttamente. Ad esempio, data di rilascio è ora due parole.

Vista indice: la data di rilascio consiste di due parole.

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il puntatore del mouse sul collegamento Modifica e un URL di collegamento visualizzato nella finestra.

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Una delle nuove funzionalità più note di ASP.NET Core è rappresentata dai Tag Helpers. Per altre informazioni, vedere Contenuto correlato.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Visualizzazione modifica: un'eccezione per il valore di prezzo non corretto

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Vista Index: Release Date è un'unica parola (senza spazi) e la data di rilascio di ogni film indica l'ora nel formato 12 AM

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
}

DataAnnotations sono spiegate 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 (Date) e quindi non vengono visualizzate le informazioni sull'ora archiviate nel campo.

Usando le annotazioni dei dati precedenti, eseguire l'applicazione e aggiornare la pagina /Movies. Poiché il markup della visualizzazione usa i metodi @Html.DisplayNameFor e @Html.DisplayFor per eseguire il rendering del nome e del valore della proprietà, la Index visualizzazione aggiornata mostra tutti i campi formattati correttamente. Ad esempio, data di rilascio è ora due parole e le informazioni sull'ora non vengono più visualizzate.

Visualizzazione indice:

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il passaggio del mouse sul collegamento Edit (Modifica) e un URL di collegamento di https://localhost:5001/Movies/Edit/5

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Una delle nuove funzionalità più note di ASP.NET Core è rappresentata dai Tag Helpers. Per altre informazioni, vedere Contenuto correlato.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Modifica visualizzazione: eccezione per un valore di prezzo non corretto di abc indica che il campo Prezzo deve essere un numero. Eccezione per un valore di data di rilascio non corretto di xyz indica: Immettere una data valida.

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Vista Index: Release Date è un'unica parola (senza spazi) e la data di rilascio di ogni film indica l'ora nel formato 12 AM

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
}

DataAnnotations sono spiegate 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 (Date) e quindi non vengono visualizzate le informazioni sull'ora archiviate nel campo.

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il passaggio del mouse sul collegamento Edit (Modifica) e un URL di collegamento di https://localhost:5001/Movies/Edit/5

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Una delle nuove funzionalità più note di ASP.NET Core è rappresentata dai Tag Helpers. Per altre informazioni, vedere Contenuto correlato.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Modifica visualizzazione: eccezione per un valore di prezzo non corretto di abc indica che il campo Prezzo deve essere un numero. Eccezione per un valore di data di rilascio non corretto di xyz indica: Immettere una data valida.

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Vista Index: Release Date è un'unica parola (senza spazi) e la data di rilascio di ogni film indica l'ora nel formato 12 AM

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
}

DataAnnotations sono spiegate 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 (Date) e quindi non vengono visualizzate le informazioni sull'ora archiviate nel campo.

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il passaggio del mouse sul collegamento Edit (Modifica) e un URL di collegamento di https://localhost:5001/Movies/Edit/5

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Una delle nuove funzionalità più note di ASP.NET Core è rappresentata dai Tag Helpers. Per altre informazioni, vedere Contenuto correlato.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Modifica visualizzazione: eccezione per un valore di prezzo non corretto di abc indica che il campo Prezzo deve essere un numero. Eccezione per un valore di data di rilascio non corretto di xyz indica: Immettere una data valida.

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Vista Index: Release Date è un'unica parola (senza spazi) e la data di rilascio di ogni film indica l'ora nel formato 12 AM

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
    }
}

DataAnnotations sono spiegate 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 (Date) e quindi non vengono visualizzate le informazioni sull'ora archiviate nel campo.

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il passaggio del mouse sul collegamento Edit (Modifica) e un URL di collegamento di https://localhost:5001/Movies/Edit/5

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Program.cs:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Gli helper tag sono una funzionalità diffusa in ASP.NET Core. Per ulteriori informazioni su di essi, vedere Contenuti correlati.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Modifica visualizzazione: eccezione per un valore di prezzo non corretto di abc indica che il campo Prezzo deve essere un numero. Eccezione per un valore di data di rilascio non corretto di xyz indica: Immettere una data valida.

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.

Abbiamo una buona base per l'app per i film, ma la presentazione non è ottimale, ad esempio ReleaseDate dovrebbe essere scritto come due parole.

Vista Index: Release Date è un'unica parola (senza spazi) e la data di rilascio di ogni film indica l'ora nel formato 12 AM

Aprire il Models/Movie.cs file e aggiungere le righe evidenziate illustrate di seguito:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.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; }
    }
}

Tratteremo DataAnnotations nel prossimo tutorial. 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 (Date) e quindi non vengono visualizzate le informazioni sull'ora archiviate nel campo.

L'annotazione dei dati [Column(TypeName = "decimal(18, 2)")] è necessaria per consentire a Entity Framework Core di eseguire correttamente il mapping di Price alla valuta nel database. Per altre informazioni, vedere Tipi di dati.

Passare al controller Movies e posizionare il puntatore del mouse su un collegamento Edit per visualizzare l'URL di destinazione.

Finestra del browser con il passaggio del mouse sul collegamento Edit (Modifica) e un URL di collegamento di https://localhost:5001/Movies/Edit/5

I collegamenti Modifica, Dettagli e Elimina vengono generati dall'Anchor Tag Helper di Core MVC nel file Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
    </td>
</tr>

Gli helper tag consentono al codice lato server di partecipare alla creazione e al rendering di elementi HTML nei file Razor. Nel codice precedente, il valore dell'attributo AnchorTagHelper HTML href viene generato dinamicamente dal metodo di azione del controller e dall'ID di route. Usa Visualizza origine dal browser preferito o usa gli strumenti di sviluppo per esaminare il markup generato. Di seguito è riportata una parte del codice HTML generato:

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Richiama il formato per il routing impostato nel file Startup.cs:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core traduce https://localhost:5001/Movies/Edit/4 in una richiesta al metodo di azione Edit del controller Movies con il parametro Id pari a 4. I metodi del controller sono noti anche come metodi di azione.

Per altre informazioni sui Tag Helper, vedi Contenuti correlati.

Aprire il Movies controller ed esaminare i due Edit metodi di azione. Il codice seguente illustra il HTTP GET Edit metodo , che recupera il filmato e popola il modulo di modifica generato dal Edit.cshtmlRazor file .

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Il codice seguente mostra il metodo HTTP POST Edit, che elabora i valori del film inviati:

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

L'attributo [Bind] è un modo per proteggersi dall'overposting. È necessario includere solo le proprietà nell'attributo [Bind] che si vuole modificare. Per altre informazioni, vedere Proteggere il controller dall'overposting. ViewModel offre un approccio alternativo per evitare l'overposting.

Si noti che il secondo metodo di azione Edit è preceduto dall'attributo [HttpPost].

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo HttpPost specifica che questo Edit metodo può essere richiamato solo per le POST richieste. È possibile applicare l'attributo [HttpGet] al primo metodo di modifica, ma non è necessario perché l'impostazione predefinita è [HttpGet].

L'attributo ValidateAntiForgeryToken viene usato per impedire la falsificazione di una richiesta e viene associato a un token antiforgery generato nel file di visualizzazione di modifica (Views/Movies/Edit.cshtml). Il file della vista di modifica genera il token anti-contraffazione tramite Form Tag Helper.

<form asp-action="Edit">

L'Form Tag Helper genera un token antiforgery nascosto che deve corrispondere al token antiforgery [ValidateAntiForgeryToken] generato nel metodo Edit del controller Movies. Per altre informazioni, vedere Prevenire attacchi tramite richieste intersito false (XSRF/CSRF) in ASP.NET Core.

Il metodo HttpGet Edit accetta il parametro movie ID, cerca il film tramite il metodo FindAsync Entity Framework e restituisce il film selezionato nella vista Edit. Se non viene trovato un film, NotFound viene restituito (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie e ha generato il codice per visualizzare gli 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

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Si noti che il template di visualizzazione contiene un'istruzione @model MvcMovie.Models.Movie nella parte superiore del file. @model MvcMovie.Models.Movie specifica che il modello previsto dalla vista per il modello di vista sia di tipo Movie.

Il codice di scaffolding usa diversi metodi helper tag per semplificare il markup HTML. Il Label Tag Helper visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'Input Tag Helper esegue il rendering di un elemento <input> HTML. L'helper tag di convalida visualizza eventuali messaggi di convalida associati a questa proprietà.

Eseguire l'applicazione e passare all'URL /Movies. Fare clic su un link Edit. Nel browser, visualizza il codice sorgente della pagina. Il codice HTML generato per l'elemento <form> è riportato di seguito.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Gli elementi <input> sono contenuti in un elemento HTML <form>, il cui attributo action è impostato per inviare una richiesta POST all'URL /Movies/Edit/id. I dati del modulo verranno inviati al server quando si fa clic sul pulsante Save. L'ultima riga prima dell'elemento di chiusura </form> mostra il token XSRF nascosto generato dal Form Tag Helper.

Elaborazione della richiesta POST

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

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L'attributo [ValidateAntiForgeryToken] valida il token XSRF nascosto generato dal generatore di token antiforgery nel Form Tag Helper

Il sistema di binding del modello acquisisce i valori inviati del modulo e crea un oggetto Movie, che viene passato come parametro movie. La proprietà ModelState.IsValid verifica che i dati inviati tramite il modulo possano essere utilizzati per apportare modifiche (modificare o aggiornare) a un oggetto Movie. Se sono validi, i dati vengono salvati. I dati dei film aggiornati (modificati) vengono salvati nel database chiamando il metodo SaveChangesAsync del contesto di database. 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.

Prima che il modulo venga inviato al server, la convalida sul lato client controlla eventuali regole di convalida applicate ai campi. Se sono presenti errori di convalida, viene visualizzato un messaggio di errore e il modulo non viene inviato. Se JavaScript è disabilitato, non verrà eseguita la convalida sul lato client, ma il server rileverà i valori inviati non validi e i valori del modulo verranno visualizzati nuovamente con messaggi di errore. Più avanti in questa esercitazione la convalida del modello verrà esaminata in maggiore dettaglio. Il Tag Helper di convalida nel modello di visualizzazione Views/Movies/Edit.cshtml si occupa della visualizzazione dei messaggi di errore appropriati.

Modifica visualizzazione: eccezione per un valore di prezzo non corretto di abc indica che il campo Prezzo deve essere un numero. Eccezione per un valore di data di rilascio non corretto di xyz indica: Immettere una data valida.

Tutti i metodi HttpGet nel controller di film seguono un pattern simile. Ricevono un oggetto film (o un elenco di oggetti nel caso di Index) e passano l'oggetto (modello) alla vista. Il metodo Create passa un oggetto film vuoto alla vista Create. Tutti i metodi che creano, modificano, eliminano o modificano in altro modo i dati lo fanno nell'overload [HttpPost] del metodo. La modifica dei dati in un HTTP GET metodo è un rischio per la sicurezza. La modifica dei dati in un HTTP GET metodo viola anche le procedure consigliate HTTP e il modello di architettura REST , 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.