Tutoriel : utiliser Silverlight 2 avec MySQL en C#

Nico-pyright(c)

Cet article présente une solution pour utiliser Silverlight avec une base de données MySQL. On y verra également quelques utilisations des composants de Silverlight.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Tutoriel : utiliser Silverlight 2 avec MySQL en C#

- Auteur : Nico-pyright(c)

- Parution : 18 juin 2008

- Mise à jour : 13 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- 1. Introduction

- 2. Préambule

- 3. Le besoin : utiliser MySQL et PHP avec Silverlight

- 4. Lire une table de la base MySQL

- 4-1. Préambule : création de la table

- 4-2. Le script PHP de lecture

- 4-3. Téléchargement asynchrone

- 4-4. Linq to Xml

- 5. Mise à jour de la table

- 5-1. Script PHP d'ajout d'un élément dans la liste

- 5-2. Envoi de données en POST

- 6. L'application : une todo liste

- 6-1. Code Xaml

- 6-2. Binding avec la ListBox

- 6-3. Supprimer un élément de la todolist

- 6-4. Autoriser le cross domain

- 6-5. Téléchargement

- 7. Démo en ligne

- 8. Conclusion

- 9. Remerciements

- 10. Contact

1 - Introduction

Vous êtes un développeur C#, débutant avec Silverlight et vous souhaitez utiliser Silverlight avec une base de données MySQL ?
Alors ce tutoriel est pour vous.

À travers cet article, nous allons présenter une méthode pour accéder à une base de données MySQL, exécuter des requêtes de sélection, d'insertion, etc.
Tout au long de ce cours, je vais utiliser Visual C# 2008.

Notez qu'au moment où j'écris cet article, Silverlight est encore en version 2 bêta 2 et j'utilise certains hacks pour contourner des problèmes qui n'existeront sans doute plus dans la version finale de Silverlight.

2 - Préambule

Dans ce tutoriel, je considère que vous avez quelques notions de Silverlight et les outils correctement installés.
Pour plus de renseignements sur l'installation des outils Silverlight, vous pouvez aller consulter l'introduction à Silverlight 2 de Benjamin Roux.

3 - Le besoin : utiliser MySQL et PHP avec Silverlight

On voit un peu partout des utilisations de Silverlight avec Sql Serveur, notamment à travers l'utilisation de LINQ et de WCF. Je vais vous montrer comment on peut utiliser Silverlight avec une base de données MySQL par exemple.

L'intérêt est de pouvoir utiliser des applications Silverlight dans un environnement open source (LAMP par exemple) ou de les héberger chez votre fournisseur d'accès qui en général propose un serveur apache avec MySQL (comme Free par exemple).

Comme on ne peut pas utiliser Linq To Sql avec autre chose que SQL Server, l'idée est d'utiliser un script PHP qui accèdera à la base de données MySQL qui servira d'interface avec notre application Silverlight.
Ainsi, quand on aura besoin d'informations, notre script PHP ira lire dans la base de données et nous renverra les informations en format XML que l'on pourra exploiter avec Linq To Xml.
De même, quand on aura besoin de faire des insertions ou des mises à jour, on enverra des données à un script PHP qui s'occupera de communiquer avec la base de données MySQL.

4 - Lire une table de la base MySQL

Imaginons que nous voulions faire une application qui affiche une todolist. Le premier besoin est d'être capable de lire dans une table (qui s'appellera todolist) avec un select. Cette table contient deux champs :

- id, qui est un entier auto-incrémenté ;

- libelle, qui est une chaîne de caractères (varchar(500)).

Le but est de récupérer la liste des éléments de ma todolist avec un select.

4-1 - Préambule : création de la table

Le script SQL suivant permet de créer la table todolist.

CREATE TABLE `todolist` (`id` INT NOT NULL AUTO_INCREMENT , `libelle` VARCHAR( 500 ) NOT NULL , PRIMARY KEY ( `id` )) ENGINE = MYISAM

Tant qu'on est dans le SQL, on va faire un petit insert into pour remplir la table avec une ligne, pour que notre select renvoie quelque chose :

INSERT INTO `todolist` (`id`, `libelle`) VALUES (NULL , 'Mettre le projet de demo en telechargement');

Ce qui nous donne :

id

libelle

1

Mettre le projet de démo en téléchargement

4-2 - Le script PHP de lecture

Nous allons donc créer un script PHP qui va nous retourner l'ensemble des éléments de ma todolist sous format XML.
L'idée est de retourner cette liste sous cette forme :

