sept. 27 2011

[ASP.NET] Nouveautés d'ASP.NET 4.5 Web Forms

ASP.NET vNextVoici le second billet sur la série ASP.NET vNext. Le premier présentait les nouveautés de la version 4.5 du runtime ASP.NET qui concernaient à la fois ASP.NET Web Forms et ASP.NET MVC. Dans ce billet nous allons voir celles concernant uniquement les Web Forms. Au programme : 

  • Les contrôles de données fortement typés
  • La liaison de modèle, emprunté à MVC, qui étend le liaison de données standard des Web Forms
  • Diverses améliorations bien utiles

Pour ce billet, je me suis inspiré du document officiel What's new in ASP.NET 4.5 and Visual Web Developer 11 Developer Preview ainsi que de la série initiée par Scott (et oui, même s'il est responsable de la division Azure depuis quelques temps, il est toujours productif lorsqu'il s'agit d'ASP.NET).

Contrôles de données fortement typés

La version 4.5 d'ASP.NET Web Forms inclus de nombreuses améliorations pour travailler avec les données. La première concerne les contrôles de données fortement typés. Dans sa toute première release ASP.NET Web Forms avait introduit le concept de template. Ces templates permettent de personaliser le rendu HTML des contrôles serveurs (comme le contrôle Repeater, le contrôle GridView, etc.). Ces templates sont généralement utilisées avec les expressions de liaison de données.

Les expressions de liaison de données sont contenues dans les délimiteurs <%# et %> et utilisent les fonctions Eval et Bind. La fonction Eval sert à définir une liaison unidirectionnelle. La fonction Bind sert à une liaison bidirectionnelle. Par exemple, jusqu'à aujourd'hui, avec ASP.NET Web Forms, pour utiliser une liaison de données sur une liste Clients dans un contrôle Repeater, il nous fallait utiliser la fonction Eval pour afficher les propriété "Nom" et "Prénom" de la classe Client :

<ul>
<asp:Repeater runat="server" ID="clients">
<ItemTemplate>
<li>
Nom: <%# Eval("Nom") %><br />
Prenom: <%# Eval("Prenom") %><br />
</li>
</ItemTemplate>
</asp:Repeater>
</ul>

Pour une liaison bidirectionnelle, c'est à dire actualisable (au contraire d'une liaison unidirectionnelle avec Eval qui est en lecture seule), il nous fallait utiliser la fonction Bind :

<asp:FormView runat="server" ID="editClient">
<EditItemTemplate>
<div>
<asp:Label runat="server" AssociatedControlID="prenom">Prenom :</asp:Label>
<asp:TextBox ID="prenom" runat="server" Text='<%# Bind("Prenom") %>' />
</div>
<div>
<asp:Label runat="server" AssociatedControlID="nom">Nom :</asp:Label>
<asp:TextBox ID="nom" runat="server" Text='<%# Bind("Nom") %>' />
</div>
<asp:Button runat="server" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>

Au moment de l'exécution, ces appels utilisent la réflexion pour lire la valeur de la propriété spécifiée (dans notre cas : Client.Nom et Client.Prenom), puis affichent le résultat dans le code HTML généré par le contrôle de données. Cette approche rend très aisée la liaison des données non structurées. Toutefois, avec les expressions de liaison de données nous n'avons pas accès à l'IntelliSense (pour automatiquement proposer les propriétés Nom et Prenom lors de la saisie). De même, aucune vérification n'est faite lors de la compilation. Prenez le temps d'y penser deux minutes : combien de fois une faute de frappe dans le nom des propriétés vous a fait perdre de préciseuses minutes lors du développement ? Que de temps perdu parce qu'on a saisi "Prnom" à la place de "Prenom" !

Pour résoudre ces problèmes, ASP.NET 4.5 ajoute la possibilité de déclarer qu'un contrôle est lié à un type de données précis. Pour cela, rien de plus simple, il suffit d'utiliser la nouvelle propriété ModelType. Lorsque qu'on définit cette propriété, deux nouvelles variables typées sont disponibles dans le champ de liaison de données d'expressions : Item et BindItem. Parce que les variables sont fortement typés, on peut alors utiliser l'IntelliSense pour sélectionner nos propriétés "Nom" et "Prenom", comme on peut le voir sur la capture ci-dessous :

ASP.NET vNext

 

Pour les expressions de liaison de donnée bidirectionnelle, c'est à dire en utilisant la fonction Bind, nous utiliserons à la place la variable BindItem. Après bien sûr avoir spécifié le modèle associé au contrôle :

