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 :
<?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
header('
Content-type: text/xml
'
);
pour indiquer que l'on retourne du XML.
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▲
<?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 :
<?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>
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.