In questo articolo tratteremo le principali novità della seconda versione di ASP.NET MVC, il framework Microsoft dedicato alla creazione di applicazioni basate sul paradigma Model-View-Controller. La versione attualmente disponibile è la Preview Release 2, rilasciata nei primi giorni di Ottobre, mentre la versione completa sarà parte integrante della release 4.0 del framework .Net attesa per il prossimo anno.
Prima di continuare è necessario, se non state lavorando con Visual Studio 2010 Beta 2 che include già ASP.Net MVC 2, scaricare la Asp.Net MVC 2 Preview Release 2 dal sito Microsoft, dove è disponibile anche il codice sorgente.
Se avete Visual Studio 2008 (o l'equivalente Express Edition) installato sul sistema, il pacchetto di installazione aggiungerà automaticamente anche i nuovi template necessari alla creazione dei progetti. L'installazione non è compatibile con Visual Studio 2010 Beta 1.
Per illustrare meglio le funzionalità di cui ci andiamo ad occupare sono disponibili due progetti di esempio che vi invito caldamente a scaricare in modo da avere un riferimento aggiuntivo.
Validazione tramite Data Annotations
Nonostante sia ancora all'inizio del suo ciclo di sviluppo, ASP.Net MVC 2 include molte nuove funzionalità. La prima di cui ci occupiamo è il supporto ai Data Annotations.
Data Annotations è un sistema incluso nel framework .Net (a partire dalla versione 3.5 SP 1) ed utilizzato anche da ASP.Net Dynamic Data che permette di inserire all'interno del Model regole di validazione ed altre informazioni sui dati attraverso l'uso di semplici attributi.
Vediamo subito un esempio: nel seguente listato è riportato il codice di una classe User
che contiene informazioni su un utente. Ad ogni proprietà sono stati aggiunti degli attributi che stabiliscono delle regole di validazione. Per i nostri esempi utilizziamo una semplice classe User
:
public class User
{
const string EmailRegEx = @"^([0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-w]*[0-9a-zA-Z].)+[a-zA-Z]{2,9})$";
[Required(ErrorMessage = "Nome richiesto.")]
public string Name { get; set; }
[Required(ErrorMessage = "Email richiesta.")]
[RegularExpression(EmailRegEx, ErrorMessage = "Email non valida.")]
public string Email { get; set; }
[Range(1, 150, ErrorMessage = "Età non valida.")]
public int Age { get; set; }
[Range(1, 4, ErrorMessage = "Nazionalità non valida.")]
public int CountryId { get; set; }
public bool IsApproved { get; set; }
}
L'attributo Required
ad esempio marca una proprietà come richiesta e genera un'eccezione nel caso in cui la proprietà abbia valore nullo; Range
limita i valori consentiti di un campo numerico tra due estremi e così via. Per una lista completa degli attributi disponibili vi rimando alla documentazione MSDN.
Proviamo subito le nuove funzionalità creando un controller chiamato UsersController
. Al suo interno implementiamo i due metodi per gestire un form di creazione utente. Per maggiori informazioni sulla gestione dei form in ASP.Net MVC vi consiglio di consultare la Guida ASP.NET MVC.
Ecco una azione dedicata alla creazione di un nuovo utente:
// GET: /Users/Create
public ActionResult Create()
{
return View();
}
Creiamo quindi la vista corrispondente.
<%= Html.ValidationSummary("Si sono verificati degli errori.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Email">Email:</label>
<%= Html.TextBox("Email") %>
<%= Html.ValidationMessage("Email", "*") %>
</p>
<p>
<label for="Age">Age:</label>
<%= Html.TextBox("Age") %>
<%= Html.ValidationMessage("Age", "*") %>
</p>
<p>
<label for="Age">CountryId:</label>
<%= Html.TextBox("CountryId")%>
<%= Html.ValidationMessage("CountryId", "*")%>
</p>
<p>
<label for="IsApproved">IsApproved:</label>
<%= Html.CheckBox("IsApproved") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Passiamo adesso all'azione relativa al POST
. Saremo in grado di verificare se i dati sono corretti semplicemente controllando la proprietà ModelState.IsValid
. Sarà ASP.Net a preoccuparsi di validare le proprietà dell'utente secondo gli attributi impostati.
// POST: /Users/Create
[HttpPost]
public ActionResult Create(User user)
{
if (!ModelState.IsValid)
return View(user); // I dati non sono validi, mostro nuovamente il form
// Aggiorno il database...
return View("UserCreated");
}
Se proviamo ad inserire valori non corretti otteniamo il seguente risultato:
Validazione lato client con JQuery
Continuando a parlare di validazione dell'input è particolarmente interessante la scelta di inserire all'interno di ASP.Net MVC la libreria Javascript JQuery Validate per la validazione lato client.
Per utilizzare questa funzionalità è sufficiente inserire i riferimenti javascript necessari all'interno della pagina e richiamare la funzione Html.EnableClientValidation()
direttamente all'interno della vista.
<script src="/Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcJQueryValidation.js" type="text/javascript"></script>
<% Html.EnableClientValidation(); %>
Il file MicrosoftMvcJQueryValidation.js
è un wrapper attorno alla libreria JQuery Validate utilizzato per richiamare il codice di validazione inserito nella pagina dalla funzione EnableClientValidation
. Se proviamo nuovamente ad inviare il form contenente degli errori otteniamo, senza nessun POST della pagina, il seguente risultato:
Helper fortemente tipizzati
Un'altra parte del framework MVC su cui si è concentrata l'attenzione degli sviluppatori è quella relativa agli helper HTML (Un'analisi completa degli helper di ASP.Net MVC la troviamo nella nostra guida).
Uno dei problemi principali degli helper HTML era legato alla scarsa tipizzazione dei dati, per cui gli errori non erano visibili in fase di compilazione e dovevano essere gestiti a runtime. In caso di refactoring del codice, inoltre, si rendeva necessario aggiornare manualmente i valori passati agli helper.
Nel listato che segue è riportato il codice usato in MVC 1.0 per generare un campo di input relativo alla proprietà Name
della classe User
vista poco fa.
<!-- Esempio di helper HTML di ASP.Net MVC 1.0 -->
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
Lo stesso codice può essere riscritto in ASP.Net MVC 2.0 utilizzando la seguente sintassi:
<!-- Esempio di helper HTML tipizzato in ASP.Net MVC 2.0 -->
<%= Html.LabelFor(user => user.Name) %>
<%= Html.EditorFor(user => user.Name) %>
<%= Html.ValidationMessageFor(user => user.Name) %>
Nel secondo snippet, utilizziamo una funzione LINQ all'interno dei nuovi extension methods LabelFor
, EditorFor
e ValidationMessageFor
per generare rispettivamente il tag HTML <label>
, il campo <input>
e gli eventuali messaggi di validazione.
EditorFor
si preoccupa automaticamente di stabilire il tipo di campo necessario a seconda del tipo di dato: avremo quindi una casella di testo per le stringhe, una checkbox per valori booleani e così via.
È possibile personalizzare la visualizzazione dei dati applicando alle proprietà del model gli attributi contenuti nei namespace System.ComponentModel
e System.ComponentModel.DataAnnotations
.
Grazie all'attributo [DisplayName]
possiamo modificare il contenuto dei <label>
del form, con [DataType]
possiamo fornire informazioni aggiuntive sul datatype della proprietà, con [ScaffoldColumn]
possiamo impedire che una proprietà compaia nel form e così via.
Ecco un esempio:
public class User
{
[DisplayName("Nome e Cognome")]
public string Name { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DisplayName("Età")]
public int? Age { get; set; }
[DisplayName("Nazione")]
public int CountryId { get; set; }
[ScaffoldColumn(false)]
public bool IsApproved { get; set; }
}
Come vedremo tra poco la funzione può essere personalizzata anche tramite template, in modo da poter gestire tipi di dati molto complessi o custom.
Una funzionalità interessante risiede nella possibilità di richiamare la funzione EditorFor
non soltanto sulle singole proprietà ma anche su tutto un'intera entità.
Ecco un esempio di Html.EditorFor
utilizzato su tutto il Model.
<% using (Html.BeginForm()) { %>
<fieldset>
<%= Html.EditorFor(user => user) %>
<p><input type="submit" value="Create" /></p>
</fieldset>
<% } %>
Con una sola riga di codice avremo un form completamente funzionante in quanto sarà ASP.Net MVC a preoccuparsi di creare i campi necessari per tutte le proprietà del model.
Se invece di modificare i dati volessimo semplicemente visualizzarli MVC 2.0 mette a disposizione la funzione DisplayFor
, il cui uso è equivalente a quello di EditorFor
. Anche in questo caso sarà possibile utilizzare gli attributi per cambiare il modo in cui le singole proprietà vengono visualizzate. La proprietà Email
, ad esempio, decorata con l'attributo [DataType(DataType.EmailAddress)]
viene renderizzata come un collegamento.
Helper personalizzati tramite template
Gli helper di ASP.Net MVC 2.0 supportano un meccanismo di personalizzazione basato su template simile a quello di ASP.Net Dynamic Data.
Possiamo creare template relativi sia alla modifica che alla visualizzazione dei dati, avendo cura di inserire i primi nella cartella EditorTemplates
mentre i secondi nella cartella DisplayTemplates
. Come avviene per le viste possiamo rendere i template disponibili ad un singolo controller inserendoli nella rispettiva cartella, oppure all'intera applicazione utilizzando la cartella Shared
, come avviene nell'immagine seguente:
Nel caso in cui un template abbia il nome di un tipo di dato, come avviene nell'immagine per DateTime.ascx
, ASP.Net applicherà automaticamente il template ogni volta che dovrà effettuare il rendering di una proprietà di quel tipo, nel nostro caso System.DateTime
.
Gli altri template dell'immagine sono invece template personalizzati, richiamabili a piacere e non legati ad un tipo di dato particolare. Ad esempio, per la proprietà CountryId
della nostra classe User
, possiamo migliorare l'esperienza utente creando un template personalizzato che visualizzi una lista di nazioni tra cui scegliere.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int?>" %>
<!-- Template personalizzato Country.ascx -->
<%
// Creo la lista delle nazioni a cui associo un ID numerico.
var countries = new SelectList(new[] {
new {CountryId = 0, CountryName="Italia" } ,
new {CountryId = 1, CountryName="France" } ,
new {CountryId = 2, CountryName="United Kingdom" } ,
new {CountryId = 2, CountryName="United States" }
},
"CountryId",
"CountryName",
Model.GetValueOrDefault(0)
);
%>
<%= Html.DropDownList("", countries) %>
Possiamo quindi richiamare il template direttamente attraverso la funzione EditorFor
:
<%= Html.EditorFor(user => user.CountryId, "Country") %>
Un sistema alternativo consiste nel decorare la proprietà CountryId
con l'attributo [UIHint]
:
public class User
{
// ...
[UIHint("Country")]
public int CountryId { get; set; }
// ...
}
In questo modo ogni volta che ASP.NET dovrà visualizzare la proprietà CountryId
utilizzerà automaticamente il template Country.ascx
.
Encoding code blocks
Una delle novità di ASP.Net 4.0 è rappresentata dai cosiddetti “encoding code blocks” (blocchi di codice con encoding).
In ASP.Net, come avveniva in ASP classico, ogni volta che vogliamo effettuare l'output di una stringa all'interno della pagina possiamo utilizzare la seguente sintassi abbreviata:
<%= nome_della_stringa_da_visualizzare %>
Sebbene molto pratica, questa sintassi non si preoccupa di effettuare alcuna operazione di encoding dei caratteri potenzionalmente pericolosi in HTML (< > "
). Il codice JavaScript del prossimo listato, ad esempio, verrà scritto ed eseguito all'interno della pagina, determinando notevoli problemi di sicurezza legati ad attacchi Cross Site Scripting (CSS):
<%= "<script type="text/javascript">alert('Allarme!')</script>" %>
Per evitare questi problemi in ASP.Net MVC 1.0 eravamo soliti utilizzare nelle nostre viste il metodo Html.Encode
:
<%= Html.Encode("<script type="text/javascript">alert('Allarme!')</script>") %>
Sebbene non ci siano controindicazioni nell'usare questo tipo di sintassi, si perdono completamente i vantaggi della scrittura abbreviata.
Per far fronte a questo problema, ASP.Net 4 introduce una nuova sintassi (grazie ai tag <%: %>
) che si occupa automaticamente dell'encoding delle stringhe prima di inviarle in output. Gli esempi precedenti diventano quindi:
<%: "stringa da inviare in output" %>
<%: "<script type="text/javascript">alert('Allarme!')<script>" %>
Per quanto riguarda MVC, sarà possibile utilizzare questa sintassi soltanto nel caso in cui si sviluppi l'applicazione utilizzando il framework .Net 4.0 come base. Il nuovo sistema non sarà infatti disponibile per le precedenti versioni di .Net.
Possiamo utilizzare il nuovo formalismo anche con tutti quegli HTML Helper che già producono in output codice HTML safe (come ad esempio Html.Action
, Html.TextBox
, ecc.), in quanto ASP.Net è in grado di distinguire se l'encoding sia già stato effettuato o meno.
Questo è reso possibile dal tipo di dato restituito dagli helper che cambia da string
alla nuova interfaccia IHtmlString
. Per maggiori informazioni sull'argomento vi consiglio questo post sul blog di Phil Haack.
MVC Areas: dividere il sito in zone distinte
ASP.Net MVC 2.0 introduce il concetto di Areas proprio del framework Monorail. Grazie alle MVC Areas è possibile dividere l'applicazione in zone funzionali distinte, ciuscuna con i propri controller e le proprie viste.
Questa funzionalità diventa particolarmente utile nel caso di applicazioni molto grandi o complesse, composte da moduli distinti come ad esempio un blog, un forum e così via. Implementare queste funzionalità in zone indipendenti le une dalle altre rende più semplice il lavoro di manutenzione e permette l'inserimento di nuovi moduli all'interno dell'applicazione.
Per capire meglio di cosa stiamo parlando partiamo dall'immagine seguente che illustra la struttura di un progetto MVC basato sulle Areas.
Come potete vedere all'interno del progetto sono presenti le cartelle /Controllers
e /Views
come avviene solitamente in MVC. In queste cartelle sono contenuti i controller e le viste per la zone principale dell'applicazione.
È presente però anche un'altra cartella (/Areas
) che contiene a sua volta le due zone in cui l'applicazione è suddivisa: /Areas/Blog
e /Areas/Forum
. Ogni zona ha i propri controller e le proprie viste, svincolati da quelli del progetto principale.
Le Areas sono raggiungibili grazie alle impostazioni di routing salvate nei rispettivi file Routes.cs
.
// Routes.cs
// relativo alla sezione blog dell'applicazione
public class Routes : AreaRegistration
{
public override string AreaName
{
get { return "blog"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"blog_default",
"blog/{controller}/{action}/{id}",
new { controller = "Blog", action = "Index", id = "" }
);
}
}
In questo caso viene registrata la sezione blog
, con una sola regola di routing impostata.
La registrazione delle MVC Areas all'interno delle regole di routing dell'applicazione principale avviene nel file Global.asax
, nello stesso momento in cui si registrano le altre regole di routing, richiamando la funzione AreaRegistration.RegisterAllAreas()
.
// Registrazione delle MVC Areas
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Registro le Areas dell'applicazione
AreaRegistration.RegisterAllAreas();
// Registro le regole di routing relative alla zone principale
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
Creare collegamenti verso le altre zone dell'applicazione è molto semplice: è infatti sufficiente aggiungere ai route parameters il nome dell'area desiderata. Nel caso della zona principale il parametro sarà vuoto.
Ecco la sintassi per creare un collegamento verso le altre zone dell'applicazione
<%= Html.ActionLink("Forum", "Index", "Forum", new { area = "forum" }, null)%>
<%= Html.ActionLink("Homepage", "Index", "Home", new { area = "" }, null)%>
Una possibilità interessante consiste nel poter creare le MVC Areas non soltanto come cartelle all'interno di un singolo progetto, ma anche come sottoprogetti distinti facenti parte di una stessa solution. Questa possibilità tuttavia non è ancora correttamente supportata da Visual Studio e preferiamo rimandare la sua discussione ad un articolo successivo.
Per maggiori informazioni sulle MVC Areas e i diversi modi in cui è possibile implementarle vi rimando alla documentazione ufficiale sul sito MSDN.
Altre novità
Asp.Net MVC 2 include anche altre piccole novità. Vediamo le principali:
Nuovi attributi
Da segnalare in particolare [HttpGet]
, [HttpPost]
, ecc. che sostituiscono la più verbosa sintassi [AcceptVerbs(HttpVerbs.Get)]
e sono molto utili in caso di applicazioni REST. Un altro attributo interessante è [RequireHttps]
che si preoccupa di reindirizzare l'utente alla stessa azione del solito controller utilizzando una connessione sicura.
Valore di default per parametri
Grazie all'attributo [DefaultValue]
saremo in grado di assegnare un valore predefinito ai parametri delle azioni:
// Valore di default assegnato con [DefaultValue]
public ActionResult Products([DefaultValue(1)] int page)
Utilizzando il framework .Net 4.0 potremo utilizzare anche una sintassi abbreviata simile a quella di PHP:
// Sintassi abbreviata in .Net 4.0
public ActionResult Products(int page = 1)
Per maggiori informazioni vi invito a leggere le Release Notes della Preview Release 2.