I. Introduction▲
Après une toute première version sortie en 2009, Phil Haack et son équipe sont de nouveau au travail pour sortir la version d'ASP .Net MVC 2, qui sera livrée directement au sein de Visual Studio 2010 dont la sortie est prévue le 12 avril 2010. Cette nouvelle version apporte son lot de nouveautés tirant parti du framework 4.
Le Framework ASP .NET MVC 2 sera cependant compilé à l'aide du framework 3.5 SP1 afin de rendre 100% de ses fonctionnalités accessibles aux développeurs qui continueront à utiliser Visual Studio 2008. Cela signifie aussi que la majorité des développements effectués avec la version 1.0 du Framework MVC seront compatibles avec la nouvelle version.
II. Support des Areas▲
La première chose que l'on apprécie dans un projet ASP .Net MVC, c'est la structure organisée, découpée et claire du projet de notre site Web. Bien que n'étant pas obligatoire, il est courant de voir un répertoire pour les vues, un répertoire pour les contrôleurs et un autre pour le modèle.
Malheureusement, plus notre site Web évolue et gagne en fonctionnalités et pages, plus notre projet se complexifie et plus il est difficile de s'y retrouver. Imaginez un site où le nombre de contrôleurs atteindrait aisément la cinquantaine, le nombre de vues peut alors atteindre la centaine voire bien plus. Comment s'y retrouver rapidement parmi cette multitude de répertoires ?
La solution passe alors par l'utilisation des Areas.
Une Area est un découpage de votre site afin de regrouper ensemble certains modèles, certaines vues et certains contrôleurs. Une image étant parfois plus parlante qu'un long discours, observez la capture suivante qui montre que notre projet MVC standard contient une Area nommée Admin, dans laquelle nous placerons tout ce qui a rapport avec la partie administrative de notre portail.
Il est aussi possible d'aller plus loin dans le découpage, en définissant un projet par Area. On va ensuite ne conserver que les informations communes dans le site principal, telles que la mise en page, les contenus graphiques, et autres vues transverses, les vues et contrôleurs spécifiques aux Areas restant dans leurs projets. Dans le cas de l'exemple précédent, déplacer l'Area Admin dans un projet externe nous donnerait la structure suivante :
À noter que les Areas seront toutes fusionnées au moment de la compilation de l'application. L'utilisation des Areas n'entraîne aucune modification de votre code, que cela soit pour les liens ou pour les règles de routage. Il s'agit purement et simplement d'une fonctionnalité qui aide le développeur à mieux s'y retrouver au sein de ses solutions Visual Studio.
III. Helpers fortement typés▲
ASP.NET MVC a introduit le concept de Helpers, à savoir des méthodes qui vont simplifier le rendu de code HTML dans la vue. Ce mécanisme permet de conserver une certaine facilité d'écriture pour des contrôles couramment utilisés, proche des contrôles serveur des formulaires Web, tout en évitant la sémantique serveur (balise runat, Id, etc.)
Les HTML Helpers proposaient, par exemple, la syntaxe suivante :
<%
=
Html.TextBox(
"firstName"
, Model.FirstName)%>
Pour un rendu final en HTML au format suivant :
<input id
=
"firstName"
name
=
"firstName"
type
=
"text"
value
=
"Philippe"
/>
Pour un article plus détaillé sur les HTML Helpers en ASP.NET MVC 1, rendez-vous à cette adresse : https://dotnet.developpez.com/mvc/aspnet-mvc-creating-custom-html-helpers/
Ces HTML Helpers avaient une limitation, à savoir qu'ils se basaient sur la réflexion pour retrouver dans le modèle les données avec lesquelles lier les contrôles. Ce mécanisme, bien que très pratique, pouvait entraîner des bugs non vérifiables à la compilation.
La version 2 du Framework introduit le concept de Helpers fortement typés, utilisant une syntaxe basée sur les expressions lambda. Par exemple, le Helper vu précédemment pourra, avec la V2 du Framework, être écrit :
<%
=
Html.TextBoxFor(client
=
>
client.FirstName) %>
Il est à noter que cette notation supporte bien entendu l'intellisense.
La syntaxe de tous ces nouveaux Helper répond à la logique suivante : Html.Type de ContrôleFor
La liste complète des Helpers est la suivante :
- Html.TextBoxFor() : crée une boîte de texte
- Html.TextAreaFor() : crée une zone multiligne dans laquelle l'utilisateur pourra entrer des données
- Html.DropDownListFor() : crée une liste de choix (une seule sélection possible)
- Html.CheckboxFor() : crée une case à cocher
- Html.RadioButtonFor() : crée un bouton à sélectionner
- Html.ListBoxFor() : crée une liste de choix (plusieurs sélections possibles)
- Html.PasswordFor() : crée un champ de type mot de passe
- Html.HiddenFor() : crée un champ caché
- Html.LabelFor() : crée une étiquette
- Html.DisplayTextFor() : encode le texte au format HTML, et l'affiche dans une étiquette
- Html.ValidationMessageFor() : affiche, si nécessaire, le message d'erreur de validation de l'objet (voir partie suivante pour plus d'information)
IV. Support des annotations▲
Les annotations, ou plus précisément les DataAnnotations, sont une nouveauté qui va permettre de déclarer explicitement les règles de validation des modèles, et d'avoir une validation automatique au niveau de la vue, via le ModelState.
Il existe quatre attributs de validation :
- [Required] : pour rendre une propriété obligatoire
- [StringLength] : pour définir la longueur maximale d'un champ
- [Range] : pour définir une plage de valeurs possibles pour un champ
- [RegularExpression] : pour valider le format d'une propriété
Au niveau utilisation, rien de bien compliqué. Vous placez les attributs au niveau de vos classes modèles, et vous laissez la génération automatique des vues fortement typées faire le reste. Prenons par exemple, notre classe User :
public
class
User
{
[Required(ErrorMessage =
"Le nom est requis"
)]
[StringLength(
75
, ErrorMessage =
"Le nom ne peut pas faire plus de 75 caractères"
)]
public
string
Name
{
get
;
set
;
}
[Required(ErrorMessage =
"L'adresse est requise"
)]
public
string
Address
{
get
;
set
;
}
[Range(
1
,
95
, ErrorMessage =
"Le numéro de département doit être compris entre 1 et 95"
)]
public
string
DepartmentNumber
{
get
;
set
;
}
[RegularExpression(
@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))
([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"
,ErrorMessage =
"Le format de l'email est incorrect"
)]
public
string
Mail
{
get
;
set
;
}
}
Puis utilisons notre classe au niveau de notre contrôleur :
public
ActionResult Create
(
)
{
User usr =
new
User
(
);
return
View
(
usr);
}
[HttpPost]
public
ActionResult Create
(
User usr)
{
try
{
if
(!
ModelState.
IsValid)
return
View
(
usr);
// Insertion de l'utilisateur en base de données
return
RedirectToAction
(
"Index"
);
}
catch
{
return
View
(
usr);
}
}
Enfin, utilisons les validateurs au sein de la vue :
<%
using
(
Html.
BeginForm
(
)) {%>
<
fieldset>
<
legend>
Fields</
legend>
<
div class
=
"editor-label"
>
<%=
Html.
LabelFor
(
model =>
model.
Name) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
TextBoxFor
(
model =>
model.
Name) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Name) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
LabelFor
(
model =>
model.
Address) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
TextBoxFor
(
model =>
model.
Address) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Address) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
LabelFor
(
model =>
model.
DepartmentNumber) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
TextBoxFor
(
model =>
model.
DepartmentNumber) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
DepartmentNumber) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
LabelFor
(
model =>
model.
Mail) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
TextBoxFor
(
model =>
model.
Mail) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Mail) %>
</
div>
<
p>
<
input type=
"submit"
value
=
"Create"
/>
</
p>
</
fieldset>
<%
}
%>
Au moment de la validation, rien à faire. Le moteur de validation du ModelState se charge de valider pour vous et de retourner les informations à la vue, qui s'adapte pour afficher les messages d'erreur :
Il est possible de créer ses propres annotations assez facilement. Pour cela, il suffit de créer une nouvelle classe, que l'on fera hériter de ValidationAttribute.
V. Validation côté client▲
Ceux qui ont suivi le développement de la bibliothèque xVal ne seront pas dépaysés par cette nouvelle fonctionnalité. En effet, la validation côté client permet, grâce aux annotations mentionnées précédemment, de gérer en JavaScript une validation fine des formulaires depuis la définition des classes.
Repartons de la classe User vue ci-dessus. On a vu qu'une fois les attributs Required, StringLength, Range et RegularExpression renseignés, si un des attributs n'est pas vérifié, il affiche au client une zone de texte décrivant les erreurs. Pour que cette vérification soit faite avant que la requête ne soit envoyée au serveur, il suffit d'ajouter au code de la vue un appel à la fonction Html.EnableClientValidation(). Le code de la vue est donc désormais :
<%
Html.
EnableClientValidation
(
);
%>
<%=
Html.
ValidationSummary
(
) %>
<%
using
(
Html.
BeginForm
(
)) {%>
<
fieldset>
<
legend>
Fields</
legend>
<
div class
=
"editor-label"
>
<%=
Html.
DisplayFor
(
model =>
model.
Name) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
EditorFor
(
model =>
model.
Name) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Name) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
DisplayFor
(
model =>
model.
Address) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
EditorFor
(
model =>
model.
Address) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Address) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
DisplayFor
(
model =>
model.
DepartmentNumber) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
EditorFor
(
model =>
model.
DepartmentNumber) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
DepartmentNumber) %>
</
div>
<
div class
=
"editor-label"
>
<%=
Html.
DisplayFor
(
model =>
model.
Mail) %>
</
div>
<
div class
=
"editor-field"
>
<%=
Html.
EditorFor
(
model =>
model.
Mail) %>
<%=
Html.
ValidationMessageFor
(
model =>
model.
Mail) %>
</
div>
<
p>
<
input type=
"submit"
value
=
"Create"
/>
</
p>
</
fieldset>
<%
}
%>
Pour que la validation côté client fonctionne correctement, il faut aussi ajouter, dans la vue, une référence aux fichiers de script suivants :
<
script src=
"../../Scripts/jquery-1.4.1.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>
Au moment où j'écris l'article, en ayant installé la version RTM de ASP.NET MVC 2, il m'a fallu aller récupérer sur le site codeplex (http://aspnet.codeplex.com/releases/) le fichier MicrosoftMvcJQueryValidation.
Ce fichier peut être trouvé dans les sources du Framework, dans le sous-répertoire MvcFutureFiles du répertoire src.
À première vue, après génération de la page, rien ne change, sauf que, si je rentre un nom, puis que je l'efface, le message d'erreur apparaît désormais avant même d'avoir cliqué sur Create (notez que, dans la fenêtre firebug, la seule requête à avoir été effectuée est un Get…).
Une dernière remarque importante. Si vous utilisez la notation Html.EditorFor(model => model.Mail), il faut absolument ajouter Html.ValidationMessageFor(model => model.Mail) pour que la validation côté client fonctionne. En effet, dans le cas contraire, le script ne saura pas où rendre les messages d'erreur.
VI. Helpers mis en forme depuis un modèle (Templated Helpers)▲
Cette fonctionnalité fait partie de celles permettant le plus gros gain de productivité dans la version 2 du framework MVC. L'idée de base est d'appliquer la logique de modèle de page utilisée pour générer les vues (utiliser la réflexion pour découvrir les propriétés d'un objet donné) sur tous les objets du modèle. Ce système permet donc de gérer l'affichage (grâce à DisplayFor) ou l'édition (grâce à TextBoxFor) d'un objet automatiquement, que ce soit un objet de la BCL ou une classe du modèle.
Pour clarifier le fonctionnement de ces Helpers, imaginons la classe suivante :
public
class
Client{
public
int
Id {
get
;
set
;}
public
string
Nom {
get
;
set
;}
public
string
Prenom {
get
;
set
;}
public
string
Email {
get
;
set
;}
}
Pour afficher cet objet dans une vue à laquelle on passe un objet de type Client, il suffit, avec le Framework MVC V2, d'avoir le code suivant :
<asp:
Content ID
=
"Content2"
ContentPlaceHolderID
=
"MainContent"
runat
=
"server"
>
<%=
Html.DisplayForModel() %>
<p>
<%=
Html.ActionLink("Edit"
, "Edit"
, new { /*
id
=
Model.PrimaryKey */ }) %>
|
<%=
Html.ActionLink("Back to List"
, "Index"
) %>
</p>
</asp
:
Content>
La page HTML générée lorsque l'on accède à la vue (en mode Details) est la suivante :
De la même façon, on peut éditer simplement un contrôle de la façon suivante :
<asp:
Content ID
=
"Content2"
ContentPlaceHolderID
=
"MainContent"
runat
=
"server"
>
<%=
Html.TextBoxForModel() %>
<p>
<input type
=
"submit"
value
=
"Create"
/>
</p>
<div>
<%=
Html.ActionLink("Back to List"
, "Index"
) %>
</div>
</asp
:
Content>
Cette fois-ci, en mode Create, on aura le rendu suivant :
Il est possible d'utiliser chacune de ces deux fonctionnalités de trois façons différentes.
On pourra, comme on vient de le voir, passer par le modèle. Dans ce cas, le code HTML généré prend en compte l'ensemble du modèle passé à la page (fortement typée ou non).
On pourra aussi se baser sur une expression. La notation dans ce cas la sera très semblable à celle des Helpers fortement typés. Par exemple, si on ajoute une classe Adresse :
public
class
Adresse{
public
string
Ligne1 {
get
;
set
;}
public
string
Ligne2 {
get
;
set
;}
public
string
CodePostal {
get
;
set
;}
public
string
Ville {
get
;
set
;}
public
string
Pays {
get
;
set
;}
}
et que l'on ajoute une variable membre de type Adresse à notre classe Client :
public
class
Client{
...
public
Adresse AdresseLivraison {
get
;
set
;}
...
}
on pourra, dans une vue à laquelle on passe un objet de type Client, afficher l'adresse de cette façon :
<%=
Html.
DisplayFor
(
client =>
client.
AdresseLivraison) %>
...
<%=
Html.
EditorFor
(
client =>
client.
AdresseLivraison) %>
avec, finalement, les rendus suivants :
Dans le cadre d'une vue fortement typée, Html.EditorForModel() est équivalent à Html.EditorFor(model => model)
Il est possible d'utiliser les métadonnées du modèle pour gérer finement les informations générées par Editor ou Display, et le système permet de développer des templates spécifiques.
VI-A. Métadonnées du modèle : la classe ModelMetaData▲
La classe ModelMetaData permet, comme son nom l'indique, de définir un ensemble de métadonnées pour des objets faisant partie du modèle (au sens Model de MVC) de l'application. Ces métadonnées permettent de rajouter des informations ou des comportements supplémentaires sans avoir à implémenter la logique manuellement.
On peut voir ces métadonnées comme un cousin des DataAnnotations vues précédemment, mais qui permettraient de changer l'affichage des champs plutôt que de les valider. La classe ModelMetaData a les propriétés suivantes :
- ConvertEmptyStringToNull : si cette propriété est à true, les chaînes vides seront converties en NULL (par défaut : true)
- DataTypeName: donne des informations supplémentaires sur le type de donnée, par exemple Email, ou Password (par défaut : null)
- Description : une description du champ (par défaut : null)
- DisplayFormatString : une chaîne représentant le format à appliquer lorsque le modèle est rendu dans un template en mode affichage (par défaut : null)
- DisplayName : nom long, utilisé pour générer l'étiquette par LabelFor (par défaut : null)
- EditFormatString : une chaîne représentant le format à appliquer lorsque le modèle est rendu dans un template en mode édition (par défaut : null)
- HideSurroundingHtml : si cette propriété vaut false, la méthode EditorFor rendra un div et un label contenant le nom du champ (ou displayName). (par défaut : false)
- IsReadOnly : indique que le champ doit être en lecture seule (par défaut : false)
- IsRequired : indique que la valeur est requise, (par défaut : true pour les types non nullables, false pour les autres)
- NullDisplayText : le texte à afficher en mode affichage quand le modèle est nul ( (par défaut : null)
- ShortDisplayName : nom court du modèle, utilisé comme titre en vue tabulaire. S’il est nul, DisplayName sera utilisé à la place (par défaut : null)
- ShowForDisplay : si vrai, le champ sera affiché en mode affichage (par défaut : true)
- ShowForEdit : si vrai, le champ sera affiché en mode édition (par défaut : true)
- Watermark : texte à utiliser comme Watermark quand le champ est vide
Pour le moment, une partie de ces propriétés étant spécifique au Framework 4.0, et les développeurs ayant fait le choix de laisser la possibilité aux utilisateurs du Framework 3.5 d'utiliser les annotations, les attributs disponibles (au travers des Dataannotations) sont les suivants :
- [HiddenInput] : cet attribut va rendre un champ caché, sans le descriptif du champ, à moins que la valeur de DisplayValue ne soit explicitement mise à true, auquel cas un label sera rendu, ainsi qu'un champ caché
- [UIHint] : permet de passer le nom du template à utiliser pour rendre le champ
- [DataType] : permet de modifier la propriété DataTypeName
- [ReadOnly] : permet de modifier la propriété ReadOnly
- [DisplayFormat] : permet de gérer les propriétés NullDisplayText, DisplayFormatString (avec DataFormatString), EditFormatString (avec ApplyFormatInEditMode), et ConvertEmptyStringToNull
- [ScaffoldColumn] : permet de gérer à la fois les propriétés ShowForDisplay et ShowForEdit
- [DisplayName] : permet de modifier la propriété DisplayName
Si on reprend l'exemple précédent de classe Client, la classe modifiée comme suit :
public
class
Client
{
[HiddenInput(DisplayValue = false)]
public
int
Id {
get
;
set
;
}
public
string
Nom {
get
;
set
;
}
[DisplayName(
"Prénom"
)]
public
string
Prenom {
get
;
set
;
}
[ScaffoldColumn(false)]
public
string
Employeur {
get
;
set
;
}
public
string
Email {
get
;
set
;
}
[DisplayFormat(DataFormatString =
"{0:dd/MM}"
)]
public
DateTime DateNaissance {
get
;
set
;
}
}
on aura, dans les vues d'édition et d'affichage, le rendu suivant :
VI-B. Templates personnalisés▲
Les templates personnalisés permettent d'aller beaucoup plus loin dans la gestion de l'affichage. Il faut savoir que lorsque le système cherche à rendre un objet complexe en se basant sur un template, il va chercher le template en question dans le répertoire DisplayTemplates pour les templates d'affichage, et EditorTemplates pour les templates d'édition.
Si on voulait ajouter un template spécifique pour un objet de notre application, il suffit donc d'ajouter un fichier ascx dans le dossier adéquat, avec, par défaut, le nom de la classe en question. Si on veut utiliser, pour une classe donnée, un template portant un nom différent, il suffit de le définir dans l'attribut UIHint.
Pour prendre un exemple fictif, supposons que je veuille, dès lors que j'affiche une adresse, avoir systématiquement l'affichage suivant :
il me suffira, pour cela, de définir un fichier Adresse.ascx dans un répertoire DisplayTemplates sous le répertoire Views, et de le définir comme suit :
<%
@ Control Language=
"C#"
Inherits=
"System.Web.Mvc.ViewUserControl<MvcApplication3.Controllers.Adresse>"
%>
<
fieldset>
<
legend>
Adresse :</
legend>
<
div class
=
"display-field"
><%=
Html.
Encode
(
Model.
Ligne1) %></
div>
<
div class
=
"display-field"
><%=
Html.
Encode
(
Model.
Ligne2) %></
div>
<
div class
=
"display-field"
><%=
Html.
Encode
(
Model.
CodePostal) %>
<%=
Html.
Encode
(
Model.
Ville) %></
div>
<
div class
=
"display-field"
><%=
Html.
Encode
(
Model.
Pays) %></
div>
</
fieldset>
et le résultat, dans notre formulaire d'édition vu précédemment, deviendra aussitôt :
VII. Actions asynchrones▲
La nouvelle classe AsyncController permet de créer des actions asynchrones. Une action asynchrone permet de déporter une partie d'un calcul ou d'une opération longue dans un second thread, de façon à paralléliser les traitements. L'idée est de ne pas mobiliser des threads pour attendre le résultat d'un calcul, le nombre de thread disponibles dans le pool étant limité.
Lorsqu’une action asynchrone est invoquée, elle est traitée de la façon suivante :
- le serveur IIS récupère un thread (thread 1) du pool de threads pour traiter la requête
- ce thread (thread 1) commence une opération asynchrone. Il est ensuite renvoyé dans le pool.
- lorsque l'action asynchrone s'achève, elle notifie le serveur
- le serveur récupère un nouveau thread dans le pool pour terminer de traiter la requête, et renvoyer la réponse
Il n'est pas avantageux d'utiliser des actions asynchrones systématiquement, mais plutôt de cibler quelques requêtes incluant des traitements longs, en particulier celles où la latence provient du réseau (appel d'un service) ou des entrées/sorties (lecture d'un fichier) . En effet, si la durée du traitement est purement liée à la puissance de calcul, utiliser des actions asynchrones va diminuer les performances.
Supposons que notre site Web contienne une action récupérant une liste des commandes d'un client donné, dont on veut rendre la récupération asynchrone. Le code du contrôleur d'origine est le suivant :
public
class
ClientController :
Controller {
public
ActionResult Details
(
int
id) {
var
objClientService =
new
ClientService
(
);
var
objCommandeService =
new
CommandeService
(
);
var
objClient =
objClientService.
GetById
(
id);
ViewData[
"Commandes"
]
=
objCommandeService.
GetCommandesByClientId
(
id);
return
View
(
objClient);
}
}
On va, pour cela, effectuer les actions suivantes :
- changer la classe de base du contrôleur en AsyncController
- créer une action DetailsAsync, qui aura la charge d'initier l'appel asynchrone
- créer une action DetailsCompleted, qui sera appelée à l'issue de l'appel asynchrone
Nommer l'action [nom de l'action]Async et [nom de l'action]Completed fait partie des conventions du Framework. Si les classes sont nommées autrement, l'action ne sera pas traitée.
DetailsAsync doit renvoyer void, et sera le point d'entrée des requêtes. Cette méthode peut envoyer des paramètres à DetailsCompleted en utilisant AsyncManager.Parameters. DetailsCompleted renvoie (comme toutes les autres actions) un objet ActionResult
Le résultat final sera le suivant :
public
class
ClientController :
AsyncController {
public
ActionResult DetailsAsync
(
int
id) {
AsyncManager.
OutstandingOperations.
Increment
(
);
var
objClientService =
new
ClientService
(
);
var
objCommandeService =
new
CommandeService
(
);
var
client =
objClientService.
GetById
(
id);
// appel, de façon asynchrone, de objCommandeService.GetCommandesByClientId
Func<
int
,
List<
Commandes>>
asyncDelegate =
objCommandeService.
GetCommandesByClientId;
asyncDelegate.
BeginInvoke
(
id,
ar =>
{
var
handler =
(
Func<
int
,
List<
Commandes>>
)ar.
AsyncState;
// passage des données en paramètre à la fonction DetailCompleted
AsyncManager.
Parameters[
"client"
]
=
client;
AsyncManager.
Parameters[
"commandes"
]
=
handler.
EndInvoke
(
ar);
AsyncManager.
OutstandingOperations.
Decrement
(
);
},
asyncDelegate);
}
public
ActionResult DetailsCompleted
(
Client client,
List<
Commandes>
commandes)
{
ViewData[
"Commandes"
]
=
commandes;
return
View
(
client);
}
}
Une fois que DetailsAsync est appelée, les appels à AsyncManager.OutstandingOperations.Increment() et AsyncManager.OutstandingOperations.Decrement() permettent de gérer le nombre d'opérations en cours. Lorsque le nombre d'opérations en cours est égal à 0, le contexte d'exécution passe à DetailsCompleted, qui retourne l'action.
VIII. Autres améliorations▲
VIII-A. Les valeurs par défaut▲
Avec ASP .Net MVC 1, il était possible de définir des paramètres optionnels, soit en rendant un paramètre d'une action Nullable, soit en modifiant la route dans global.asax pour inclure des valeurs par défaut.
Avec MVC 2, il est possible d'ajouter à vos méthodes, des attributs DefaultValueAttribute afin de s'assurer de la présence d'une valeur pour un paramètre donné. Ainsi, en ajoutant une valeur par défaut sur notre paramètre de filtrage, les URL /ListUsers et /ListUsers/Administrateurs permettent d'obtenir le même résultat.
public
ActionResult ListUsers
([
DefaultValue
(
"administrateurs"
)]
string
groups)
{
}
À noter que Visual Studio 2010 et le C# 4.0 vous permettent de définir des valeurs par défaut, directement dans la signature de la méthode, ce qui permet de transformer le code précédent en:
public
ActionResult ListUsers
(
string
groups =
"administrateurs"
)
{
}
VIII-B. Paramètres d'URL optionnels▲
À contrario, il est aussi désormais possible de déterminer qu'un des paramètres de l'URL est optionnel.
Avec ASP.NET MVC 1, un problème récurrent était la gestion du paramètre Id. Tous ceux qui ont développé des sites avec cette version reconnaîtront ce code :
[HttpPost]
public
ActionResult Create
([
Bind
(
Exclude =
"Id"
)]
User newUser)
{
try
{
// code d'insertion
}
catch
(
Exception exc)
{
LogManager.
AddLog
(
exc);
return
View
(
);
}
}
le verbe Bind(Exclude = « Id ») est ajouté pour empêcher que le paramètre Id venant de la route par défaut du projet n'entre en collision avec un éventuel membre Id du modèle. Pour enlever cette petite verrue, il est désormais possible de déterminer que le paramètre Id est optionnel, en lui affectant comme valeur par défaut la valeur UrlParameter.Optional.
routes.
MapRoute
(
"Default"
,
"{controller}/{action}/{id}"
,
new
{
controller =
"Home"
,
action =
"Index"
,
id =
UrlParameter.
Optional }
);
Ce n'est pas une révolution, mais ça fait plaisir quand même.
VIII-C. Nouveaux attributs Http▲
Ici, aucune grande nouveauté, simplement la possibilité de simplifier le code en transformant vos anciens [AcceptVerbs(HttpVerbs.XXXX)] en un simple [HttpXXXX]. Ainsi, les deux codes suivants, tous deux valables en MVC 2, font exactement la même chose :
[AcceptVerbs(HttpVerbs.Post)]
public
ActionResult CreateUser
(
User usr)
{
}
...
[HttpPost]
public
ActionResult CreateUser
(
User usr)
{
}
Les verbes concernés sont :
- HttpDelete
- HttpGet
- HttpPost
- HttpPut
VIII-D. Le type MvcHmtlString▲
Dorénavant, les helpers retourneront un objet typé MvcHmtlString en lieu et place de l'objet String. Ce nouvel objet permet de tirer profit d'un format de chaîne qui n'a pas besoin d'être réencodé pour l'affichage. Ce format qui apparaît notamment au sein d'ASP .Net 4.0 a été intégré grâce à un petit peu de magie comme l'explique Phil Haack sur son blog
Ainsi, plus besoin de faire sans cesse un Html.Encode(XXX) de chacun de vos helpers.
Cela veut aussi dire que, cette fonctionnalité étant liée à ASP.NET 4.0, vous ne pouvez pas compter dessus dans un développement visant le Framework 3.5.
IX. Conclusion▲
La version 2 du Framework MVC permet d'améliorer encore la productivité des développeurs utilisant cette forme de développement Web, tout en restant concentré sur les aspects extensibilité, développement par convention et Scaffolding de la version 1.0.
Bien que n'ayant pas encore la même base d'utilisateurs que le Framework WebForms, le Framework MVC monte encore en puissance et montre que des alternatives à Webform viables existent. Pour ceux qui seraient intéressés par un petit coup d'œil vers le futur, la RoadMap prévisionnelle de la version 3 est disponible sur Codeplex, à l'adresse suivante : Roadmap MVC
Pour tous les développeurs utilisant encore Visual studio 2008, le framework est téléchargeable à l'adresse suivante : http://www.asp.net/mvc/download/
X. Remerciements▲
Merci à toute l'équipe de rédaction .Net, et en particulier à jacques_jean pour leurs corrections.