<datas>
<data>
<id>1</id>
<libelle>Mettre le projet de démo en téléchargement</libelle>
</data>
...
</datas>

Voici le script PHP qui fait ça :

gettodolist.php

<?php
header('Content-type: text/xml');

$host = "localhost"; // le nom ou l'adresse du host
$user = "admin"; // user de la base
$pass = ""; // mot de passe
$connexion = mysql_connect($host,$user,$pass) or die('Erreur de connexion');
$bdd = "mysqldb"; // nom de la base de données
if (!mysql_select_db($bdd,$connexion))
return 0;
if (!$connexion)
return 0;

$query = "SELECT * FROM `todolist`";

$result = mysql_query($query);
if (!$result)
return 0;
echo "<datas>";
while ($line = mysql_fetch_assoc($result))
{
echo "<data>";
$id = $line["id"];
echo "<id>".$id."</id>";
echo "<libelle>".$line["libelle"]."</libelle>";
echo "</data>";
}
mysql_free_result($result);    // Libération des résultats
echo "</datas>";

mysql_close($connexion);    // Fermeture de la connexion, cela ne libère pas les résultats
?>

Ce script commence par créer la connexion à la base de données et fait un select pour retourner tous les éléments de la liste.
Ensuite, il s'occupe de créer les balises XML en bouclant sur tous les éléments retournés par le select.
Notez l'utilisation de

gettodolist.php

header('Content-type: text/xml');

pour indiquer que l'on retourne du XML.

Cliquez ici pour tester le script.

4-3 - Téléchargement asynchrone

Nous allons donc devoir appeler ce script depuis Silverlight pour pouvoir ensuite interpréter le xml généré.

Pour ce faire, on va utiliser l'objet WebClient pour faire un appel asynchrone au script PHP. On utilisera la méthode DownloadStringAsync pour déclencher le téléchargement asynchrone et la surcharge de l'événement DownloadStringCompletedEventHandler permettra d'agir lors de la fin de téléchargement de la réponse renvoyée par le script PHP.

J'ai créé à cet effet une petite classe helper toute simple :

public class WebClientHelper
{
public event DownloadStringCompletedEventHandler DownloadComplete;
private void OnDownloadComplete(object sender, DownloadStringCompletedEventArgs e)
{
if (DownloadComplete != null)
{
DownloadComplete(sender, e);
}
}

private readonly string _url;

public WebClientHelper(string url)
{
var random = new Random();
_url = url;
if (_url.Contains("?"))
_url = _url + "&trick=" + random.Next();
else
_url = _url + "?trick=" + random.Next();
}

public void Execute()
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += webClient_DownloadStringCompleted;
webClient.DownloadStringAsync(new Uri(_url));
}

void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
OnDownloadComplete(sender, e);
}
}
}

Dans cette classe Helper, vous pouvez remarquer que je passe un paramètre en GET à mon script PHP qui est un nombre aléatoire (paramètre trick). L'utilisation de ce paramètre permet de tromper le cache de Silverlight.
Aujourd'hui, il n'y a aucun mécanisme qui permet de définir les options de cache (cela sera sûrement ajouté dans la version définitive). Mon objectif est de me passer des fonctionnalités de cache, c'est pour cela que je rajoute cet argument, ce qui va forcer le script PHP a être réinterprété.

Pour l'utiliser, on fera :

private void ChargementDonnees()
{
try
{
var helper = new WebClientHelper(string.Format("{0}/gettodolist.php", Config.BASEPATH));
helper.DownloadComplete += helper_DownloadComplete;
helper.Execute();
}
catch (Exception ex)
{
HtmlPage.Window.Alert(ex.Message);
}
}

Vous pouvez constater que j'ai choisi de mettre l'url du serveur dans une classe de configuration :

public static class Config
{
public const string BASEPATH = "https://nico-pyright.developpez.com/tutoriel/vs2008/csharp/silverlightandmysql";
}

On associe l'événement DownloadComplete du helper à une méthode : helper_DownloadComplete. C'est dans cette méthode que l'on va pouvoir faire le traitement du résultat du script, c'est-à-dire le traitement du XML.

Remarque : n'oubliez pas de référencer l'assembly System.Net.

4-4 - Linq to Xml

L'appel au script PHP nous renvoie donc du XML. Nous allons le parser grâce aux méthodes de Linq To Xml.
Dans un premier temps, nous allons créer un objet qui va représenter un élément de la todolist :

public class TodoElement
{
public int Id { get; set; }
public string Libelle { get; set; }
}

Comme on l'a vu au-dessus, c'est dans la méthode helper_DownloadComplete que nous pourrons effectuer notre select avec Linq :

