Accès aux données générique avec Linq To SQL et C#

25. February 2010 by Pierre-Luc.ROUAYS

Accès aux données générique avec Linq To SQL et C#

Introduction :

Cet article permet de construire une couche d’accès aux données générique en utilisant LINQ To SQL. Ceci permettra par la suite d’effectuer les opérations basiques du CRUD. Nous créerons une classe nommée EntityCrud qui implémentera ces fonctionnalités.

Vous pourrez par la suite créer des Adapters pour chacune de vos classe dérivant ce cette classe.

 

Place à l’action !

Nous allons créer une simple solution contenant 2 projet : le programme et la couche d’accès aux données (DAL).

 

 

Ajouter un élément de type classe LINQ To SQL :

Puis ensuite ajouter les Tables à votre projet par le biais de l’explorateur de solution.

Code Projet MonProgramme.DAL

La classe EntityCrud contient tout le code nécessaire pour communiquer avec la base de donnée par le biais de LinqToSQL.

Voici la classe AdapterFactory :

 

 

using System;

using System.Collections;

using System.Collections.Specialized;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Linq;

using System.Data.Linq;

using System.Data.Linq.Mapping;

using System.Text;

using System.ComponentModel;  

The next section contains the namespace and class declarations. 

namespace MonProgramme.DAL

{

 

    /// <summary>

    /// Generic Interface to Database

    /// using LINQ to SQL

    /// </summary>

          public class EntityCrud

          {

 

 

Première fonction permettant de récuperer un objet par sa clé Primaire.

               /// <summary>

                   /// Select by Primary Key Value

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="id">The PK value to search for</param>

                   /// <returns>Single matching PK to id</returns>

                   public static T SelectByPK<T>(String id) where T : class

