mai 30 2010

[ASP.NET 4.0] Découverte du QueryExtender

Category: ASP.NET | Linq To Sql | ASP.NET 4Nicolas Esprit @ 22:43

Après relecture de l’article de Philippe Vialatte et Louis-Guillaume Morand sur Les nouveautés d’ASP.NET 4.0, je viens de m’apercevoir que j’avais complètement zappé l’existence d’une nouveauté géniale : le contrôle QueryExtender. En lisant sa description dans l’article, j’ai décidé d’aller plus loin en me documentant via MSDN et de tester toutes les possibilités qu’il offre… et je peux vous dire que l’essayer c’est l’adopter !

Pour commencer revenons sur la définition du QueryExtender introduite dans l’article :

La façon de retourner un set de données a beaucoup évolué ces dernières années, en imposant peu à peu l'utilisation d'ORM, puis via le langage de requêtage objet Linq. Ainsi, la façon de filtrer des données Linq ou Entity est devenue extrêmement simple grâce à ces requêtes objet, mais ne répond pas pourtant à tous les besoins courants des sites Web.
Prenons un exemple simple, une grille affichant la liste des utilisateurs et une textbox permettant de faire une recherche sur ces derniers. Pour le moment, rien de complexe puisqu'il suffit de créer une fonction qui génère une requête filtrée. Maintenant, imaginons que nous pouvions nous passer de ce code superflu tout en gardant cette possibilité de filtre. Ne serait-il pas merveilleux? Et bien c'est maintenant possible grâce au contrôle QueryExtender.

Dans la plupart des applications Web que j’ai été amené à développer, on utilisait très souvent des GridView avec de multiples filtres. Que de temps perdu à coder ces filtres et offrir la possibilité à l’utilisateur de visionner uniquement les données qui l’intéresse. Avec le QueryExtender, c’est d’une facilité enfantine.

Pour tester les différentes fonctionnalités offertes, j’ai simplement créé une nouvelle Web Application dans Visual Studio 2010 et ai utilisé la base de données AdventureWorks pour SQL Server 2008 (elle est disponible sur codeplex, et pour information la version pour SQL Serveur 2008 R2 est désormais disponible également). Dans ma page j’ai ajouté une GridView ainsi qu’une LinqDataSource pointant sur la table Product comme indiqué sur le code ci-dessous :

<asp:GridView ID="GridView1" runat="server" DataSourceID="LinqDataSource1" Width="100%"  DataKeyNames="ProductID"  
        AllowPaging="True" AllowSorting="true" AutoGenerateColumns="false">
        <Columns>
            <asp:BoundField DataField="ProductNumber" HeaderText="Product Number" SortExpression="ProductNumber" />
            <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
            <asp:BoundField DataField="Size" HeaderText="Size" SortExpression="Size" />
            <asp:BoundField DataField="Color" HeaderText="Color" SortExpression="Color" />
            <asp:BoundField DataField="StandardCost" HeaderText="Standard Cost" SortExpression="StandardCost" />
            <asp:BoundField DataField="ListPrice" HeaderText="List Price" SortExpression="ListPrice" />
            <asp:CheckBoxField DataField="MakeFlag" HeaderText="Make Flag" SortExpression="MakeFlag" />
        </Columns>
    </asp:GridView>
    <asp:LinqDataSource ID="LinqDataSource1" runat="server" 
        ContextTypeName="QueryExtenderTest.AdventureWorksDataContext" 
        EntityTypeName="" TableName="Products">
    </asp:LinqDataSource>

 

SearchExpression

Supposons maintenant qu’il soit nécessaire d’ajouter une fonctionnalité de filtre sur le nom des produits. On aura tout d’abord besoin d’une TextBox ainsi que d’un boutton comme ceci :

Filter Results by Name: <asp:TextBox ID="SearchTextBox" runat="server" ></asp:TextBox>
<asp:Button ID="btnFilter" runat="server" Text="Filter" /> 

Ensuite pour indiquer à notre LinqDataSource qu’on souhaite filtrer ces données, on ajoute le contrôle QueryExtender à notre page. Enfin on utilise une SearchExpression :

<asp:QueryExtender ID="QueryExtender" runat="server" TargetControlID="LinqDataSource1">
    <asp:SearchExpression SearchType="StartsWith" DataFields="Name">
        <asp:ControlParameter ControlID="SearchTextBox" />
    </asp:SearchExpression>
</asp:QueryExtender>

La classe SearchExpression compare la chaîne spécifiée dans notre TextBox “SearchTextBox” à la valeur Name de nos produits. L'expression peut exécuter une recherche de type « commence par », « contient » ou « se termine par ». Le type de recherche est à spécifier avec la property SearchType. Dans notre cas, nous utilisons StartsWith, les autres valeurs possibles sont : Contains et EndsWith.

Note : si le fournisseur LINQ que vous utilisez dans le contrôle QueryExtender prend en charge le respect de la casse, vous pouvez activer ou ignorer cette fonction en employant la propriété ComparisonType.

 

RangeExpression