void helper_DownloadComplete(object sender, DownloadStringCompletedEventArgs e)
{
try
{
XDocument xmlElements = XDocument.Parse(e.Result);
var elements = from data in xmlElements.Descendants("data")
select new TodoElement
{
Id = (int)data.Element("id"),
Libelle = ((string)data.Element("libelle")).Trim()
};
ListeTodo.ItemsSource = elements;
}
catch (Exception ex)
{
HtmlPage.Window.Alert(ex.Message);
}
}

Remarque : n'oubliez pas de rajouter la référence à System.Xml.Linq.

Le résultat de l'exécution du script PHP, s'il est correct, est disponible dans e.Result. On peut donc s'en servir pour le parser avec l'objet XDocument. Nous pourrons alors faire notre requête Linq.
Notez qu'on affecte la liste des éléments construits à la propriété ItemSource d'un objet ListeTodo.
Il s'agit d'une ListBox qui sera déclarée dans le XAML de notre page, nous y reviendrons plus tard.

Vous avez donc vu, à travers ces différentes étapes, comment on pouvait faire une requête SQL de type select dans notre base MySQL.

5 - Mise à jour de la table

De la même façon, on veut pouvoir effectuer d'autres opérations sur la table, comme un insert into ou un update, afin de rajouter ou de supprimer des éléments dans notre todolist. Pour ça, on va passer encore une fois par un script PHP. Sauf que cette fois-ci, nous allons devoir lui passer des paramètres. Pour ce faire, nous allons les lui envoyer en POST.

5-1 - Script PHP d'ajout d'un élément dans la liste

add.php

<?php
$host = "localhost"; // le nom ou l'adresse du host
$user = "admin"; // user de la base
$pass = ""; // mot de passe
$connexion = mysql_connect($host,$user,$pass) or die('Erreur de connexion');
$bdd = "mysqldb"; // nom de la base de données
if (!mysql_select_db($bdd,$connexion))
return 0;
if (!$connexion)
{
echo "ERR1";
return 0;
}

if (isset($_POST["data"]))
{
$libelle = mysql_real_escape_string($_POST["data"]);
$query = "INSERT INTO `todolist` (`id`, `libelle`) VALUES (NULL , '".$libelle."')";

$result = mysql_query($query);
if (!$result)
echo "ERR2";
}
else
echo "ERR";

mysql_close($connexion);    // Fermeture de la connexion, cela ne libère pas les résultats
?>

Dans un premier temps, ce script se connecte à la base de données. Ensuite, il vérifie s'il y a dans les données postées quelque chose qui l'intéresse. Si c'est le cas, on l'insère dans la table todolist.
Ce qui a pour but d'ajouter un nouvel élément dans la todolist.

5-2 - Envoi de données en POST

