Esame dei metodi di azione e delle visualizzazioni per il controller di film
Nota
Una versione aggiornata di questa esercitazione è disponibile qui che usa ASP.NET MVC 5 e Visual Studio 2013. È più sicuro, molto più semplice da seguire e dimostra più funzionalità.
In questa sezione verranno esaminati i metodi di azione generati e le visualizzazioni per il controller di film. Si aggiungerà quindi una pagina di ricerca personalizzata.
Eseguire l'applicazione e passare al Movies
controller aggiungendo /Movies all'URL nella barra degli indirizzi del browser. Tenere premuto il puntatore del mouse su un collegamento Modifica per visualizzare l'URL a cui si collega.
Il collegamento Edit è stato generato dal Html.ActionLink
metodo nella vista Views\Movies\Index.cshtml :
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
L'oggetto Html
è un helper esposto usando una proprietà nella classe di base System.Web.Mvc.WebViewPage . Il ActionLink
metodo dell'helper semplifica la generazione dinamica di collegamenti ipertestuali HTML che si collegano ai metodi di azione nei controller. Il primo argomento del metodo è il testo del collegamento di cui eseguire il ActionLink
rendering, ad esempio <a>Edit Me</a>
. Il secondo argomento è il nome del metodo di azione da richiamare. L'argomento finale è un oggetto anonimo che genera i dati della route (in questo caso, l'ID di 4).
Il collegamento generato visualizzato nell'immagine precedente è http://localhost:xxxxx/Movies/Edit/4
. La route predefinita (stabilita in App_Start\RouteConfig.cs) accetta il modello {controller}/{action}/{id}
url . Pertanto, ASP.NET si traduce http://localhost:xxxxx/Movies/Edit/4
in una richiesta al Edit
metodo di azione del Movies
controller con il parametro ID
uguale a 4. Esaminare il codice seguente dal file App_Start\RouteConfig.cs .
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
È anche possibile passare parametri del metodo di azione usando una stringa di query. Ad esempio, l'URL http://localhost:xxxxx/Movies/Edit?ID=4
passa anche il parametro ID
4 al Edit
metodo di azione del Movies
controller.
Aprire il Movies
controller. I due Edit
metodi di azione sono illustrati di seguito.
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Si noti che il secondo metodo di azione Edit
è preceduto dall'attributo HttpPost
. Questo attributo specifica che l'overload del Edit
metodo può essere richiamato solo per le richieste POST. È possibile applicare l'attributo HttpGet
al primo metodo di modifica, ma non è necessario perché è l'impostazione predefinita. Si farà riferimento ai metodi di azione assegnati in modo implicito all'attributo HttpGet
come HttpGet
metodi.
Il HttpGet
Edit
metodo accetta il parametro ID filmato, cerca il film usando il metodo Entity Framework Find
e restituisce il filmato selezionato alla visualizzazione Modifica. Il parametro ID specifica un valore predefinito pari a zero se il Edit
metodo viene chiamato senza un parametro. Se non è possibile trovare un film, viene restituito HttpNotFound . Quando il sistema di scaffolding ha creato la vista Edit, ha esaminato la classe Movie
e il codice creato per eseguire il rendering degli elementi <label>
e <input>
per ogni proprietà della classe. L'esempio seguente mostra la visualizzazione Edit generata:
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Si noti che il modello di visualizzazione dispone di un'istruzione @model MvcMovie.Models.Movie
all'inizio del file, che specifica che la vista prevede che il modello per il modello di visualizzazione sia di tipo Movie
.
Il codice con scaffolding usa diversi metodi helper per semplificare il markup HTML. L'helper Html.LabelFor
visualizza il nome del campo ("Title", "ReleaseDate", "Genre" o "Price"). L'helper esegue il Html.EditorFor
rendering di un elemento HTML <input>
. L'helper Html.ValidationMessageFor
visualizza tutti i messaggi di convalida associati a tale proprietà.
Eseguire l'applicazione e passare all'URL /Movies . Fare clic su un collegamento Edit (Modifica). Nel browser visualizzare l'origine per la pagina. Di seguito è riportato il codice HTML per l'elemento del modulo.
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
Gli <input>
elementi si trovano in un elemento HTML <form>
il cui action
attributo è impostato per pubblicare l'URL /Movies/Edit . I dati del modulo verranno inseriti nel server quando si fa clic sul pulsante Modifica .
Elaborazione della richiesta POST
Nell'elenco seguente viene indicata la versione HttpPost
del metodo di azione Edit
.
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Lo strumento di associazione di modelli MVC ASP.NET accetta i valori del modulo pubblicati e crea un Movie
oggetto passato come movie
parametro. Il metodo ModelState.IsValid
verifica che i dati inviati nel formato possano essere usati per cambiare (modificare o aggiornare) un oggetto Movie
. Se i dati sono validi, i dati del film vengono salvati nella Movies
raccolta dell'istanza db(MovieDBContext
di . I nuovi dati relativi ai film vengono salvati nel database chiamando il SaveChanges
metodo di MovieDBContext
. Dopo aver salvato i dati, il codice reindirizza l'utente al Index
metodo di azione della MoviesController
classe , che visualizza l'oggetto della raccolta di film, incluse le modifiche appena apportate.
Se i valori pubblicati non sono validi, vengono riprodotti nel formato. Gli Html.ValidationMessageFor
helper nel modello di visualizzazione Edit.cshtml si occupano della visualizzazione dei messaggi di errore appropriati.
Nota
per supportare la convalida di jQuery per impostazioni locali non in lingua inglese che usano una virgola (",") per un separatore decimale, è necessario includere globalize.js e i file di impostazioni cultura/globalize.cultures.js specifici (da https://github.com/jquery/globalize ) e JavaScript per usare Globalize.parseFloat
. Il codice seguente mostra le modifiche apportate al file Views\Movies\Edit.cshtml per l'uso delle impostazioni cultura "fr-FR":
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
Il campo decimale può richiedere una virgola, non un separatore decimale. Come correzione temporanea, è possibile aggiungere l'elemento globalization al file web.config radice dei progetti. Il codice seguente mostra l'elemento globalization con le impostazioni cultura impostate su Stati Uniti inglese.
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
Tutti i HttpGet
metodi seguono un modello simile. Ottengono un oggetto filmato (o un elenco di oggetti, nel caso di Index
) e passano il modello alla visualizzazione. Il Create
metodo passa un oggetto filmato vuoto alla visualizzazione Create. Tutti i metodi che creano, modificano, eliminano o cambiano in altro modo i dati, eseguono questa operazione nell'overload HttpPost
del metodo. La modifica dei dati in un metodo HTTP GET è un rischio per la sicurezza. La modifica dei dati in un metodo GET viola anche le procedure consigliate HTTP e il modello REST dell'architettura, che specifica che le richieste GET non devono modificare lo stato dell'applicazione. In altre parole, l'esecuzione di un'operazione GET deve essere sicura, senza effetti collaterali e non modificare dati persistenti.
Aggiunta di un metodo di ricerca e di una visualizzazione di ricerca
In questa sezione si aggiungerà un SearchIndex
metodo di azione che consente di cercare film in base al genere o al nome. Sarà disponibile usando l'URL /Movies/SearchIndex . La richiesta visualizzerà un modulo HTML che contiene elementi di input che un utente può immettere per cercare un filmato. Quando un utente invia il modulo, il metodo di azione otterrà i valori di ricerca pubblicati dall'utente e userà i valori per eseguire ricerche nel database.
Visualizzazione del modulo SearchIndex
Per iniziare, aggiungere un SearchIndex
metodo di azione alla classe esistente MoviesController
. Il metodo restituirà una visualizzazione contenente un modulo HTML. Ecco il codice:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
La prima riga del SearchIndex
metodo crea la query LINQ seguente per selezionare i film:
var movies = from m in db.Movies
select m;
La query viene definita a questo punto, ma non è ancora stata eseguita nell'archivio dati.
Se il searchString
parametro contiene una stringa, la query movies viene modificata per filtrare il valore della stringa di ricerca, usando il codice seguente:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Il codice s => s.Title
precedente è un'espressione lambda. Le espressioni lambda vengono usate nelle query LINQ basate su metodo come argomenti per metodi dell'operatore di query standard, ad esempio il metodo Where usato nel codice precedente. Le query LINQ non vengono eseguite quando vengono definite o quando vengono modificate chiamando un metodo come Where
o OrderBy
. Al contrario, l'esecuzione della query viene posticipata, il che significa che la valutazione di un'espressione viene ritardata fino a quando il valore realizzato non viene effettivamente iterato o viene chiamato il ToList
metodo . Nell'esempio SearchIndex
la query viene eseguita nella vista SearchIndex. Per altre informazioni sull'esecuzione posticipata di query, vedere Esecuzione di query.
È ora possibile implementare la SearchIndex
visualizzazione che visualizzerà il modulo all'utente. Fare clic con il pulsante destro del mouse all'interno del SearchIndex
metodo e quindi scegliere Aggiungi visualizzazione. Nella finestra di dialogo Aggiungi visualizzazione specificare che si passerà un Movie
oggetto al modello di visualizzazione come classe del modello di modello. Nell'elenco dei modelli di scaffolding scegliere Elenco, quindi fare clic su Aggiungi.
Quando si fa clic sul pulsante Aggiungi , viene creato il modello di visualizzazione Views\Movies\SearchIndex.cshtml . Poiché è stato selezionato Elenco nell'elenco dei modelli di Scaffolding, Visual Studio ha generato automaticamente (scaffolded) un markup predefinito nella visualizzazione. Lo scaffolding ha creato un modulo HTML. Ha esaminato la classe e creato il codice per eseguire il Movie
rendering <label>
degli elementi per ogni proprietà della classe . L'elenco seguente mostra la visualizzazione Crea generata:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Eseguire l'applicazione e passare a /Movies/SearchIndex. Accodare una stringa di query, ad esempio ?searchString=ghost
, all'URL. Vengono visualizzati i film filtrati.
Se si modifica la firma del SearchIndex
metodo in modo che abbia un parametro denominato id
, il id
parametro corrisponderà al {id}
segnaposto per le route predefinite impostate nel file Global.asax .
{controller}/{action}/{id}
Il metodo originale SearchIndex
è simile al seguente:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Il metodo modificato SearchIndex
sarà simile al seguente:
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
È ora possibile passare il titolo della ricerca come dati di route (un segmento di URL), anziché come valore della stringa di query.
Tuttavia, non è possibile supporre che gli utenti modifichino l'URL ogni volta che desiderano cercare un film. A questo punto si aggiungerà l'interfaccia utente per aiutarli a filtrare i film. Se è stata modificata la firma del SearchIndex
metodo per verificare come passare il parametro ID associato a route, modificarla di nuovo in modo che il SearchIndex
metodo prenda un parametro stringa denominato searchString
:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Aprire il file Views\Movies\SearchIndex.cshtml e, subito dopo @Html.ActionLink("Create New", "Create")
, aggiungere quanto segue:
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
L'esempio seguente mostra una parte del file Views\Movies\SearchIndex.cshtml con il markup di filtro aggiunto.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
L'helper Html.BeginForm
crea un tag di apertura <form>
. L'helper Html.BeginForm
fa in modo che il modulo invii se stesso quando l'utente invia il modulo facendo clic sul pulsante Filtro .
Eseguire l'applicazione e provare a cercare un film.
Non esiste alcun HttpPost
overload del SearchIndex
metodo. Non è necessario, perché il metodo non modifica lo stato dell'applicazione, filtrando semplicemente i dati.
È possibile aggiungere il metodo HttpPost SearchIndex
seguente. In tal caso, il invoker azione corrisponde al HttpPost SearchIndex
metodo e il HttpPost SearchIndex
metodo verrebbe eseguito come illustrato nell'immagine seguente.
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
Tuttavia, anche se si aggiunge questa versione HttpPost
del metodo SearchIndex
, esiste una limitazione sul modo sulla relativa implementazione. Si supponga che si desideri usare come segnalibro una ricerca specifica o inviare un collegamento agli amici su cui possono fare clic per visualizzare lo stesso elenco filtrato di film. Si noti che l'URL per la richiesta HTTP POST è uguale all'URL per la richiesta GET (localhost:xxxxx/Movies/SearchIndex). Non sono presenti informazioni di ricerca nell'URL stesso. Al momento, le informazioni sulla stringa di ricerca vengono inviate al server come valore del campo modulo. Ciò significa che non è possibile acquisire le informazioni di ricerca nel segnalibro o inviarle agli amici in un URL.
La soluzione consiste nell'usare un overload di BeginForm
che specifica che la richiesta POST deve aggiungere le informazioni di ricerca all'URL e che deve essere instradata alla versione HttpGet del SearchIndex
metodo . Sostituire il metodo senza parametri BeginForm
esistente con il codice seguente:
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
Ora quando si invia una ricerca, l'URL contiene una stringa di query di ricerca. La ricerca passerà anche al metodo di azione HttpGet SearchIndex
, anche se si dispone di un metodo HttpPost SearchIndex
.
Aggiunta della ricerca per genere
Se è stata aggiunta la HttpPost
versione del SearchIndex
metodo, eliminarla ora.
Successivamente, si aggiungerà una funzionalità per consentire agli utenti di cercare film per genere. Sostituire il metodo SearchIndex
con il codice seguente:
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
Questa versione del SearchIndex
metodo accetta un parametro aggiuntivo, ovvero movieGenre
. Le prime righe di codice creano un List
oggetto per contenere generi di film dal database.
Il codice seguente è una query LINQ che recupera tutti i generi dal database.
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
Il codice usa il AddRange
metodo della raccolta generica List
per aggiungere tutti i generi distinti all'elenco. (Senza il Distinct
modificatore, verranno aggiunti generi duplicati, ad esempio, la commedia verrebbe aggiunta due volte nell'esempio). Il codice archivia quindi l'elenco dei generi nell'oggetto ViewBag
.
Nel codice seguente viene illustrato come controllare il movieGenre
parametro . Se non è vuoto, il codice vincola ulteriormente la query film per limitare i film selezionati al genere specificato.
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
Aggiunta di markup alla visualizzazione SearchIndex per supportare la ricerca per genere
Aggiungere un Html.DropDownList
helper al file Views\Movies\SearchIndex.cshtml , subito prima dell'helper TextBox
. Il markup completato è illustrato di seguito:
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
Eseguire l'applicazione e passare a /Movies/SearchIndex. Provare una ricerca per genere, per nome del film e per entrambi i criteri.
In questa sezione sono stati esaminati i metodi di azione CRUD e le viste generate dal framework. È stato creato un metodo di azione di ricerca e una visualizzazione che consente agli utenti di cercare in base al titolo e al genere del film. Nella sezione successiva verrà illustrato come aggiungere una proprietà al Movie
modello e come aggiungere un inizializzatore che creerà automaticamente un database di test.