<asp:FormView runat="server" ID="editClient" ModelType="ASPNETvNextTest.Model.Client">
<EditItemTemplate>
<div>
<asp:Label runat="server" AssociatedControlID="prenom">Prenom :</asp:Label>
<asp:TextBox ID="prenom" runat="server" Text='<%# BindItem.Prenom %>' />
</div>
<div>
<asp:Label runat="server" AssociatedControlID="nom">Nom :</asp:Label>
<asp:TextBox ID="nom" runat="server" Text='<%# BindItem.Nom %>' />
</div>
<asp:Button runat="server" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>

Comme on l'a vu dans les exemples précédents, l'ajout de l'IntelliSense pour les expressions de liaison de données va franchement faciliter la vie au développeur Web Forms (j'entends le rire sarcastique des développeurs MVC derrière, mouarf !). Mais n'oubliez pas que cela va aussi permettre de gagner un temps fou de débugage, car il n'y aura plus de fautes de frappes. L'utilisation des contrôles fortement typés permet de détecter les erreurs directement lors de la compilation :

ASP.NET vNext

Pour résumer cette nouveauté : pas grand chose... mais que de temps gagné ! De plus la plupart des contrôles de données Web Forms supporteront la propriété ModelType.

 

La liaison de modèle... ou la convergence de Web Forms vers MVC

 

Sélection des données

Pour configurer un contrôle de données afin qu'il utilise la liason de modèle pour sélectionner les données, il faut définir la propriété SelectMethod du contrôle avec le nom d'une méthode du code-behind de la page. Le contrôle appellera ainsi cette méthode au moment opportun dans le cycle de vie de la page et se liera automatiquement aux données retournées. Comme vous l'avez deviné, il ne sera plus nécessaire d'appeller notre bonne vieille méthode DataBind. Afin de représenter cette nouvelle (et meilleure) façon de faire, un petit exemple s'impose. Nous allons utiliser le contrôle GridView et le configurer afin qu'il utilise une liaison de données avec le modèle "Category" :

<asp:GridView ID="categoriesGrid" runat="server"
ModelType="WebApplication1.Model.Category"
SelectMethod="GetCategories" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="CategoryID" HeaderText="ID" />
<asp:BoundField DataField="CategoryName" HeaderText="Name" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="# of Products">
<ItemTemplate><% #Item.Products.Count %></ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>

La GridView ci-dessus fait appel à la méthode GetCategories dans le code-behind de la page. Pour une opération de simple sélection, la méthode ne nécessite pas de paramètres et doit retourner un objet IEnumerable ou IQueryable. Si la nouvelle propriété ModelType est également spécifiée pour le contrôle GridView afin d'activer la liaison fortement typée, les versions génériques de ces interfaces doivent être retournés - IEnumerable <T> ou IQueryable <T>, avec le paramètre T correspondant au type de la propriété ModelType. Dans notre cas, vu que ModelType référencie la classe Category, alors la méthode GetCategories devra retourner un objet de type  IQueryable<Category> ou IEnumerable<Category>.

Le code ci-dessous présente l'implémentation de cette méthode. Afin d'avoir un jeu de données pour illustrer les différents concepts, la base Northwind est utilisée via un modèle Entity Framework. Le code fait en sorte que la requête retourne des détails sur les produits liés à chaque catégorie en passant par la méthode Include :

public IQueryable<Category> GetCategories()
{
var db = new Northwind();
return db.Categories.Include(c => c.Products);
}

Lorsque la page s'exécute, le contrôle GridView appelle la méthode GetCategories automatiquement et affiche les données renvoyées en utilisant les champs configurés :

Parce que la méthode Select retourne un objet IQueryable, le contrôle GridView peut manipuler davantage la requête avant de l'exécuter. Par exemple, le contrôle GridView peut ajouter les expressions de requête pour le tri et de pagination pour l'objet retourné IQueryable avant qu'il ne soit exécuté, de sorte que ces opérations soient effectuées par le fournisseur sous-jacent Linq. Dans notre cas, Entity Framework se chargera d'effectuer pour nous ces opérations dans la base de données. L'exemple suivant montre que le contrôle GridView modifié pour permettre le tri et de pagination :

<asp:GridView ID="categoriesGrid" runat="server"
AutoGenerateColumns="false"
AllowSorting="true" AllowPaging="true" PageSize="5"
ModelType="WebApplication1.Model.Category" DataKeyNames="CategoryID"
SelectMethod="GetCategories"
UpdateMethod="UpdateCategory">
<Columns>
<asp:BoundField DataField="CategoryID" HeaderText="ID"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Name"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="# of Products">
<ItemTemplate><% # Item.Products.Count %></ItemTemplate>
</asp:TemplateField>
</Columns>
<EmptyDataTemplate>No categories found with a product count of
<% # minProductsCount.SelectedValue %></EmptyDataTemplate>
</asp:GridView>

Maintenant lorsque la page s'exécute, seule la page courante est affichée et les données sont triés :