Le principe est d'utiliser l'objet HttpWebRequest (n'oubliez pas de rajouter la référence à System.Net) et d'envoyer les données de manière asynchrone en POST via BeginGetRequestStream.
J'utilise ici un helper inspiré du site d'Albert Cameron.

Voici son implémentation :

public class HttpHelper
{
private HttpWebRequest Request { get; set; }
public Dictionary<string, string> PostValues { get; private set; }

public event HttpResponseCompleteEventHandler ResponseComplete;
private void OnResponseComplete(HttpResponseCompleteEventArgs e)
{
if (ResponseComplete != null)
{
ResponseComplete(e);
}
}

public HttpHelper(Uri requestUri, string method, params KeyValuePair<string, string>[] postValues)
{
Request = (HttpWebRequest)WebRequest.Create(requestUri);
Request.ContentType = "application/x-www-form-urlencoded";
Request.Method = method;
PostValues = new Dictionary<string, string>();
if (postValues != null && postValues.Length > 0)
{
foreach (var item in postValues)
{
PostValues.Add(item.Key, item.Value);
}
}
}

public void Execute()
{
Request.BeginGetRequestStream(BeginRequest, this);
}

private static void BeginRequest(IAsyncResult ar)
{
var helper = ar.AsyncState as HttpHelper;
if (helper != null)
{
if (helper.PostValues.Count > 0)
{
using (var writer = new StreamWriter(helper.Request.EndGetRequestStream(ar)))
{
foreach (var item in helper.PostValues)
{
writer.Write("{0}={1}&", item.Key, item.Value);
}
}
}
helper.Request.BeginGetResponse(BeginResponse, helper);
}
}

private static void BeginResponse(IAsyncResult ar)
{
var helper = ar.AsyncState as HttpHelper;
if (helper != null)
{
var response = (HttpWebResponse)helper.Request.EndGetResponse(ar);
if (response != null)
{
var stream = response.GetResponseStream();
if (stream != null)
{
using (var reader = new StreamReader(stream))
{
helper.OnResponseComplete(new HttpResponseCompleteEventArgs(reader.ReadToEnd()));
}
}
}
}
}
}

public delegate void HttpResponseCompleteEventHandler(HttpResponseCompleteEventArgs e);
public class HttpResponseCompleteEventArgs : EventArgs
{
public string Response { get; set; }

public HttpResponseCompleteEventArgs(string response)
{
Response = response;
}
}

Pour l'utiliser :

var helper = new HttpHelper(new Uri(string.Format("{0}/add.php", Config.BASEPATH)), "POST",
new KeyValuePair<string, string>("data", nouvelleTache.Text));
helper.ResponseComplete += AddComplete;
helper.Execute();

private void AddComplete(HttpResponseCompleteEventArgs e)
{
string retour = e.Response;
if (retour == "ERR")
HtmlPage.Window.Alert("Erreur dans l'ajout");
else
{
if (retour == "ERR1")
HtmlPage.Window.Alert("Problème de connexion à la base de données");
else
{
if (retour == "ERR2")
HtmlPage.Window.Alert("Problème d'insertion");
else
ChargementDonnees();
}
}
}

La méthode AddComplete est appelée lorsque l'envoi des données en POST est terminé. On récupère le retour de cet appel dans e.Response, ce qui me permettra de vérifier que tout s'est bien passé.
Puis on rappelle le chargement des données pour prendre en compte la nouvelle donnée.

6 - L'application : une todo liste

Je commence par créer un nouveau projet de type Silverlight Application.

Ensuite je choisis d'utiliser un nouveau site web qui contiendra le contrôle silverlight.

N. B. Si vous n'utilisez pas ce type de projet, vous aurez une exception de type « security error » lors de l'utilisation de l'objet WebClient.

6-1 - Code Xaml

<UserControl x:Class="demoTodoList.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600">
<UserControl.Resources>
<Style x:Key="MaList" TargetType="ListBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ScrollViewer x:Name="ScrollViewerElement"
VerticalScrollBarVisibility="Visible" Loaded="ScrollViewerElement_Loaded" >
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Canvas>
<Rectangle Width="600" Height="50" Stroke="Orange" StrokeThickness="5" />
<TextBlock Text="Ajouter une nouvelle tâche : " Canvas.Left="20" Canvas.Top="10"/>
<TextBox x:Name="nouvelleTache" Width="200" Canvas.Left="240" Canvas.Top="10"/>
<Button Click="Button_Click" Width="80" Canvas.Left="470" Canvas.Top="10" Content="Ajouter"/>
<TextBlock Text="Liste des tâches en cours : " Canvas.Left="15" Canvas.Top="60"/>
<ListBox x:Name="ListeTodo" Height="400" Canvas.Top="100"
Style="{StaticResource MaList}" SelectionChanged="OnSelectionChanged" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Libelle}" Margin="5"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock x:Name="textSupprimer" Canvas.Left="500" Canvas.Top="150"
Text="Voulez-vous supprimer cet élément ?" Visibility="Collapsed"/>
<Button x:Name="boutonSupprimer" Content="Supprimer" Canvas.Top="200"
Canvas.Left="600" Click="Button_Click_1" Visibility="Collapsed"/>
</Canvas>
</UserControl>

On a donc un cadre composé d'un TextBlock, d'un TextBox et d'un Button qui permettra de faire l'ajout d'un nouvel élément dans la todolist.
En dessous, nous avons une ListBox qui contiendra la liste des éléments de la todolist qui sont sauvegardés en base.
Le clic sur un élément de la ListBox provoquera l'affichage du TextBlock et du bouton qui permettront de supprimer un élément de la liste, comme on le verra au 6.3.

Comme il s'agit d'une bêta, j'utilise une astuce pour afficher la scrollbar de notre ListBox en utilisant l'événement Loaded pour ajuster la visibilité de la Scrollbar verticale.
Gageons que ce hack sera corrigé dans la version finale.

private void ScrollViewerElement_Loaded(object sender, RoutedEventArgs e)
{
((ScrollViewer)sender).VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
}

6-2 - Binding avec la ListBox

On remarque dans le xaml ci-dessus le TextBox dans le DataTemplate de la ListBox :

<TextBlock Text="{Binding Libelle}" Margin="5"/>

