In un precedente articolo abbiamo iniziato a conoscere ASP.NET MVC, un framework disponibile con le ASP.NET 3.5 Extensions che permette di implementare nelle nostre applicazioni il paradigma Model-View-Controller, particolarmente utile per separare la logica di accesso ai dati dalla loro visualizzazione. Ci siamo tuttavia occupati soltanto di quest'ultimo aspetto costruendo un piccolo catalogo dei prodotti come applicazione di esempio.
In questo secondo articolo espanderemo le funzionalità del catalogo aggiungendo le opzioni di modifica e aggiunta di prodotti. Anche in questo caso utilizziamo nomi in inglese per funzioni e pagine, pratica che consiglio a tutti di seguire nei propri progetti per una maggiore compatibilità e facilità di lettura.
Modificare i dati
Iniziamo l'analisi dalla modifica dei dati invece che dal loro inserimento, perché questo ci consente una trattazione più approfondita delle funzionalità messe a disposizione da MVC.
Come già visto per la visualizzazione dei dati, anche l'aggiornamento fa uso degli action methods delle classi Controller per richiamare le View necessarie. In particolare per gestire lo scambio di informazioni con l'utente avremo bisogno di due metodi: il primo, che chiameremo Edit
, si occuperà di richiamare la vista incaricata di mostrare il form HTML; il secondo, SaveUpdates
, avrà invece il compito di leggere l'input inviato dall'utente e aggiornare di conseguenza il database.
Per separare ulteriormente il livello Controller dal Model aggiungiamo le seguenti funzioni di aiuto all'interno del file ExampleDataContext.cs
che contiene anche altre procedure (create nel precedente articolo) per il recupero delle categorie dei prodotti:
public Product GetProductById(int id) { return Products.Single(p => p.ID == id); } public void AddProduct(Product p) { Products.InsertOnSubmit(p); }
La prima procedura estrae un singolo prodotto dal database, mentre l'altra, che utilizzeremo in seguito, si occupa dell'inserimento di un nuovo prodotto.
Vediamo in dettaglio il primo nuovo metodo del Controller:
[ControllerAction] public void Edit(int id) { Product p = context.GetProductById(id); RenderView("EditProduct", p); }
Il funzionamento è molto semplice: il parametro ID (passato al controller attraverso un URL come il seguente:
/Products/Edit/2
contiene il codice per identificare l'articolo che vogliamo modificare. La funzione estrae quindi i dati dal database e li passa alla vista EditProduct
:
<form action="<%= Url.Action(new { Action="SaveUpdates", id=ViewData.ID }) %>" method="post"> <div> Nome: <input name="Name" value="<%= ViewData.Name %>" /></div> <div> Categoria: <input name="Name" value="<%= ViewData.CategoryID %>" /></div> <div> <input type="submit" value="Invia!" /></div> </form>
L'attributo action
del form viene generato automaticamente attraverso la funzione Url.Action
, il cui funzionamento è già stato trattato nell'articolo introduttivo- Il resto del codice è semplice markup XHTML.
Nella versione finale delle ASP.NET 3.5 Extensions sarà anche possibile utilizzare l'oggetto di aiuto Html
, specifico del framework MVC. In particolare il metodo Html.TextBox
sarà utile alla creazione di un tag input e avrà come parametri l'attributo name del tag e il suo valore di default:
<%= Html.TextBox("Name", ViewData.Name) %>
Questo e gli altri metodi dell'oggetto Html
dovrebbero, tra le altre cose, anche generare codice aderente agli standard W3C, molto più pulito e corretto rispetto all'output di controlli lato server.
Aggiungiamo alcune funzionalità
La soluzione appena vista può essere migliorata: la categoria del prodotto viene infatti visualizzata tramite una casella di testo mentre sarebbe sicuramente molto più comodo poter scegliere da una lista le categorie presenti nel database. Per ottenere questa funzionalità dobbiamo modificare leggermente il codice precedente, in particolare è necessario estrarre dal database le categorie e passarle alla View:
public void Edit(int id) { Product p = context.GetProductById(id); List<Category> c = context.GetCategories(); RenderView("EditProduct", new { product = p, categories = c }); }
Nell'esempio abbiamo utilizzato un oggetto anonimo per il passaggio dei dati. Un altro approccio, più classico, consiste invece nel creare una classe ad-hoc da utilizzare come oggetto ViewData
:
public class EditProductViewData { public Product product; public List<Category> categories; }
In questo modo avremo anche accesso alle funzionalità di completamento automatico del codice all'interno di Visual Studio. Nella nostra View sarà sufficiente utilizzare un ciclo foreach per visualizzare il campo a scelta multipla:
<div> Categoria: <select name="CategoryID"> <% foreach (var cat in ViewData.categories) { if (cat.ID != ViewData.product.ID) { %> <option value="<%= cat.ID %>"> <%= cat.Name%> </option> <% } else { %> <option value="<%= cat.ID %>" selected="selected"> <%= cat.Name%> </option> <% } } %> </select> </div>
Oppure utilizzare il metodo Html.Select
:
<%= Html.Select("CategoryID", ViewData.categories, ViewData.product.ID) %>
È interessante notare che in tutti gli esempi precedenti i nomi dei campi input sono identici alle colonne del database che rappresentano. Questa convenzione ci tornerà molto utile in previsione della versione definitiva delle ASP.NET 3.5 Extensions in quanto ci permetterà di utilizzare l'extension method UpdateFrom
:
public void SaveUpdates(int ID) { Product product = context.GetProductById(ID); product.UpdateFrom(Request.Form); context.SubmitChanges(); RedirectToAction(new { Action = "List", id = product.Category.Name.Trim() }); }
Così facendo ASP.NET MVC gestisce automaticamente la mappatura tra i dati del database e l'input dell'utente, risparmiandoci la scrittura di molto codice e aumentandone la leggibilità. Questa funzione si fa apprezzare specie nel caso in cui i campi di input siano molto numerosi.
Un'ultima osservazione: avremmo potuto anche non utilizzare il codice in-line e gestire il form con controlli lato server, effettuando il databind nel code behind della pagina. Ovviamente l'HTML di output non sarebbe stato della qualità che abbiamo ottenuto con il codice in-line.
Il metodo SaveUpdates
è strutturato invece come segue:
public void SaveUpdates(int ID, string Name, int CategoryID) { Product product = context.GetProductById(ID); product.Name = Name; product.CategoryID = CategoryID; context.SubmitChanges(); RedirectToAction(new { Action = "List", id = product.Category.Name.Trim() }); }
Dopo aver caricato dal database il prodotto che ci interessa, lo aggiorniamo e invochiamo il metodo SubmitChange per salvare le modifiche appena effettuate.
Inserimento di nuovi dati
Come già detto, l'inserimento dei dati non differisce molto dalla modifica. Le funzioni necessarie all'interno della classe Controller sono le seguenti:
public void New() { NewProductViewData data = new NewProductViewData(); data.categories = context.GetCategories(); RenderView("NewProduct", data); } public void SaveNew(string Name, int CategoryID) { Product p = new Product(); p.Name = Name; p.CategoryID = CategoryID; context.AddProduct(p); context.SubmitChanges(); RedirectToAction(new { Action = "List", id = p.Category.Name.Trim() }); }
La prima richiama la View in cui è contenuto il form, mentre la seconda salva il nuovo prodotto nel database e richiama l'azione List, mostrando la categoria in cui abbiamo appena inserito l'articolo. Il codice della View rimane praticamente invariato. Fa eccezione il tag <form>
in cui è non è più necessario utilizzare codice in-line:
<form action="/Products/SaveNew" method="post">
Il codice completo è disponibile nel file allegato.