Pour filtrer les données retournées, des paramètres doivent être ajoutés à la méthode Select. Ces paramètres seront remplies par la liaison de modèle au moment de l'exécution, et on peut les utiliser pour modifier la requête avant de retourner les données. Par exemple, supposons que nous souhaitons laisser la possibilité à l'utilisateur de filtrer les produits en saisissant un mot-clé. Pour cela, il nous suffit d'ajouter un paramètre à la méthode et et mettre à jour la requête Linq pour prendre en compte ce filtre :

public IQueryable<Product>GetProducts(string keyword)
{
IQueryable<Product> query = _db.Products;

if (!String.IsNullOrWhiteSpace(keyword))
{
query = query.Where(p => p.ProductName.Contains(keyword));
}

return query;
}

 

Les fournisseurs de valeur

Dans l'exemple précédent, il n'a pas été précisé d'où la valeur du paramètre keyword provenait. Pour indiquer cette information, on peut utiliser un attribut de paramètre via la classe QueryStringAttribute qui se trouve dans l'espace de noms System.Web.ModelBinding :

public IQueryable<Product> GetProducts([QueryString]string keyword)
{
IQueryable<Product> query = _db.Products;

if (!String.IsNullOrWhiteSpace(keyword))
{
query = query.Where(p => p.ProductName.Contains(keyword));
}

return query;
}

Cet attribut sur le paramètre indique à la lisaison de modèle de tenter de lier une valeur de la QueryString pour le paramètre keyword au moment de l'exécution. Si une valeur n'est pas trouvée et que son type n'est pas Nullable, alors une exception est levée.

Les sources de valeurs pour ces méthodes sont appelés fournisseurs de valeur. ASP.NET Web Forms dans sa version 4.5 inclus les fournisseurs de valeur et leurs attributs correspondants pour toutes les sources classiques d'entrée utilisateur dans une application Web Forms. Comme par exemple les QueryString, les cookies, les valeurs du formulaire, le ViewSate, la Session, etc. En plus de cela il est tout à fait possible d'écrire ses propres fournisseurs de valeur personnalisés.

Par défaut, le nom du paramètre est utilisé comme clé pour trouver une valeur dans la collection des fournisseurs de valeur. Dans l'exemple précédent, le code va chercher une valeur dans la QueryString nommée "keyword" (par exemple, ~ / default.aspx?keyword=chef). ASP.NET nous permet de spécifier directement une clé personnalisée en la passant comme argumant à l'attribut du paramètre. Par exemple pour utiliser la valeur de la QueryString nommée q, nous pourrions faire ceci :

public IQueryable<Product> GetProducts([QueryString("q")]string keyword)
{
IQueryable<Product> query = _db.Products;

if (!String.IsNullOrWhiteSpace(keyword))
{
query = query.Where(p => p.ProductName.Contains(keyword));
}

return query;
}

Si cette méthode est dans le code de la page, les utilisateurs peuvent filtrer les résultats en passant un mot-clé en utilisant la chaîne de requête :

On ne s'en rend pas compte comme ça, mais la liaison de modèle accomplit pas mal de tâches à notre place. Et ces tâches nous aurions du nous farcir le codage nous même. Par exemple : la lecture de la valeur, la vérification pour une valeur nulle, la convertion dans le type approprié, vérifier si la conversion a réussi, et enfin, utiliser cette valeur dans la requête ! Pour résumer la liaison de données nous économise l'écriture de pas mal de code et a aussi le mérite d'être réutilisable partout dans notre application.


Filtrage par les valeurs d'un contrôle

Reprenons l'exemple précédant et supposons mainteant que l'utilisateur filtrera les produits, non pas en saisissant un mot-clé, mais en sélectionnant une valeur dans une liste déroulante. Pour la DropDownList, nous pouvons utiliser, comme avec un GridView, la propriété SelectMethod pour la remplir :

<asp:Label runat="server" AssociatedControlID="categories"
Text="Select a category to show products for: " />
<asp:DropDownList runat="server" ID="categories"
SelectMethod="GetCategories" AppendDataBoundItems="true"
DataTextField="CategoryName" DataValueField="CategoryID"
AutoPostBack="true">
<asp:ListItem Value="" Text="- all -" />
</asp:DropDownList>

Typiquement, nous pouvons aussi ajouter un élément EmptyDataTemplate au contrôle GridView afin que le contrôle affiche un message si aucun produit correspondant à la catégorie selectionnée par l'utilisateur n'est trouvé :

<asp:GridView ID="productsGrid" runat="server" DataKeyNames="ProductID"
AllowPaging="true" AllowSorting="true" AutoGenerateColumns="false"
SelectMethod="GetProducts" >
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ID" />
<asp:BoundField DataField="ProductName" HeaderText="Name"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Unit Price"
SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="# in Stock"
SortExpression="UnitsInStock" />
</Columns>
<EmptyDataTemplate>
No products matching the filter criteria were found</EmptyDataTemplate>
</asp:GridView>