                   {

                             try

                             {

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                              // Récupère la table du type passé

                                                var table = context.GetTable<T>();

 

                              // Récupère le mapping de l’objet avec la Base

                                                MetaModel modelMap = table.Context.Mapping;

 

                              // Récupère les members de l’objet

                              ReadOnlyCollection<MetaDataMember> dataMembers

                              = modelMap.GetMetaType(typeof(T)).DataMembers;

 

                              // Cherche le nom du champ de la clé primaire

                              string pk = 

                              (dataMembers.Single<MetaDataMember>(m =>

                              m.IsPrimaryKey)).Name;

 

                              // Retourne l’objet unique qui correspond à cette clé primaire

                              // passée en argument

                                    return table.SingleOrDefault<T>(delegate(T t)

                                    {

                                    String memberId =

      t.GetType().GetProperty(pk).GetValue(t,

                                    null).ToString();

                                    return memberId.ToString() == id.ToString();

                                    }

      );

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

 

Seconde méthode permettant de récupérer toutes les objets de la base.

 

        /// <summary>

        /// Select All by Type

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <returns>Matching table as typed list</returns>

        public static IList<T> SelectAll<T>() where T : class

        {

            try

            {

                using (NWindDataContext context = new NWindDataContext())

                {

                    // Récupère la table qui correspond au type

                    // et retourne tous les enregistrement sous forme de liste

                    var table = context.GetTable<T>().ToList<T>();

                    return table;

                }

            }

            catch (Exception)

            {

                throw;

            }

        }

 

Méthode d’insertion de donnée dans la base.

                   /// <summary>

                   /// Insert an Object into a Table

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item"></param>

                   public static void Insert<T>(T item) where T : class

                   {

                             try

                             {

                // Récupère la table du type passé en argument

                // Insert l’enregistrement et soumet les changements à la Base

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                              // Récupère la table correspondante

                                                var table = context.GetTable<T>();

 

                              // Passe l’objet à la method InsertOnSubmit

                              // et Soumet les changements

                                                table.InsertOnSubmit(item);

                              context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

              }

 

Méthode de mise à jour de donnée.

 

       /// <summary>

                   /// Update an Existing Object

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item">The object to update</param>

                   public static void Update<T>(T item) where T : class

                   {

                             try

                             {

                        // Crée une nouvelle instance de l’objet

                                      Object newObj = Activator.CreateInstance(typeof(T),    

                        new object[0]);

 

                                      PropertyDescriptorCollection originalProps =

                        TypeDescriptor.GetProperties(item);

 

                        // Donne la valeur de la donnée passée en argument à l’objet

                                      foreach (PropertyDescriptor currentProp in

                        originalProps)

                                      {

                                                if (currentProp.Attributes[typeof(

                              System.Data.Linq.Mapping.ColumnAttribute)] !=

                              null)

                                                {

                                                          object val = currentProp.GetValue(item);

                                                          currentProp.SetValue(newObj, val);

                                                }

                                      }

 

                        // Lie l’objet à un nouveau context de donnée

                        // et soumet les changement à la Base.

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                                                var table = context.GetTable<T>();

                                                table.Attach((T)newObj, true);

                                                context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

Code de suppression d’un objet.

               /// <summary>

                   /// Remove an object

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item"></param>

                   public static void Remove<T>(T item) where T : class

                   {

                             try

                             {

                        // Crée une instance de l’objet avec le type passé en argument

                                      Type tType = item.GetType();

                                      Object newObj = Activator.CreateInstance(tType, new 

                        object[0]);

 

                        // Récupère les proprieties de l’objet passé en argument

                                      PropertyDescriptorCollection originalProps =

                        TypeDescriptor.GetProperties(item);

 

                        // Copie le contenue de la donnée passée en argument

                        // au nouveau objet du meme type

                                      foreach (PropertyDescriptor currentProp in

                        originalProps)

                                      {

                                                if (currentProp.Attributes[typeof(

                              System.Data.Linq.Mapping.ColumnAttribute)] !=

                              null)

                                                {

                                                          object val = currentProp.GetValue(item);

                                                          currentProp.SetValue(newObj, val);

                                                }

                                      }

 

                        //lie l’objet à la table, appel de la function deleteOnSubmit

                        // et soumet les changement à la base.

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                                                var table = context.GetTable<T>();

                              table.Attach((T)newObj, true);

                                                table.DeleteOnSubmit((T)newObj);

                                                context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

Utilisation :

Une fois l’EntityCrud créé vous pouvez l’utiliser comme dans les exemples suivant :

Exemple Insert :

           try

                             {

                             Customer c = new Customer();

                             c.CustomerID = "AAAAA";

                             c.Address = "554 Westwind Avenue";

                             c.City = "Wichita";

                             c.CompanyName = "Holy Toledo Bibles";

                             c.ContactName = "Frederick Flintstone";

                             c.ContactTitle = "Boss";

                             c.Country = "USA";

                             c.Fax = "316-335-5933";

                             c.Phone = "316-225-4934";

                             c.PostalCode = "67214";

                             c.Region = "EA";

 

                             Order_Detail od = new Order_Detail();

         od.Discount = .25f;

                             od.ProductID = 1;

                             od.Quantity = 25;

                             od.UnitPrice = 25.00M;

 

                             Order o = new Order();

                             o.Order_Details.Add(od);

                             o.Freight = 25.50M;

                             o.EmployeeID = 1;

                             o.CustomerID = "AAAAA";

 

                             c.Orders.Add(o);

 

                             EntityCrud.Insert<Customer>(c); //INSERT Customer mais aussi les autres champs qui lui sont associés (order, order_detail).

 

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

 

Les données seront toutes insérée en base : Le Customer, L’order et l’order_detail avec seulement cet appel : EntityCrud.Insert<Customer>(c);

Exemple Delete :

                             try

                             {

                             Customer doa = EntityCrud.SelectByPK<Customer>("AAAAA");

                                     EntityCrud.Remove<Customer>(doa); //DELETE

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

Exemple Update :

 

         try

                             {

                                      var q = EntityCrud.SelectByPK<Customer>("AAAAA");

 

                                      q.City = "Calgary";

                                      q.Country = "Canada";

                                      q.Address = "100 Longhorn Drive";

                                      q.CompanyName = "Holy Moses Bibles";

                                      q.Region = "EE";

                                      q.ContactTitle = "Gopher";

                                      q.ContactName = "Moxie Peroxide";

 

                                      EntityCrud.Update<Customer>(q);//UPDATE

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

 

ADO.NET, ASP.NET AJAX, C#, WPF , , , ,

Trucs et astuces WPF

Voilà une compilation de divers trucs et astuces WPF récoltés aux quatre coins du web, ainsi que la présentation d’outils et de composant pouvant améliorer grandement le développement de vos applications.

Comportement

Comment se lier à une propriété définie dans les settings ?

Il est bien pratique d’utiliser les Settings disponibles depuis Visual Studio 2005 pour définir des paramètres qui seront accessibles directement depuis le code, après que leur valeur ait été instanciée au démarrage de l’application.

Seulement, on pourrait se demander comment accéder à ces paramètres dans le code XAML et il s’avère que cela n’est pas très compliqué. Après avoir définit vos paramètres:

clip_image001

Il suffit de mapper un namespace XML pointant vers le namespace CLR définissant vos propriétés:

xmlns:properties="clr-namespace:TestSettings.Properties"

Puis, dans votre code XAML, accéder à votre propriété en utilisant la syntaxe suivante:

x:Static properties:Settings.Default

Ce qui donne, au final, un code ressemblant à ceci:

<ListBox x:Name="lb"
ItemsSource="{Binding Source={x:Static properties:Settings.Default}, Path=Names}" />


Simple et efficace.

Auteur : Thomas Lebrun

Site : http://blogs.codes-sources.com/tom/

 

Héritage de la classe Window :

Imaginons que nous avons comme projet de faire une jolie application WPF, avec des fenêtres personnalisées. Problème : on supprime la barre de titre Windows, donc la possibilité de déplacer la fenêtre en la cliquant et la déplaçant. Il faut donc implémenter ce comportement à toutes nos fenêtres.
En windows form, on pouvait très facilement faire une fenêtre mère dont hériterait des fenêtres enfant afin d’avoir par exemple le même comportement.
Mais en WPF, on ne peut pas le faire aussi simplement.

En effet, si on fait hériter notre classe Window1 de notre fenêtre mère MotherWindow dans le fichier Window1.xaml.cs comme ceci :

public partial class Window1 : MotherWindow

Le compilateur va nous retourner une erreur, car l’élément racine du fichier xaml correspondant précise la classe mère dont doit hériter la classe Window1.
Il suffit donc de le changer lui aussi, en définissant aussi un espace de nom dans le fichier xaml :

<local:MotherWindow 
 x:Class="Heritage.Window1"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="clr-namespace:Heritage"
 Title="Window1" Height="300" Width="300">
  <Grid>
  </Grid>
</local:CloseMove>


Sans oublier bien sûr de préciser parmi les espaces de noms que local correspond à notre assembly.

Auteur : Jérémie Bertrand

Site : http://blog.developpez.com/laedit/

 

Comment être notifié (et réagir) lorsqu’une DependencyProperty interne à WPF est modifiée

Lorsque l’on définit ses propres DependencyProperties, on a la possibilité de définir une Callback qui est appelée lorsque la propriété est modifiée.

Cependant, on peut vouloir faire la même chose, au niveau d’un CustomControl, sur une DependencyProperty définie sur l’une des classes disponibles dans le framework WPF (ActualWidthProperty, HeightProperty, etc…)

Pour cela, il est nécessaire d’utiliser la syntaxe suivante:

public MonControl()
{
DependencyPropertyDescriptor.FromProperty(WidthProperty, 
 typeof(MonControl)).AddValueChanged(this, delegate
{
MessageBox.Show("La valeur a été modifiée !");
});
}

Comme vous pouvez le voir, on fait appel à la méthode FromProperty de la classe DependencyPropertyDescriptor qui permet d’accéder au PropertyDescriptor (métadonnées) de la propriété spécifiée (WidthProperty dans notre cas). Ensuite, on appelle la méthode AddValueChanged en lui passant, tout simplement, un délégué vers la méthode qui sera invoquée lorsque la propriété sera modifiée.

Auteur : Thomas Lebrun

Site : http://blogs.codes-sources.com/tom/

 

Comment assigner une ressource dynamique depuis le code-behind ?

Dans le cadre d’un développement WPF, on est très souvent amené à assigner des ressources à des contrôles/composants de l’interface graphique. Lorsque l’on fait cet assignement en XAML, rien de plus simple: il suffit d’utiliser la MarkupExtension StaticResource (ou DynamicResource, si la ressource est amené à être modifiée).

<Button Content="Find Position" Click="Button_Click" Background="{DynamicResource brush}" />

Mais qu’en est-il des cas où l’on souhaite faire ce genre de chose depuis le code behind ? Dans ce cas, il convient d’utiliser la méthode SetResourceReference (http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.setresourcereference.aspx):

<Window.Resources>
  <SolidColorBrush x:Key="brush" Color="Red" />
</Window.Resources>
<Button x:Name="btn"Content="Find Position" Click="Button_Click" />

this.btn.SetResourceReference(BackgroundProperty, "brush");

Comme vous pouvez le voir, c’est relativement simple à mettre en place: on définit la ressource, on définit le contrôle puis, dans le code behind, on appelle la méthode SetResourceReference en lui passant en paramètres:

  • la Dependency Property sur laquelle on va appliquer la ressource
  • le nom de la ressource

Auteur : Thomas Lebrun

Site : http://blogs.codes-sources.com/tom/

 

 

 

 

Découvrons la collection CompositeCollection

Je dois reconnaitre que j'ai découvert très récemment cette collection mais elle semble être bien pratique.

En effet, la CompositeCollection vous permet de mélanger plusieurs collections et éléments de façon à ce qu'ils soient affichés comme une seule et même liste.

Par exemple, il arrive souvent que l'on rencontre des gens se demandant comment rajouter un élément vide dans une ComboBox qui est bindée à une ObservableCollection. Pour cela, la première idée serait de travailler directement sur l'ObservableCollection utilisée comme source de données.

L'autre technique est de passer par cette fameuse CompositeCollection soit:

<ComboBox x:Name="TheCombo" DisplayMemberPath="Name">
  <ComboBox.ItemsSource>
    <CompositeCollection>
      <CollectionContainer Collection="{Binding Source={StaticResource TheItems}}" />
      <ComboBoxItem Content="" />
    </CompositeCollection>
  </ComboBox.ItemsSource>
</ComboBox>

C'est simple et efficace mais attention tout de même à ne pas en abuser, afin de conserver un code un tant soit peu correctement organisé.

Auteur : Thomas Lebrun

Site : http://blogs.codes-sources.com/tom/

 

Localiser un composant en C#

Imaginons la fenêtre XAML suivante :

<Windowx:Class="Exemple1" 
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="clr-namespace:Exemple1"
 Title="Exemple1" Height="Auto" Width="Auto">
  <StackPanel>
<ListView x:Name="Liste">
<ListViewItem Content="Test 01" />
<ListViewItem Content="Test 02" />
<ListViewItem Content="Test 03" />
</ListView>
</StackPanel>
</Window>

Comment faire pour récupérer la position de Liste en C# ?

Dans notre code nous allons faire appel au composant primaire Canvas :

Double posX = Canvas.GetLeft(this.Liste) ;
Double posY = Canvas.GetTop(this.Liste) ;

Le tour est joué.

Bien évidement les fonctions setLeft et setTop permettront de repositionner le composant.

Auteur : UNi

Blog : http://blog.developpez.com/index.php?blog=167

 

Scroll + Drag&Drop avec une listbox

Voici une petite astuce pour pouvoir dans une même listbox utiliser la barre de scroll avec la souris et utiliser le drag&drop (je vous renvoi à l'excellent article de Thomas Lebrun pour la mise en place du drag&drop http://blogs.developpeur.org/tom/archive/2006/06/08/21434.aspx)

Le principe est simple

J'ai ma listbox dans laquelle j'ai implémenté le drag&drop

<ListBox MouseMove="MouseMoveMethode" AllowDrop="True" 
 SelectionMode="Extended" Width="440" Height="250" 
 x:Name="LBIVueFile" ItemsSource="{Binding}" 
 Background="#FFA9C8FF" />

Si je souhaite utiliser la barre de scroll avec la souris il va croire que je suis en train d'effectuer un drag&drop ce qui n'est pas du tout le cas. Pour éviter d'avoir ce problème il suffit d'entourer la listbox avec un scrollviewer, ce qui nous donne ceci :

<ScrollViewer CanContentScroll="True" Width="440" Height="230" 
 VerticalScrollBarVisibility="Auto"> 
  <ListBox MouseMove="MouseMoveMethode" AllowDrop="False" 
   SelectionMode="Extended" Width="440" x:Name="LBIVueFile" 
   ItemsSource="{Binding}" Background="#FFA9C8FF" /> 
</ScrollViewer>

Auteur : UNi

Site : http://blog.developpez.com/index.php?blog=167

 

Animation

Storyboard :

Il arrive parfois que l’on veuille faire référence à une propriété d’un enfant d’une Window, d’une Page ou d’un UserControl dans un Storyboard situé dans les ressources de celui-ci.
Pour y arriver, il suffit de préciser l’attribut Storyboard.TargetName avec le nom x:Name de l’enfant et ensuite de préciser sa propriété comme on le ferait normalement dans l’attribut Storyboard.targetProperty d’une balise AnimationUsingKeyFrames :

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
 Storyboard.TargetName="border" Storyboard.TargetProperty="Background">

En ayant bien sûr précisé le x:Name de la Border :

<Border x:Name="border">

Et pour appeler un Storyboard défini dans un fichier XAML à partir du code, il suffit d’utiliser la méthode beginStoryboard avec le nom du Storyboard :

this.BeginStoryboard((Storyboard)this.FindResource("myStoryboard"));

Au final, rien de bien compliqué en soit, mais que l'on peut chercher durant un moment si on ne le sait pas.

Auteur : Jérémie Bertrand

Site : http://blog.developpez.com/laedit/

 

Interface graphique

Comment faire pour masquer un contrôle, plutôt que de le désactiver, si la commande qui lui est liée est désactivée ?

Pour cela, on va commencer par déclarer un petit converter, BooleanToVisibilityConverter. Attention, pas besoin de le redéfinir soi-même: celui-ci est disponible de base dans WPF.

<BooleanToVisibilityConverter x:Key="convVisibility" />

Ensuite, il ne reste plus qu’à faire un peu de binding sur la propriété Visibility du contrôle, par rapport à SA propriété IsEnabled:

<Button Content="Supprimer" Command="ApplicationCommands.Delete"
Visibility="{Binding IsEnabled, RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource convVisibility}}"/>

Simple mais terriblement efficace !

Auteur : Thomas Lebrun

Site : http://blogs.codes-sources.com/tom/

 

 

 

Afficher toutes les couleurs disponible avec WPF

Pour cela c’est très simple. On va d’abord déclarer une méthode qui va nous servir à récupérer les couleurs :

public PropertyInfo[] GetColors(Type type) 
{ 
  return type.GetProperties(); 
} 


Et ensuite, on l’utilise en tant que ObjectDataProvider et on affiche les couleurs dans une combobox :

<Window.Resources>
  <ObjectDataProvider x:Key="colors" MethodName="GetColors" ObjectType="{x:Type local:Tools}">
    <ObjectDataProvider.MethodParameters>
<x:Type TypeName="Colors"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate x:Key="dTemplate">
<StackPanel Orientation="Horizontal">
<Rectangle Width="16" Height="12" Fill="{Binding Name}" Stroke="#FF000000"/>
<TextBlock Margin="1" Text ="{Binding Name}" Foreground="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource colors}}" 
   ItemTemplate="{DynamicResource dTemplate}" Margin="50,50,50,0" VerticalAlignment="Top" />
</Grid>

N'oubliez pas de mettre le bon namespace.

Auteur : Nicolas Biedermann

Blog : http://blogs.developpeur.org/bidou/

Site : http://www.nicolasbiedermann.ch

 

Afficher toutes les fonts disponibles avec leurs représentations

Il vous suffit de placer ce code XAML dans votre fenêtre :

<Grid>
   <ComboBox Width="120" Height="25" x:Name="FontSelector" ItemsSource="{x:Static Fonts.SystemFontFamilies}">
      <ComboBox.ItemTemplate>
         <DataTemplate>
            <TextBlock Text="{Binding}" FontFamily="{Binding}"/>
         </DataTemplate>
      </ComboBox.ItemTemplate>
   </ComboBox>
</Grid

 

Auteur : Nicolas Biedermann

Blog : http://blogs.developpeur.org/bidou/

Site : http://www.nicolasbiedermann.ch

 

 

Utilisez le ribbon dans vos applications WPF

Vous avez déjà surement remarqué le ruban dans les applications office :

clip_image002

Mais comment faire pour intégrer rapidement le même composant dans vos applications wpf ?

Microsoft vient de mettre à disposition une librairie à télécharger ici.

Comment l’utiliser ? Tout d’abord insérer le projet téléchargé ci-dessus et ajouter une référence à votre projet initial.

Ensuite il suffit d’utiliser la librairie:

  • Déclaration de l’espace de nom
xmlns:c="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"

  • Charger l’un des nombreux thèmes graphiques présents dans la librairie:
<Window.Resources> 
  <ResourceDictionary> 
    <ResourceDictionary.MergedDictionaries> 
      <ResourceDictionary x:Name="rdTheme" Source="/RibbonControlsLibrary;component/Themes/Office2007Silver.xaml" /> 
    </ResourceDictionary.MergedDictionaries> 
  </ResourceDictionary> 
</Window.Resources>

  • Puis utilisez les composants, par exemple:
<c:Ribbon x:Name="mainRibbon" />

Auteur : Julien Dollon

Site : http://blogs.dotnet-france.com/juliend

 

 

Créer un effet de transparence en WPF

A l’occasion de la création d’un simple menu, j’ai souhaité avoir un effet de transparence sur mes boutons:

clip_image003

Nous allons voir comment réaliser l’effet de transparence.

Tout d’abord voici à quoi ressemble le template, très basique, des boutons ci-dessus:

<StackPanel OpacityMask="{x:Null}" Cursor="Hand" 
 x:Name="stackPanel" RenderTransformOrigin="0.5,0.5">
<Grid x:Name="MaGrid" Height="50" Width="Auto">
<ContentPresenter />
</Grid>
</StackPanel>

Je vais ensuite rajouter un rectangle en dessous de ma grid, y insérer un virtualbrush qui copiera le contenu de ma grid. Puis j’appliquerai une transformation à mon rectangle afin de le retourner et lui donner un effet de transparence.

Voici le code à ajouter entre </Grid> et </StackPanel>

<Rectangle Height="50" RenderTransformOrigin="0.5,0.5">
  <Rectangle.OpacityMask>
<LinearGradientBrush SpreadMethod="Reflect" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop Color="#B3FFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
</TransformGroup>
</Rectangle.RenderTransform>
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=MaGrid}" Stretch="Uniform"/>
</Rectangle.Fill>
</Rectangle>

Bon rien de bien compliqué mais toujours bon de revoir ses classiques.

Auteur : Julien Dollon

Site : http://blogs.dotnet-france.com/juliend

 

Changer le curseur de vos applications

Il suffit de créer un Canvas contenant votre nouveau curseur (une image par exemple) intégré au canvas principal.

Mettre l’utilisation du curseur par défaut à None:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Cursor.Page" Width="640" Height="480">
<Canvas x:Name="LayoutRoot" Background="#FFFFFFFF"
MouseEnter="LayoutRoot_MouseEnter" 
   MouseMove="LayoutRoot_MouseMove" Cursor="None">
<Canvas Height="20" Width="20" x:Name="NewCursor" Visibility="Visible"
     Canvas.Left="310" Canvas.Top="230">
<Image Height="Auto" Width="20" Source="newcursor.png" Stretch="Fill"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Canvas>
</Canvas>
</UserControl>

Et s’abonner aux événements MouseEnter, MouseLeave:

private void LayoutRoot_MouseEnter(object sender, MouseEventArgs e)
{ 
PointMousePoint=e.GetPosition(this);
Canvas.SetLeft(NewCursor,MousePoint.X);
Canvas.SetTop(NewCursor,MousePoint.Y);
} 
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{ 
PointMousePoint=e.GetPosition(this);
Canvas.SetLeft(NewCursor,MousePoint.X);
Canvas.SetTop(NewCursor,MousePoint.Y);
}

private void LayoutRoot_MouseEnter(object sender, MouseEventArgs e) 
{ 
Point MousePoint = e.GetPosition(this); 
Canvas.SetLeft(NewCursor, MousePoint.X); 
Canvas.SetTop(NewCursor, MousePoint.Y); 
} 

clip_image004

Auteur : julien Dollon

Site : http://blogs.dotnet-france.com/juliend

 

Astuces sur les ListBoxItems

Par défaut, lorsque vous allez vous positionner sur un des ListBoxItem, sa couleur de fond passera en bleu de la même façon que si votre application perd le focus, les ListBoxItem sélectionnés auront un font gris pâle :

clip_image005clip_image006

Pour éviter ce genre de comportement, vous allez devoir redéfinir deux Brush système qui sont utilisé automatiquement lorsque le ListBoxItem est sélectionné. Vous pouvez faire cette manipulation dans les ressources du style de vos ListBoxItem :

<Style.Resources>
  <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
  <SolidColorBrush x:Key="{x:Static SystemColors.ControlsBrushKey}" Color="Transparent"/>
</Style.Resources>

La couleur HighlightBrushKey est le bleu affiché lorsque l’item est sélectionné (ici passé à transparent) et ControlBrushKey est le gris pâle de l’image de droite si dessus (ici passé à transparent également). Dès lors, deuxième problème résolu !

Un autre problème qui survient lorsque vous naviguez dans vos ListBoxItem à l’aide du clavier, ceux-ci s’entourent d’une bordure noire en pointillés. Pour éviter l’apparition de celle-ci, il vous suffit tout simplement de modifier le style FocusVisualStyle de votre ListBoxItem ou de le mettre à null :

<ListBoxItem FocusVisualStyle="Null" />

Bien sûr, ces astuces peuvent être utilisées avec n’importe quel contrôle.

Auteur : Julien Corioland

Site : http://blogs.dotnet-france.com/julienc

 

Outils et composants

Word 2007 XAML Generator : permet de générer du XAML à partir de document Word. Très pratique pour inclure de longs textes dans une application.
Site : http://www.codeplex.com/Word2007ToXaml
Gratuit

FluidKit : c'est une bibliothèque de composants WPF assez variés, en passant d'une GlassWindow à un DragDropManager ou un ElementFlow (comme iTunes pour présenter les albums).
Site : http://www.codeplex.com/fluidkit/
Gratuit

XAML Templates : c'est un site qui propose à la vente des templates XAML, afin que les développeurs ne s'embêtent plus avec le design de leur application.
Il y a un sample gratuit qui est d'assez bonne qualité.
Site : http://www.xamltemplates.net/
Payant

IPod Controls : c’est une librairie de contrôle de style "IPod".
Site : http://www.codeplex.com/WpfIpodControls
Gratuit

BookControls : il permet de simuler un livre, en WPF et en silverlight. On peut mettre n'importe quel composant sur ses pages, il les gère.
Site : http://www.codeplex.com/wpfbookcontrol
Gratuit

Pixels Shaders Effect : Librairie d'effets sur des images (entres autres Magnify, Monochrome, Pixelate, Ripple, Swirl, Tone, Toon, and ZoomBlur ) et d'effets de transitions.
Site : http://wpffx.codeplex.com/
Gratuit

WPF Transition Framework (WTF) : une librairie de transition en cours de développement, seulement 4 transitions sont disponibles pour le moment (BlurIn/BlurOut et FadeIn/FadeOut). Mais elle propose le choix de la qualité de l'animation.
Site : http://wtf.nukeation.com/
Gratuit

XCeed Datagrid : XCeed propose une datagrid en version 'express' (gratuit).
Site : http://xceed.com/Grid_WPF_Intro.html
Gratuit

BlackLight : une librairie de contrôles WPF/Silverlight très bien fournis.
Site : http://www.codeplex.com/blacklight
Gratuit

Mole : c'est un visualiseur du debugger qui permet de parcourir le Visual Tree d'une fenêtre ou page WPF. On peut voir toutes les valeurs des propriétés, et même l'origine de la valeur (par défaut, style, binding...), changer les valeurs, voir un aperçu visuel de chaque contrôle.Très pratique pour déboguer des templates.
Site : http://karlshifflett.wordpress.com/mole-for-visual-studio/
Gratuit

AvalonDock : c’est un système de layout permettant d'avoir des onglets "déplaçables", des fenêtres flottantes et des panneaux ancrables/masquables automatiquement.
Site : http://avalondock.codeplex.com/
Gratuit

Visifire : c’est une librairie de contrôles open source WPF/Silverlight de visualisation de données. Elle propose toutes sortes de graphiques, animés ou non.
Site : http://www.visifire.com/
Gratuit

Odyssey : c’est une librairie de contrôles WPF proposant entre autres une OutlookBar, une BreadCrumBar (comme l’explorer de vista), une ExplorerBar ou encore un Ribbon.
Site : http://odyssey.codeplex.com/
Gratuit

 

Et pour ceux qui manqueraient encore de réponses, voici une excellente FAQ sur WPF et Silverlight sur le site CodeProject :

http://www.codeproject.com/KB/WPF/WPFSilverLight.aspx

C#, WPF , , , , , ,

Premier contact avec les différents types de stockage dans Azure

19. June 2009 by Leonard.LABAT

Vous avez peut être déjà effectués vos premiers pas dans le cloud en créant votre premier site ou service pour Windows Azure. Si ce n’est pas le cas, je vous invite à suivre ce billet de Julien Dollon :

http://blogs.dotnet-france.com/juliend/post/Azure-Introduction-Azure-Services-Platform-(Windows-Azure-NET-Services-Live-Services-SQL-Services).aspx

Dans ce billet, nous allons nous pencher sur l’utilisation des trois types de storage proposés par Azure, à savoir :

  • Les Queues;
  • Les Blobs;
  • Les Tables.

Pour vous présenter tout ça, je vais utiliser un petit exemple tiré du monde de la restauration. Imaginez que vous êtes au restaurant , le Web Role représente le serveur qui vient prendre votre commande. Bien évidemment, le serveur ne va préparer votre commande instantanément sous vos yeux, il va la transmettre au chef (qui est représenté par le Worker Role) qui la traitera dès qu’il en aura fini avec les commandes prises avant la votre.

La communication entre le Web Role (le serveur) et le Worker Role (le chef) se fait via la Queue. Ainsi, comme il s’agit d’une file, les commandes passées par les clients (d’une manière générale, comprenez des tâches), sont traitées par ordre chronologique par le chef.

Enfin, une fois que le chef a fini d’œuvrer, il renvoi le plat en salle. Les différents plats seront stockés dans les Blobs.

On pourra tenir à jour la liste des plats disponibles. Pour se faire, je vais utiliser les Tables.

Maintenant que vous avez pris connaissance de l’exemple que je vais utiliser, on va pouvoir attaquer. Commencez par créer un projet Web & Worker.

 I. Les Queues

Dans cette première partie, nous allons travailler sur la prise de commande (du côté du projet web) et la réception de la commande (du côté du worker).

Commencez par ajouter une page web.

Complétez le formulaire en lui ajoutant quelques petits contrôles :

  • Un champs pour la saisie du nom du plat;
  • Un bouton pour valider la commande;
  • Un label qui donne l'état du serveur (celui du restaurant hein).

<form id="form1" runat="server">
<div>
    <asp:Label ID="lOrderName" runat="server" Text="Order Name : " /><asp:TextBox ID="tbOrderName" runat="server" /><br />
    <asp:Button ID="bTakeOrder" runat="server" Text="OK"
        onclick="bTakeOrder_Click" /><br />
    <asp:Label ID="lWaiterStatus" runat="server" />
</div>
</form>

Avant de pouvoir utiliser la Queue, il faut configurer un peu vos rôles pour qu’ils puissent se connecter au service de stockage.

Ouvrez le fichier ServiceDefinition.csdef et renseignez le comme ci-dessous.

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole" enableNativeCodeExecution="false">
    <InputEndpoints>
      <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
    </InputEndpoints>
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WebRole>
  <WorkerRole name="WorkerRole" enableNativeCodeExecution="false">
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WorkerRole>
</ServiceDefinition>

Occupez vous ensuite du fichier ServiceConfiguration.cscfg. Remarquez deux choses :

  • - Ici, je travaille en local. Vous le savez surement si vous avez déjà eu une première expérience avec Azure, vous disposez en local des outils Development Fabric et Development Storage qui simulent le fonctionnement du cloud. C’est sur ce deuxième outil que vous pouvez récupérer l’adresse et le port du Queue Storage.
  • Vous pouvez utiliser si vous le voulez des configurations différentes pour les deux rôles.
<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Maintenant, ajoutez à votre solution le projet StorageClient présent dans les exemples du SDK d’Azure et référencez le dans le projet Web et le Worker.

Ajoutez ensuite dans le code behind de la page Order un champs de type StorageAccountInfo. Ce type est défini dans le projet que vous venez de récupérer. Utilisez ensuite la méthode GetDefaultQueueStorageAccountFromConfiguration() qui, comme son nom l’indique, va venir lire la configuration que vous avez complétée.

StorageAccountInfo storageAccountInfoQueue;

protected void Page_Load(object sender, EventArgs e)
{
    storageAccountInfoQueue = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
}

Chargeons nous ensuite de compléter la méthode bTakeOrder_Click. Il faut commencer par récupérer une instance de la classe QueueStorage en utilisant les infos de configuration que nous avons au préalablement récupérés. La QueueStorage est composée de différents MessageQueue identifiable par leur nom. Ainsi, récupérons un de ces MessageQueue que nous appelerons « orders ». Si il n’existe pas (méthode DoesQueueExists()), il faut le créer (méthode CreateQueue()).

Ensuite, nous pouvons simplement ajouter des messages à la Queue grâce à la méthode PutMessage. Pour finir, on met à jour le label du status du serveur.

protected void bTakeOrder_Click(object sender, EventArgs e)
{
    QueueStorage queueStorage = QueueStorage.Create(storageAccountInfoQueue);

    MessageQueue messageQueue = queueStorage.GetQueue("orders");

    if (!messageQueue.DoesQueueExist())
        messageQueue.CreateQueue();

    messageQueue.PutMessage(new Message(tbOrderName.Text));

    lWaiterStatus.Text = "Order sent to chef";
}

Avant de s’attaquer à l’écriture du Worker, on va vérifier que la prise de commande fonctionne correctement. Lancez l’application et passez quelques commandes de test.

 

Nous pouvons voir le contenu de la Queue en utilisant un petit utilitaire disponible sur CodePlex : Azure Storage Explorer (http://azurestorageexplorer.codeplex.com/)

 

Nos commandes ont bien été ajoutées à la Queue !

Passons maintenant au Worker. Je ne reviens pas sur l’initialisation qui est la même que pour le projet Web. Vous pouvez récupérer un message en utilisant simplement la méthode GetMessage(). Son contenu est ensuite accessible via la méthode ContentAsString(). Enfin, on enlève de le message de la Queue avec DeleteMessage(). J’ai ajouté un petit message de log et une temporisation d’une seconde.

public override void Start()
{
    RoleManager.WriteToLog("Information", "Worker Process entry point called");

    StorageAccountInfo storageAccountInfoQueue = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
    QueueStorage queueStorage = QueueStorage.Create(storageAccountInfoQueue);
    MessageQueue messageQueue = queueStorage.GetQueue("orders");

    if (!messageQueue.DoesQueueExist())
        messageQueue.CreateQueue();

    while (true)
    {
        Message message = messageQueue.GetMessage();

        if (message != null)
        {
            RoleManager.WriteToLog("Information", "Chef must cook ... " + message.ContentAsString());
            messageQueue.DeleteMessage(message);
        }

        Thread.Sleep(1000);
    }
}

On lance et on jette un coup d’œil au moniteur du Worker dans la Development Fabric.

 

Un petit coup d’œil dans l’Azure Storage Explorer et vous constaterez que la Queue s’est bien vidée de ses messages. Vous pouvez continuer à tester l’application en prenant de nouvelles commandes du côté Web et en constatant qu’elles sont bien traitées une par une du côté du Worker.

Vous pouvez tester au passage le fonctionnement du multi instance des Worker. Modifiez le fichier ServiceConfiguration.cscfg de la manière suivante :

<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="2"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Lancez l’application et passez rapidement plusieurs commandes. Du côté de la Development Fabric, vous constaterez que les deux instances du Worker se répartissent bien le travail.

 

  II. Les Blobs

Je rappelle que nous utiliserons les blobs pour stocker les plats préparés par le chef. Ces plats seront simplement représentés par une chaine du caractère correspondant au nom du plat.

La récupération du conteneur de blobs se fait de la même manière que pour récupérer une Queue. N’oubliez pas de compléter le fichier de configuration pour pouvoir vous connecter au service de stockage.

StorageAccountInfo storageAccountInfoBlob = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
BlobStorage blobStorage = BlobStorage.Create(storageAccountInfoBlob);
BlobContainer blobContainer = blobStorage.GetBlobContainer("dishes");

if(!blobContainer.DoesContainerExist())
    blobContainer.CreateContainer();

Pour ajouter un blob au conteneur, vous devez utiliser la méthode CreateBlob(). Nous devons passer à cette méthode un objet de type BlobProperties et un autre de type BlobContents. Le premier contiendra le nom du blob et le second son contenu que nous devons lui passer en un tableau de byte.

if (message != null)
{
    RoleManager.WriteToLog("Information", "Chef must cook ... " + message.ContentAsString());

    BlobProperties blobProperties = new BlobProperties(message.ContentAsString());
    ASCIIEncoding encoding = new ASCIIEncoding();
    BlobContents blobContents = new BlobContents(encoding.GetBytes(message.ContentAsString()));
    blobContainer.CreateBlob(blobProperties, blobContents, true);
   
    messageQueue.DeleteMessage(message);

    RoleManager.WriteToLog("Information", "Order ready");
}

 

Intéressons nous un peu à la surcharge de la méthode CreateContainer() qui utilise deux paramètres. Le premier concerne l’association de meta data à votre blob. Le second vous permet de rendre le container accessible depuis une url. Essayez la valeur suivante :

blobContainer.CreateContainer(null, ContainerAccessControl.Public);

Si vous vous rendez sur l’URL suivante, vous pourrez voir le contenu du blob : http://127.0.0.1:10000/devstoreaccount1/dishes/Salade
"Salade"

Essayez maintenant en utilisant la valeur Private.

blobContainer.CreateContainer(null, ContainerAccessControl.Private);

Si vous essayez à présent d’accéder à l’URL, vous pourrez lire :

<Error>
<Code>ResourceNotFound</Code>
<Message>The specified resource does not exist.</Message>
</Error>

Par défaut, la valeur utilisée est Private.

Pour en finir avec les blobs, nous allons voir comment les extraire du conteneur. Dans le projet Web, ajoutez une page appelée GetDish. Il faut utiliser la méthode GetBlob() à laquelle il faut passer le nom du blob ainsi que le BlobContents qui devra être complétée.

protected void Page_Load(object sender, EventArgs e)
{
    StorageAccountInfo storageAccountInfoBlob = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();

    BlobStorage blobStorage = BlobStorage.Create(storageAccountInfoBlob);
    BlobContainer blobContainer = blobStorage.GetBlobContainer("dishes");

    if (!blobContainer.DoesContainerExist())
        blobContainer.CreateContainer(null, ContainerAccessControl.Private);

    string dish = Request.QueryString["dish"];
    BlobContents blobContents =new BlobContents(Response.OutputStream);

    try
    {
        blobContainer.GetBlob(dish, blobContents, false);
    }
    catch(Exception ex)
    {
        Response.StatusCode = 404;
        Response.End();
    }
}

Essayez d’accéder à l’url suivante : http://127.0.0.1:85/GetDish.aspx?dish=Salade. Le contenu du blob s’affiche bien dans la page.

III. Les Tables

Il est temps à présent de parler du dernier type de stockage mis à votre disposition : les tables. Le schéma de fonctionnement des tables est le suivant : chaque table contient une collection d’entités (on peut les assimiler à des lignes dans la table), chacune de ces entités est composée d’une collection de propriétés (on peut les assimiler à des colonnes) et chacune de ses propriétés porte un nom, un type et une valeur. Les types utilisables sont les suivants :

  • Binary
  • Bool
  • DateTime
  • Double
  • GUID
  • Int
  • Long
  • String

Chose importante : une entité doit obligatoirement porter trois propriétés, une partition key, une row key et un timestamp. Les deux premiers forment un identifiant unique pour l’entité. Dans l’exemple suivant, nous utiliserons l’API fournie par le projet StorageClient. Cette API prend en charge le timestamp, nous n’aurons pas à nous en occuper.

Commencez par modifier le fichier de définition et le fichier de configuration.

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole" enableNativeCodeExecution="false">
    <InputEndpoints>
      <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
    </InputEndpoints>
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="BlobStorageEndpoint"/>
      <Setting name="QueueStorageEndpoint"/>
      <Setting name="TableStorageEndpoint"/>
    </ConfigurationSettings>
  </WebRole>
  <WorkerRole name="WorkerRole" enableNativeCodeExecution="false">
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="BlobStorageEndpoint"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WorkerRole>
</ServiceDefinition>

<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="BlobStorageEndpoint" value="http://127.0.0.1:10000"/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
      <Setting name="TableStorageEndpoint" value="http://127.0.0.1:10002"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="BlobStorageEndpoint" value="http://127.0.0.1:10000"/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Ensuite, il faut créer la classe qui représentera l’entité plat. On oublie pas de définir les deux propriétés partition key et row key. La classe doit dériver de la classe TableStorageEntity, classe fournie par le projet StorageClient.

public class Dish : TableStorageEntity
{
    public string DishName { get; set; }

    public Dish()
    {
        PartitionKey = "Dishes";
        RowKey = DateTime.Now.Ticks.ToString();
    }
}

Ajoutez maintenant une référence à l’assembly System.Data.Services.Client. Créez une classe RestaurantContext qui dérivera de TableStorageDataServiceContext (qui nous vient encore et toujours de StorageClient). On ajoute la déclaration de la table Dishes.

public class RestaurantContext : TableStorageDataServiceContext
{
    public IQueryable<Dish> Dishes
    {
        get
        {
            return CreateQuery<Dish>("Dishes");
        }
    }
}

Il est maintenant temps de générer les tables représentant les entités que vous venez de définir. Pour se faire, Visual Studio dispose d’un petit bouton magique. Faîtes un clic droit sur le projet Cloud et choisissez « Create Test Storage Tables ».

 

Si vous ouvrez Microsoft SQL Server Management, vous pourrez constater que la table a bien été générée :

 

Attention ! Cette manière de faire ne marche que pour créer les tables en local. Pour pouvoir créer une table sur le cloud, vous devrez passer par le code.

TableStorage.CreateTablesFromModel(typeof(RestaurantContext),
    StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());

On va maintenant ajouter une petite méthode statique à la classe RestaurantContext histoire de nous faciliter l’ajout d’un plat à la table Dishes.

public static void AddDish(Dish dish)
{
    RestaurantContext restaurantContext = new RestaurantContext();

    restaurantContext.AddObject("Dishes", dish);

    restaurantContext.SaveChanges();
}

On ajoute une page AddDish.aspx. Sur cette page on place une textbox, un bouton et un label.

<asp:TextBox ID="tbDishName" runat="server" />
<asp:Button ID="bAddDish" runat="server" Text="Add" onclick="bAddDish_Click" />
<asp:Label ID="lStatus" runat="server" />

Dans le code behind, on crée un objet de type Dish et on l’insère dans la table.

protected void bAddDish_Click(object sender, EventArgs e)
{
    Dish dish = new Dish()
    {
        DishName = tbDishName.Text
    };

    RestaurantContext.AddDish(dish);

    lStatus.Text = tbDishName.Text + " added";
}

Testez ce nouveau formulaire et admirez le contenu de la table Dishes via Azure Storage Explorer.

 

Dernière chose à faire : récupérer le contenu de la table pour l’afficher dans une liste déroulante sur la page Order.aspx. On ajoute un composant DropDownList et on profite du databinding pour le remplir.

<asp:Label ID="lOrderName" runat="server" Text="Order Name : " /><asp:DropDownList ID="ddlOrderName" runat="server" /><br />
<asp:Button ID="bTakeOrder" runat="server" Text="OK"
    onclick="bTakeOrder_Click" /><br />
<asp:Label ID="lWaiterStatus" runat="server" />

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        RestaurantContext restaurantContext = new RestaurantContext(StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());
        ddlOrderName.DataSource = restaurantContext.Dishes;
        ddlOrderName.DataValueField = "DishName";
        ddlOrderName.DataBind();
    }
}

Un petit coup d’œil sur la page Order.aspx et on constate que le contenu de la table est correctement récupéré.

 

Voila, j’espère que ce billet saura vous guider dans vos premiers pas avec les systèmes de stockage mis à disposition par Windows Azure. A bientôt !

C#, Azure , , , ,

Create Word document using the OpenXML 2.0 SDK

** A French version of this article is available here**

Today, users should be able to export date from a system and document’s generation is a big problematic in a project.

In this article I’ll try to explain how to use the last version of the OpenXML SDK (2.0 April 2009 CTP) in order to generate Word 2007 documents (.docx).

Before that, let’s have a look at the document’s structure.

Structure of a “.docx”

A Word 2007 document is nothing else than a package that contains other files. Create a new document in Word and write just one word like that :

OpenXML01

Save the document and extract it to access the contained files :

OpenXML02

The major files in this packages are :

  • document.xml that contains all the structure and content of the document as you can see in the snippet bellow :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document><!-- xmlns tronqués pour lisibilité -->
  <w:body>
    <w:p w:rsidR="00AA5D53" w:rsidRDefault="00DF5AC3">
      <w:r>
        <w:t>Paragraphe</w:t>
      </w:r>
    </w:p>
    <w:sectPr w:rsidR="00AA5D53" w:rsidSect="00AA5D53">
      <w:pgSz w:w="12240" w:h="15840"/>
      <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
        w:header="720" w:footer="720" w:gutter="0"/>
      <w:cols w:space="720"/>
      <w:docGrid w:linePitch="360"/>
    </w:sectPr>
  </w:body>
</w:document>

All these tags are identified by classes inside the OpenXML SDK. By installing it, you’ll access a new library name “DocumentFormat.OpenXml.dll” that contains namespaces specialized in the manipulation of Office Documents. For example, you will use the DocumentFormat.OpenXml.Wordprocessing namespace for Word documents.

Here is an equivalence between tags and .NET classes :

  • <w:document> : it is identified by the “Document” class and it represents the document’s root.
  • <w:body> : it is identified by the “Body” class and it represents the document’s body.
  • <w:p> : it is identified by the “Paragraph” class and represents a paragraph in your document.
  • <w:r> : it is identified by the “Run” class. You can’t add directly content in a paragraph. You should add a run and add content in the run (text, hyperlink…)
  • <w:t> : it is identified by the “Text” class and represents you text.
  • styles.xml : this file is like a CSS stylesheet in a web application. It contains all the definitions of styles that can be used in document. Styles are referenced by a unique ID and can be used directly in the document.xml file.

Note : writing OpenXML files with .NET is very simple and intuitive. It’s like write HTML code or XML code but with different tags. Always remember that you should have a Text inside a Run inside a Paragraph to write some text.

Create a simple Document

To write a more attractive article I choose a concrete case : write the orders history of each customer from the Northwind company. Here is the DataContext I’ll use in the following :

OpenXML03

First, you should add two references to your project. The first one, “WindowsBase.dll” allows you to implicitly use the System.IO.Packaging namespace that contains all classes allowing the creation of Open XML package. The second one, “DocumentFormat.OpenXml.dll” allows you to use all the classes for creating a Word document.

The first class to use is WordprocessingDocument that represents the “.docx” package. You can create an instance of this class by calling its “Create” static method :

WordprocessingDocument document = WordprocessingDocument.Create(
    @"C:\CustomersHistory.docx",
    WordprocessingDocumentType.Document
);

Now, you should compose the package. The first part that you have to create is the “document.xml” file. For that, call the AddMainDocumentPart method on the WordprocessingDocument instance :

MainDocumentPart mainDocumentPart = document.AddMainDocumentPart();

You can create the <w:document> root :

mainDocumentPart.Document = new Document();

And the document’s body :

Body documentBody = new Body();
mainDocumentPart.Document.Append(documentBody);

You’ve just achieve to create the document’s structure. Now you can get the customers from the database and write the content of the document :

List<Customer> customers = null;

using (NorthwindDataContext db = new NorthwindDataContext())
{
    DataLoadOptions options = new DataLoadOptions();
    options.LoadWith<Customer>(c => c.Orders);

    db.LoadOptions = options;

    customers = db.Customers.ToList();
}

The first paragraph is the title of the document. Remember that for creating a paragraph, you should create three instances :

  • Paragraph
  • Run
  • Text

Paragraph titleParagraphe = new Paragraph();
Run titleRun = new Run();
Text titleText = new Text("Northwind's Customers History");
titleRun.Append(titleText);
titleParagraphe.Append(titleRun);

Once the paragraph is create, you can add it to the document’s body by calling the “Append” method of the Body class :

documentBody.Append(titleParagraphe);

After that, I choose to create a paragraph for each customer and write a table that summaries all its orders :

foreach (var customer in customers)
{
    //paragraph for the name
    Paragraph customerNameParagraph = new Paragraph();
    Run customerNameRun = new Run();
    Text customerNameText = new Text(
        String.Format("#{0} : {1}",
            customer.CustomerID,
            customer.ContactName
        )
    );

    customerNameRun.Append(customerNameText);
    customerNameParagraph.Append(customerNameRun);

    //add the paragraph to the document’s body
    documentBody.Append(customerNameParagraph);

    //create a table :
    Table ordersTable = new Table();

    //loop on the orders
    foreach (var order in customer.Orders)
    {
        //création d'une ligne
        TableRow orderRow = new TableRow();

        //create a cell for the OrderID
        //notez que l'on retrouve l'imbriquation para/run/texte
        TableCell orderIDCell = new TableCell();
        orderIDCell.Append(
            new Paragraph(
                new Run(
                    new Text(order.OrderID.ToString())
                )
            )
        );

        //create a cell for the date
        TableCell orderDateCell = new TableCell();
        orderDateCell.Append(
            new Paragraph(
                new Run(
                    new Text(order.OrderDate.Value.ToShortDateString())
                )
            )
        );

        //add cells to the row
        orderRow.Append(orderIDCell, orderDateCell);
        //add the line to the table
        ordersTable.Append(orderRow);
    }

    //add the table to the document’s body
    documentBody.Append(ordersTable);
}

The last step is to save and dispose the WordprocessingDocument :

document.MainDocumentPart.Document.Save();
document.Dispose();

If you run this code, you’ll obtain a Word’s document that looks like the following screen :

OpenXML04

It will be more pretty with different styles :)

Styles management

There is two solutions for using styles in an Open Xml document. The first one is to extract a style sheet from a document to get a base. For example, you can extract all the Office 2007 styles from the document you’ve created when discussing about the structure.

When you’ve get the “styles.xml” file, add it to your project and configure it as it’s shown here :

OpenXML05

Now, you should add a new part to the Open Xml package. To do that, you will use the AddNewPart<T> generic method of the MainDocumentPart class. Here T is the StyleDefinitionsPart type :


StyleDefinitionsPart styleDefinitionsPart =
    mainDocumentPart.AddNewPart<StyleDefinitionsPart>();

Get a file stream on the styles.xml file. You’ll use it to load all styles in your package :


FileStream stylesTemplate =
    new FileStream("styles.xml", FileMode.Open, FileAccess.Read);
styleDefinitionsPart.FeedData(stylesTemplate);
styleDefinitionsPart.Styles.Save();

To apply a style to a paragraph, you should use a ParagraphProperties instance. For example, if I want use the “Title” style for the title of the document, I’ll add this code :

ParagraphProperties titleProperties = new ParagraphProperties();
titleProperties.ParagraphStyleId = new ParagraphStyleId { Val = "Title" };

titleParagraphe.Append(titleProperties);

If I want use the “Heading1” style for the name of each customer, I’ll add this code :


ParagraphProperties customerNameProperties = new ParagraphProperties();
customerNameProperties.ParagraphStyleId =
    new ParagraphStyleId { Val = "Heading1" };
customerNameParagraph.Append(customerNameProperties);

Here is the result :

OpenXML06

The second solution for using styles is to create your own style sheet directly. Here is a description of each steps to follow to create your own style sheet :

  • Create the “styles.xml” file by calling AddNewPart<T>
  • Instanciate the collection of style on the StyleDefinitionsPart :

stylePart.Styles = new Styles();

  • Create a style and add it to the collection :

Style dateStyle = new Style();
dateStyle.Append(new Name { Val = "DateFormat" });

RunFonts font = new RunFonts();
font.Ascii = "Calibri";//police

RunProperties runProperties = new RunProperties();
runProperties.Append(font);//font
runProperties.Append(new Color() { Val = "006600" });//couleur
runProperties.Append(new Italic());//italic
runProperties.Append(new FontSize { Val = 32 });//taille 32

dateStyle.Append(runProperties);

stylePart.Styles.Append(dateStyle);

  • Use the style

As you saw previously, you have to use a ParagraphProperties :

ParagraphProperties dateProperties = new ParagraphProperties();
dateProperties.ParagraphStyleId = new ParagraphStyleId
{
    Val = "DateFormat"
};

And add these properties to the paragraph :

TableCell orderDateCell = new TableCell();
orderDateCell.Append(
    new Paragraph(
        dateProperties,
        new Run(
            new Text(order.OrderDate.Value.ToShortDateString())
        )
    )
);

Result :

OpenXML07

The Open Xml SDK is a very powerful tool that allows to work with Office 2007 formats. Here we’ve discussed about the creation of Word files, but it’s also possible to read files using the same classes and manipulate other formats like pptx or excel, for example.

You can find the source code here.

Bye.

Julien Corioland

C#

Abstraction des Accès BDD dans une Factory en ADO .NET

Ecrit par Thomas COVILLE tutoré par Jérémie BERTRAND

    Dans le cadre du développement d’une application reposant sur l’utilisation d’une base de données, il est important de penser à la réutilisabilité de son code. Il n’est pas question de réécrire toute une application à chaque changement de moteur de BDD. Une des solution est donc de rajouter une couche intermédiaire entre la partie business et la base de donnée. Dans le pire des cas, seule cette couche est à réécrire.

    En allant plus loin, il est possible d’intégrer les accès base dans une fabrique abstraite (factory). Microsoft nous a mâché le travail, depuis le framework .Net 2.0 le namespace System.Data.Common contient tout ce qu’il nous faut pour le faire facilement. Voici rapidement les types les plus important contenus dans ce namespace:

DbProviderFactories

Conteneur de Factory. C’est à travers ce type, que l’on va pouvoir binder notre architecture abstraite à un provider spécifique.

DbProviderFactory

Type implémenté pour chaque provider, c’est à travers un objet de ce type que l’on pourra instancier tous les types du provider choisi.

DbConnection

Type gérant la connexion à la base.

DbCommand

Type permettant d’envoyer des commandes (paramétrées ou nom) dans une DbConnection.

DbDataReader

Retour de l’exécution d’une DbCommand (un peu l’équivalent des bons vieux recordsets d’antan…)

Providers

Microsoft a déjà intégré un certain nombre de providers se conformant à cette architecture :
  • System.Data.SqlClient (SQL Server)
  • System.Data.OleDb (MS Access)
  • System.Data.OracleClient (Oracle DataBase)

D’autre providers sont disponibles sur internet, pour connaître ceux dont vous disposez sur votre machine vous pouvez soit jeter un oeil dans le fichier machine.config du framework à la section <DbProviderFactories>. Soit appeler la Méthode static  GetFactoryClasses() du type DbConnectionFactory :

DataTable tab =  DbProviderFactories.GetFactoryClasses();

foreach (DataRow row in tab.Rows)
{
foreach (DataColumn col in tab.Columns)
{
System.Console.WriteLine("{0} = {1} ;", col.ColumnName, row[col]);
}
Console.WriteLine(Environment.NewLine);
}

 

utilisation des DbProviderFactories

Entrons un peu plus dans le détail maintenant que nous avons une vue générale de ce que Microsoft à mis à notre disposition. nous allons donc réaliser une Classe nommée ConnectDb  encapsulant l’utilisation des types vus précédemment :

image_thumb4 

     La classe ConnectDb contient un ensemble de méthodes statiques qui couvrent les opérations de base pour accéder à une base de données.

    Personnellement j’ai choisi de la définir en abstract puisque toutes ses méthodes sont statiques et qu’elle n’a pas vocation à être instanciée. On aurait pu mettre le constructeur par défaut en privé, le résultat serait le même, je pense que c’est surtout une question de goût…

Voyons en détail l’implémentation de cette classe.

 

 

 

Implémentation

Gestion des paramètres de connexion

    Pour ma part j’ai choisi de stocker la chaîne de connexion, ainsi que le provider choisi dans un fichier de configuration (Pour savoir comment créer votre propre fichier de configuration,vous pouvez consulter ce billet).

    Voici à quoi ressemble mon fichier App.config :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MyDefaultConnectionString"
connectionString="Data Source=MYSERVER\SQLEXPRESS;Initial Catalog=MyDataBase;Integrated Security=True;Pooling=False"/>
</connectionStrings>
<appSettings>
<add key="MyDefaultProvider" value="System.Data.SqlClient"/>
</appSettings>
</configuration>

     Pour mon exemple j’ai choisi d’utiliser une base de données SQL Server et le provider correspondant : Sytem.Data.SqlClient . Bien entendu, la suite du code fonctionnera avec n’importe quel provider implémentant les Types abstraits du namespace System.Data.Common.

static readonly String _connectionString;
static readonly String _provider;

static ConnectDb()
{
_connectionString = ConfigurationManager.ConnectionStrings["MyDefaultConnectionString"].ConnectionString;
_provider = ConfigurationManager.AppSettings["MyDefaultProvider"].ToString();
}

    J’ai défini un constructeur statique pour récupérer les valeurs de la chaîne de connexion et le provider choisi depuis le fichier de configuration. Un constructeur static est appelé automatiquement à la première référence du type. Il ne nécessite donc pas d’instanciation explicite .

Utilisation de la factory

    La récupération de la factory pour un provider spécifique se fait à l’aide de la classe DbProviderFactories. Pour ma part j’ai choisi de l’encapsuler dans une Propriété “Factory” :

protected static DbProviderFactory Factory
{ get
{
return DbProviderFactories.GetFactory(_provider);
}
}

    A ce stade nous disposons donc d’une instance d’un type implémentant DbProviderFactory, spécifique au provider que nous avons choisi. (dans mon cas il s’agit en interne d’une instance de  System.Data.SqlClient.SqlClientFactory).

    Parmi l’ensemble  des méthodes que nous propose l’objet DbProviderFactory , nous n’en utiliserons que deux dans le cadre de cet article :

//Crée une instance d'un objet de type DbCommand
Factory.CreateCommand();
//Crée une instance d'un objet de type DbConnection
Factory.CreateConnection();

   voyons comment les utiliser :

Connexion à la Base de données

    Rien de bien compliqué ici, on appelle la méthode CreateConnection() de la factory, puis on affecte la Chaîne de connection à l’objet DbConnection que l’on a récupéré.

protected static DbConnection GetConnection()
{
DbConnection conn = Factory.CreateConnection();
conn.ConnectionString = ConnectionString;
return conn;

}

    Attention, l’objet connexion retourné est FERME. la connexion à la base n’est donc pas active. J’ai choisit de ne pas retourné d’objet systématiquement ouvert pour ne pas risquer d’oublier de le refermer. Cela permet donc de garder le contrôle sur le pool de connexion ouvertes.

Manipulation des objets DbCommand

    Création

    Une fois la connexion à la base de donnée gérée, il ne reste plus qu’à envoyer les requêtes. C’est là que l’objet DbCommand entre en jeu. Il y a 2 façons de créer un objet commande: en utilisant la factory, ou en passant par un objet DbConnection :

  • Solution 1
public static DbCommand CreateCommand()
{
DbCommand cmd = Factory.CreateCommand();
return cmd;
}
  • Solution 2
public static DbCommand CreateCommand()
{
DbConnection conn = GetConnection();
DbCommand cmd = conn.CreateCommand();
return cmd;
}

    Pour ma part je préfère la première version. C’est encore une fois assez discutable, la raison est que je préfère garder mes objets indépendants les uns des autres le plus longtemps possible.

Envoi d’une requête

   Une fois notre Objet DbCommand créé nous pouvons lui affecter une requête simple à l’aide de la propriété CommandText (nous verrons par la suite comment faire pour créer des commandes paramétrées") :

DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT Champ1, Champ2 FROM MATABLE WHERE Champ3='toto'";

   Attention : La syntaxe des requêtes SQL peut différer d’un SGBD à l’autre, c’est pourquoi, si l’on veut conserver la souplesse de notre factory, l’affectation d’une requête SQL à une DbCommand doit être effectuée EN DEHORS de la classe ConnectDb, par exemple dans une classe de DAO classique.

Nous pouvons regrouper les différents types de requête en 3 catégories :

  • Les requêtes de Sélection qui renvoient un ensemble de données (SELECT .. FROM… JOIN.. WHERE)
  • Les ordres DDL qui renverront la plupart du temps un nombre d’enregistrements affectés (Update, Delete,Insert…)
  • Les requêtes Scalaires qui retournent un champ unique dans un enregistrement unique (ex : “SELECT Count(*) FROM matable”, “SELECT ORACLE_SEQUENCE.NEXTVAL FROM dual”)

    Typiquement nous pouvons donc créer 3 méthodes de requêtage pour gérer les différents cas. Le code de ces méthodes diffère assez peu, dans tous les cas, il nous faut récupérer un objet DbConnection, l’ouvrir et l’affecter à l’objet DbCommand contenant la requête.

Comme un exemple vaut mieux qu’un long discours, voici l’implémentation de ces fonctions :

  • ExecuteQuery (requêtes de Sélection)
public static DbDataReader ExecuteQuery(DbCommand cmd)
{
DbDataReader ret;
DbConnection conn = GetConnection();
conn.Open();
cmd.Connection = conn;
cmd.cr
ret = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);

return ret;
}

    L’objet DbDataReader est un Curseur sur un ensemble d’enregistrement en provenance de la base de donnée en mode connecté, un peu à la manière des vieux recordset. c’est donc dans cet objet que sont renvoyés les enregistrements sélectionnés par la commande.

     Le paramètre passé à la méthodes ExecuteReader() de l’objet DbCommand est une énumération qui  permet de s’assurer que la connexion sera fermée en même temps que le DataReader. En effet, pour que ce dernier fonctionne correctement, la connexion sur laquelle il repose doit rester ouverte jusqu’au dernier moment. On ne peut donc pas fermer la connexion immediatement après la création du DataReader.

  • ExecuteNonQuery (ordres DDL)

public static int ExecuteNonQuery(DbCommand cmd)
{
int ret;
using (DbConnection conn = GetConnection())
{
conn.Open();
cmd.Connection = conn;
ret = cmd.ExecuteNonQuery();
conn.Close();
return ret;
}
}

     Ici le code est assez similaire à la méthode précédente, on notera cette fois la présence du bloc using autour de l’objet DbConnection. En effet Microsoft à eu la bonne idée d’implémenter IDisposable sur ces objets. Ainsi, dans le cas de l’objet DbConnection, on est sûr que quoiqu’il arrive, la méthode Dispose() sera appelée, et que la connexion à la base, fermée.

  • ExecuteScalar (requêtes Scalaires)
public static object ExecuteScalar(DbCommand cmd)
{
object ret;
using (DbConnection conn = GetConnection())
{
conn.Open();
cmd.Connection = conn;
ret = cmd.ExecuteScalar();
conn.Close();
return ret;
}
}

    Encore une fois, le code ne diffère pas énormément de la méthode ExecuteQuery(), la seule chose à noter est que le type de retour de la méthode est de type object. il vous faudra donc penser lors de l’utilisation, à effectuer un cast sur la valeur retournée en fonction du type que vous attendez.

    Maintenant que nous avons nos 3 méthodes, l’exécution d’une requête est simplissime :

ex:

//requête simple
DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT * FROM Personne where prenom = ‘toto’";

DbDataReader reader = ConnectDb.ExecuteQuery(cmd);
while (reader.Read())
{
System.Console.WriteLine("Nom : {0} ,Prenom : {1} , mail : {2}",reader["nom"],reader["prenom"],reader["mail"]);
}
reader.Close();

     Le parcours d’un objet DbDataReader se fait grâce à la méthode booléenne Read(). A la construction de l’objet le curseur pointe AVANT le tout premier enregistrement. A chaque appel de Read(), le curseur passe à l’enregistrement suivant, si l’on est arrivé à la fin du DataReader ou si la requête n’a aucun résultat , Read() renvoi False.

    Pour accéder aux champs des enregistrement, vous pouvez soit utiliser la méthode “tableaux” à l’aide de crochets : reader[“nomColonne”] ou reader[NumColonne"], sans oublier de caster correctement les valeurs de retour.

    Ou bien encore utiliser toute les méthodes reader.GetXXXX() pour récupérer directement une valeur typée.

     Nous avons vu qu’il était très simple d’envoyer une requête à la base de données, cependant il peut parfois être utile de paramétrer une requête, c’est à dire, dissocier les valeurs des champs. Cela permet un traitement plus automatisé, plus optimisé et plus sûr des requêtes, notamment au niveau des attaques par injection de code SQL. Voyons ensemble comment écrire de telles requêtes.

Création d’une requête paramétrée

     Créer une requête paramétrée n’est pas bien plus difficile que de créer une requête sans paramètre : reprenons un objet DbCommand, et voyons quelles autres méthodes s’offrent à nous :

     Nous avons une Méthode CreateParameter() et une propriété Parameters qui contient la liste de tous les paramètres associés à cette commande.

    Avant d’associer des paramètres à une DbCommand, il faut tout d’abord référencer ces paramètres dans la requête elle même. cela se fait grâce à un mot clé choisi par vous-même, précédé d’un symbole qui indiquera au provider que la chaîne de caractère suivante est un paramètre. Ce symbole varie d’un provider à l’autre, voici un tableau récapitulant ceux que je connais :

Oracle ‘:’ + NomParametre
SqlServer ‘@’+ NomParametre
Access ‘?’+ NomParametre

    Il nous faut ensuite créer les paramètres à proprement parler et leur donner leur valeur. rien de plus simple, on crée un objet DbParameter à l’aide de la méthode CreateParameter de la DbCommand et on affecte les propriétés suivantes :

  • ParameterName : chaîne de caractères correspondant au paramètre dans la requête (sans le symbole)
  • Value : valeur à affecter au paramètre
  • DbType (facultatif) : Type à affecter au paramètre

Puis on ajoute le paramètre créé à la Liste des paramètres de la commande. A partir de là la commande s’exécute exactement comme une commande normale, à l’aide des 3 méthodes que nous avons définies plus haut.

Exemple d’une requête d’insertion: on suppose que l’on dispose d’une table personne dans notre base de données :

image1_thumb

//requête Parametrée
DbCommand cmd2 = ConnectDb.CreateCommand();
cmd2.CommandText = "INSERT INTO Personne (mail,nom,prenom)VALUES (@mail,@nom,@prenom)";
DbParameter mailParam = cmd2.CreateParameter();
mailParam.ParameterName = "mail";
mailParam.Value = "uneadressemail@kkpart.com";
cmd2.Parameters.Add(mailParam);




DbParameter nomParam = cmd2.CreateParameter();
nomParam.ParameterName = "nom";
nomParam.Value = "COVILLE";
cmd2.Parameters.Add(nomParam);



DbParameter prenomParam = cmd2.CreateParameter();
prenomParam.ParameterName = "prenom";
prenomParam.Value = "Thomas";
cmd2.Parameters.Add(prenomParam);


ConnectDb.ExecuteNonQuery(cmd2);

    Et voilà, vous disposez désormais d’une classe complète permettant de gérer tout vos accès base en mode connecté, de façon totalement indépendante du SGBD choisi !

    Nous pouvons cependant aller encore plus loin grâce à la généricité. En effet pourquoi se contenter d’utiliser un DbDataReader, quand on peut facilement disposer d’une Liste d’objets directement prêts à l’usage ? C’est ce que nous allons faire de suite :

La Methode Find<T>(…)

Implémentation

    L’objectif de cette méthode, est d’obtenir, en se basant sur une DbCommand on ne peut plus classique, une liste d’objets de type T  (générique donc).

    Pour ce Faire nous allons utiliser la notion de delegate et plus particulièrement le Type Func<Targ0, TResult> ajouté dans le framework 3.5 . Grosso modo, c’est une référence vers une fonction qui prendrait un paramètre de type Targ0 et renverrait un objet de type TResult.

    L’intérêt pour nous, c’est que nous allons donc pouvoir ajouter en paramètre de notre méthode Find, un delegate qui prendrait en Parametre un DbDataReader et renverrait un objet de Type T  remplit avec les données du DbDataReader. Pour faire simple, le delegate serait un “traducteur” pour passer d’un DbDataReader, vers une List<T>.

Voici donc à quoi ressemble notre Méthode Find :

public static List<T> Find<T>(DbCommand cmd, Func<DbDataReader, T> ParseObject)
{
using (DbDataReader rs = ExecuteQuery(cmd))
{
List<T> retList = new List<T>();

while (rs.Read())
{
T oRow = ParseObject(rs);
retList.Add(oRow);
}
return retList;
}
}

   Tout comme le type DbConnection,  le type DbDataReader implémente IDisposable. On ne se privera donc pas de l’entourer d’une directive using, pour s’assurer que les connections à la base seront correctement fermées. A l’aide de la méthode ExecuteQuery() que nous avons codée précédemment, nous récupérons le DbDataReader correspondant à notre DbCommand.

   Puis, pour chaque Enregistrement du DataReader, nous appelons le delegate passé en paramètre à notre méthode find  (Parseobject()). Il ne nous reste plus qu’à ajouter l’objet de Type T à la List<T> et à retourner cette liste une fois le reader entièrement parcouru.

   Très bien me direz vous, mais comment ça s’utilise ce machin ? démonstration :

Utilisation

   Considérons tout d’abord que dans notre Base de données, nous avons une table Personne contenant les champs suivants :

   Reprenons l’exemple de notre table Personne :

image11

      Nous réalisons une classe Personne dans notre programme pour la manipulation des données :

public class Personne
{
public String NomPrenom
public String Mail;

}

      J’ai fait exprès de Concaténer dans ma Classe les champs Nom et Prenom et d’omettre le champ Age,  pour bien montrer que les structure des classes du programme peuvent être différente de celles des tables de la base.

nous créons notre requête de sélection :

DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT * FROM Personne";

    Il ne reste plus qu’à appeler notre fameuse méthode Find() . Avant cela je dois introduire la notion d’Anonymous Delegate. Grâce à ce concept nous allons pouvoir passer directement à notre Func<DbDataReader,T>, un bloc de code qui sera interprété comme une fonction :

List<Personne> personnes = ConnectDb.Find(cmd, delegate(DbDataReader rs)
{
Personne p = new Personne();
p.mail = rs["mail"].ToString();
p.NomPrenom = rs["Nom"].ToString() + rs["prenom"].ToString();
return p;
});

    Le code est donc dérisoirement simple, on passe donc en paramètre au find notre delegate et directement à la ligne, grâce au délégué anonyme nous mettons notre bloc de traitement.

    Remarquez une chose assez intéressante, je n’ai pas spécifié le type générique utilisé pour la méthode Find. Alors que pourtant, le prototype de la fonction est bien Find<T>(…). En fait, le compilateur C# est “intelligent”, et déduit du code, le type générique utilisé et est capable de déterminer si il est correct ou non. Ce mécanisme s’appelle Inférence de Type.

   Rentrons maintenant dans une amélioration introduite par le framework 3.5, et que permet d’utiliser Func<Targ,TResult> : Les lambda Expressions. L’explication détaillée des lambda expressions n’entre pas dans le cadre de cet article, je me permet juste de vous montrer comment aurait pu s’écrire l’appel à la méthode Find en utilisant ce mécanisme, libre à vous d’approfondir vos recherches sur ce sujet :

List<Personne> personnesLambda = ConnectDb.Find(cmd, rs => new Personne()
{
mail = rs["mail"].ToString(),
NomPrenom = rs["Nom"].ToString() + rs["prenom"].ToString()
});

     Les lambda expressions reposent en grande partie sur l’Inférence de type dont je vous parlais plus haut.  Décomposons les grandes lignes de ce code :

    Tout d’abord le rs => new Personne(). J’ai volontairement conservé les nom des variables pour que l’exemple soit plus clair:

     Par inférence de type, le compilateur déduit que la variable rs doit être de type DbDataReader. la flèche (=>) introduit ensuite une ligne de code, devant nécessairement retourner dans notre cas, un objet de Type Personne (toujours par inférence de type). Je n’ai donc qu’à instancier mon objet personne et à initialiser ses membres publics.

      Ceci n’est qu’un exemple extrêmement simpliste de ce qu’il est possible de faire à l’aide des lambda Expressions, si vous souhaitez approfondir cette voie, je vous conseille d’effectuer des recherches sur LINQ introduit également au framework 3.5, au sujet duquel j’écrirais sans doute un article sous peu.

Conclusion

    Nous avons donc désormais entre les mains, une classe permettant d’abstraire tous les accès à une base de données, assez souple pour interagir avec n’importe quel provider .NET implémentant les classes du namespace System.Data.Common.

   Pour conclure, je joint à cet article le code source (librement utilisable, distribuable, commercialisable, etc) complet de cette classe avec les exemples que nous avons vu, et reste à votre entière disposition si vous avez des questions, ou simplement envie de débattre d’une autre méthode d’implémentation.

  Notez bien que tout le code que nous avons écrit ne manipule QUE les objets abstraits du namespace System.Data.Common. EN AUCUN CAS la classe ConnectDb ne doit contenir de référence à un provider spécifique. Nous perdrions d’emblée tout l’intérêt de l’utilisation d’une factory.

Thomas COVILLE

Blog : http://thoums.blogspot.com

ADO.NET, C#