grâce à la MarkupExtension Binding, Silverlight est capable de binder la propriété Libelle d'un élément de la source de données.

6-3 - Supprimer un élément de la todolist

On veut aussi pouvoir supprimer un élément de la todolist. Pour ce faire, on va réagir à un événement de sélection d'un élément de la ListBox et afficher le bouton de suppression.

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListeTodo.SelectedItem != null)
{
textSupprimer.Visibility = Visibility.Visible;
boutonSupprimer.Visibility = Visibility.Visible;
}
}

Ensuite, lors du clic sur ce bouton, on récupère l'élément sélectionné et on passe au script de suppression PHP (en POST) la valeur de l'ID de l'item sélectionné, de la même façon que précédemment. Ce qui nous donne :

private TodoElement _current;
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_current = ((TodoElement)ListeTodo.SelectedItem);
if (_current == null)
return;
try
{
var helper = new HttpHelper(new Uri(string.Format("{0}/delete.php", Config.BASEPATH)), "POST",
new KeyValuePair<string, string>("data", _current.Id.ToString()));
helper.ResponseComplete += DeleteComplete;
helper.Execute();
}
catch (Exception ex)
{
HtmlPage.Window.Alert(ex.Message);
}
}

private void DeleteComplete(HttpResponseCompleteEventArgs e)
{
string retour = e.Response;
if (retour == "ERR")
HtmlPage.Window.Alert("Erreur dans la suppression");
else
{
if (retour == "ERR1")
HtmlPage.Window.Alert("Problème de connexion à la base de données");
else
{
if (retour == "ERR2")
HtmlPage.Window.Alert("Problème de suppression");
else
{
Dispatcher.BeginInvoke(() =>
{
textSupprimer.Visibility = Visibility.Collapsed;
boutonSupprimer.Visibility = Visibility.Collapsed;
});
nouvelleTache.Focus();
ChargementDonnees();

}
}
}
}

Notez à nouveau le hack que j'utilise pour contourner des bogues de la version bêta. En effet, les changements de visibilité des contrôles provoquent souvent des erreurs.
On utilise ici le Dispatcher pour gérer cette visibilité dans un thread qui s'occupe de mettre à jour l'interface utilisateur.
De même, je place le focus sur un autre élément que la LisBbox, sinon, j'aurais une belle exception : HRESULT E_FAILED has been returned from a call to a COM component.

Voici le script PHP qui va s'occuper de faire la suppression d'un élément de la todolist :

delete.php

<?php
$host = "localhost"; // le nom ou l'adresse du host
$user = "admin"; // user de la base
$pass = ""; // mot de passe
$connexion = mysql_connect($host,$user,$pass) or die('Erreur de connexion');
$bdd = "mysqldb"; // nom de la base de données
if (!mysql_select_db($bdd,$connexion))
return 0;
if (!$connexion)
{
echo "ERR1";
return 0;
}

if (isset($_POST["data"]))
{
$id = (int)$_POST["data"];
$query = "DELETE FROM `todolist` where `id` = ".$id;
$result = mysql_query($query);
if (!$result)
echo "ERR2";
}
else
echo "ERR";

mysql_close($connexion);    // Fermeture de la connexion, cela ne libère pas les résultats
?>

6-4 - Autoriser le cross domain

Si votre script PHP à appeler est sur un domaine différent de celui où est hébergée votre application Silverlight, il faudra autoriser explicitement l'application à appeler une url externe.
Pour ce faire, le site de destination devra posséder un fichier crossdomain.xml à la racine du serveur, dont le contenu autorise le cross-domain.
L'exemple suivant autorise tous les appels :

<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

Plus d'infos sur msdn.

6-5 - Téléchargement

Vous pouvez télécharger ici les sources du projet : version rar (656 Ko) , version zip (782 Ko).

7 - Démo en ligne

Vous trouverez ci-dessous la démo en ligne du projet d'exemple de l'article, mais je l'ai bridé pour ne permettre que la consultation.
Voir la démo.

8 - Conclusion

Cet article montre comment utiliser une base de données MySQL à partir d'une application silverlight, en utilisant le PHP et Linq To Xml. Ainsi, vous pourrez utiliser votre application Silverlight chez votre hébergeur préféré.
Nous avons également vu un peu de Linq To Xml et la manipulation élémentaire d'une listbox.

9 - Remerciements

Je remercie particulièrement Yogui pour ses conseils experts sur mon code PHP ainsi que l'équipe Dotnet pour leurs relectures attentives du document.

10 - Contact

Si vous constatez une erreur dans le tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.