Supposons maintenant qu’il soit nécessaire d’ajouter une fonctionnalité de filtre sur les coûts des produits avec une borne inférieure et une borne supérieure, on utilisera dans ce cas une RangeExpression. Nous pouvons ajouter deux TextBoxs pour la saisie comme ci-dessous :

Filter Results by Standard Cost between :<asp:TextBox ID="FromTextBox" runat="server" Width="50px"></asp:TextBox>
and <asp:TextBox ID="ToTextBox" runat="server" Width="50px"></asp:TextBox>

Enfin, on utilise dans la RangeExpression dans le QueryExtender comme ceci :

<asp:QueryExtender ID="QueryExtender" runat="server" TargetControlID="LinqDataSource1">
    <asp:RangeExpression DataField="StandardCost" MinType="Inclusive"  
        MaxType="Inclusive">
      <asp:ControlParameter ControlID="FromTextBox"/>
      <asp:ControlParameter ControlID="ToTextBox"/>
    </asp:RangeExpression>
</asp:QueryExtender>

Comme pour une SearchExpression, la classe RangeExpression nécessite qu’on lui spécifie le DataField concerné, ainsi que deux ControlParameters pour références les contrôles de saisies des bornes Min et Max. Il est également possibles de spécifier grâces aux properties MinType et MaxType quel type de comparaison sera utilisé avec les bornes. Ces properties peuvent prendre trois valeurs possibles : None, Inclusive et Exclusive. Il est assez claire que Exclusive correspond à une comparaison du type Min < value < Max, tandis que Inclusive correspond à Min <= value <= Max. Par défaut, ces propriétés sont à None, c’est à dire qu’aucune comparaison a lieu.

 

PropertyExpression et MethodExpression

Les PropertyExpressions et MethodExpressions ont un fonctionnement semblable à RangeExpression, elles permettent de filtrer les données en comparant une valeur avec celle de la propriété filtrée. Par exemple, avec une PropertyExpression, nous allons pouvoir filtrer les lignes dont la valeur booléenne MakeFlag est à true ou false. Pour ce faire, il suffit d’une CheckBox et de rajouter une PropertyExpression :

Make Flag: <asp:CheckBox ID="MakeCheckBox" runat="server" />
<asp:QueryExtender ID="QueryExtender" runat="server" TargetControlID="LinqDataSource1">
    <asp:PropertyExpression>
        <asp:ControlParameter ControlID="MakeCheckBox" Name="MakeFlag" />
    </asp:PropertyExpression>
</asp:QueryExtender>

Il est bien évidemment possible d’utiliser plusieurs PropertyExpressions dans un même QueryExtender. La requête finale concaténera les différentes expressions avec l’opérateur booléen AND. Au passage notez qu’il n’est pas obligatoire d’utiliser un ControlParameter, on peut très bien spécifier une valeur comme ci-dessous :

<asp:PropertyExpression>
    <asp:Parameter Type="String" DefaultValue="Blue"  Name="Color" />
</asp:PropertyExpression>

Les MethodExpressions vont nous permettre d’appeler un méthode afin de filtrer les données. Ce type d’expression nous offre la possibilité d’utiliser d’autres requêtes Linq à l’intérieur de notre méthode ou une autre source de données. Egalement, cela nous permet de requêter une couche Business (en renseignant la property TypeName) et non plus de filtrer les données métier dans la partie UI. Le fonctionnement est simple :

<asp:MethodExpression MethodName="FilterProductsByPrice"></asp:MethodExpression>
public static IQueryable<Product> FilterProductsByPrice(IQueryable<Product> query)
{
    return from p in query
           where p.ListPrice >= 400
           select p;
}

 

CustomExpression

CustomExpression a le même fonctionnement qu’une MethodExpression :

<asp:CustomExpression OnQuerying="FilterProductsByColor"></asp:CustomExpression>
protected void FilterProductsByColor(object sender, CustomExpressionEventArgs e)
{
    e.Query = from p in e.Query.Cast<Product>()
              where p.Color == "Black"
              select p;
}

Notez toutefois une différence majeure entre les deux. MethodExpression nous permet d’appeler une méthode static qui reçoit en paramètre une collection IQueryable (de produits dans notre exemple) et renvoie une collection filtrée IQueryable. Pour une CustomExpression c’est différent, il s’agit là d’un évènement (OnQuerying). Dans les deux cas, il est possible de passer des paramètres aux méthode grâce à la balise Parameter.

 

OrderByExpression

Ce type d’expression permet de trier les données, mais également de le faire sur plusieurs champs grâce à la balise ThenBy. Ainsi dans l’exemple suivant le tri est effectué de manière descendante sur les prix des produits, puis de manière ascendante sur leur nom.

<asp:OrderByExpression DataField="ListPrice" Direction="Descending">
    <asp:ThenBy DataField="Name" Direction="Ascending" />
</asp:OrderByExpression>

Il existe également deux FilterExpressions spécifiques à ASP.NET Dynamic Data : ControlFilterExpression et DynamicFilterExpression.

Les sources utilisées dan ce billet sont disponibles ici (elles sont simples, mais peuvent vous faire gagner du temps si vous désirez tester par vous même).

Tags: , , ,

Les commentaires sont clos