Dans le code-behind de la page, la méthode GetCategories pour alimenter la DropDownList est on ne peut plus simple :

public IQueryable<Category> GetCategories()
{
return _db.Categories;
}

Pour prendre en compte le choix utilisateur, il ne reste plus qu'à modifier la méthode GetProducts pour qu'elle puisse recevoir un nouveau paramètre qui contient l'ID de la catégorie sélectionnée dans la DropDownList :

public IQueryable<Product> GetProducts(
[QueryString("q")] string keyword,
[Control("categories")] int? categoryId)
{
IQueryable<Product> query = _db.Products;

if (!String.IsNullOrWhiteSpace(keyword))
query = query.Where(p => p.ProductName.Contains(keyword));

if (categoryId.HasValue && categoryId > 0)
query = query.Where(p => p.CategoryID == categoryId);

return query;
}

Maintenant lorsque la page s'exécute, les utilisateurs peuvent sélectionner une catégorie dans la liste déroulante, et le contrôle GridView est automatiquement mis à jour pour afficher les données filtrées. Ceci est possible car la lisaison de modèle piste les valeurs des paramètres pour les méthodes de sélection et détecte si toute valeur du paramètre a changé après une publication. Si oui, alors la liaison de modèle force le contrôle de données associé à se recharger :


Encodage HTML des expressions de liaison de données

Dans ASP.NET 4.5 il est maintenant possible d'encoder en HTML les expressions de liaison de données. C'est une petite nouveauté, mais bien utile. Pour ce faire, il suffit d'ajouter ':' à la fin du préfixe <%# pour marquer l'expression à encoder. Exemple ci-dessous :

<asp:TemplateField HeaderText="Name">
<ItemTemplate><% #:Item.Products.Name %></ItemTemplate>
</asp:TemplateField>

 

Unobtrusive Validation (ou validation discrète)

Lors de la sortie d'ASP.NET MVC 3, j'évoquais le javascript discret (Unobtrusive Javascript) dans ce billet. Et bien le support du javascript discret commence à faire son chemin pour les Web Forms également. Vous pouvez maintenant configurer les validators pour qu'ils utilisent la validation discrète, pour la partie cliente de la validation. Cela permet de franchement réduire le code javascript dans le markup de nos pages, et facilite donc la lecture du code (on est encore loin de Razor avec MVC... mais c'est toujours ça !). Il y a plusieurs façon de configurer les contrôles de validation pour qu'ils utilisent le javascript discret ;

  • Dans le fichier Web.config pour que ce soit paramétré pour toute l'application :
  • <add name="ValidationSettings:UnobtrusiveValidationMode" value="WebForms" />
  • Même chose mais en passant par le Global.asax, via la propriété statique System.Web.UI.ValidationSettings.UnobtrusiveValidationMode.
  • Ou bien le paramétrer pour une page précise, via la propriété UnobtrusiveValidationMode de la classe Page.

 

HTML5

  • La version 4.5 d'ASP.NET Web Forms apporte également des améliorations pour tirer parties des nouvelles fonctionnalités d'HTML5 :
  • Le contrôle TextBox a vu sa propriété TextMode mise à jour afin de gérer les nouveaux types de saisies d'HTML5 comme email, datatime et d'autres encore.
  • Le contrôle FileUpload supporte maintenant l'upload multiple de fichiers avec les navigateurs supportant cette fonctionnalité HTML5.
  • Les contrôles validators supportent maintenant la validation des élements de saisie HTML5.
  • Le contrôle UpdatePanel supporte maintenant la soumission de champs de saisie HTML5.
  • Bien entendu, ces nouveautés concernent les contrôles ASP.NET Web Forms. Visual Studio 11 apporte lui aussi des nouveautés pour faciliter la vie lors du développement d'application HTML5 avec ASP.NET

 

C'est tout pour aujourd'hui. Pour rappel vous pouvez également consulter les nouveautés du runtime ASP.NET 4.5. Dans un prochain article je reviendrais cette fois sur les nouveautés d'ASP.NET MVC 4.

Tags: , , ,

Commentaires

1.
trackback Nicolas Esprit says:

[ASP.NET] Introduction MVC 4 - Part 2 : Un peu d'histoire

[ASP.NET] Introduction MVC 4 - Part 2 : Un peu d'histoire

2.
trackback ASP.NET Français Blogs says:

[ASP.NET] Introduction MVC 4 - Part 3 : MVC c'est quoi ? Quels avantages ?

Le patron de conception Modèle-Vue-Contrôleur est un patron de conception architectural

Les commentaires sont clos