1.Introduction▲
Vous êtes un développeur C#, vous travaillez avec Silverlight et vous souhaitez pouvoir faire de l'upload de fichier sur un serveur PHP ?
Alors ce tutoriel est pour vous.
À travers cet article, nous allons présenter comment créer et utiliser un web service développé avec NuSOAP permettant l'envoi de fichier sur un serveur, ainsi que sa consommation par une application Silverlight.
Tout au long de ce cours, je vais utiliser Visual C# 2008.
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.Silverlight et l'envoi de fichier▲
3.1.Envoi de fichier et sécurité▲
Les applications Silverlight n'ont pas accès aux ressources de l'ordinateur client. Elles sont en effet exécutées dans une sandbox (bac à sable) ce qui permet notamment d'empêcher l'appel à des API du système d'exploitation, d'empêcher l'accès à la base de registre… Bref, de protéger l'utilisateur contre tout code malicieux.
Par contre, le contrôle OpenFileDialog permet d'accéder en lecture à des fichiers (avec l'accord de l'utilisateur, puisque c'est lui qui va sélectionner un fichier).
Grâce à ce contrôle, Silverlight peut donc accéder à un fichier. Cependant, ce n'est pas suffisant, nous sommes encore côté client. Pour uploader un fichier sur un serveur, on va donc devoir l'envoyer sur celui-ci.
Une solution est de transmettre le contenu de ce fichier à un web service afin qu'il puisse recréer un fichier identique sur le serveur.
3.2.Préambule sur la création de web service avec NuSOAP▲
3.2.1.NuSoap▲
NuSOAP est un ensemble de classes PHP qui permet à un développeur de créer des web services SOAP. Un des intérêts de cette bibliothèque est qu'elle fonctionne simplement en copiant les fichiers sur son serveur. Aucune extension n'est requise.
Plus d'infos sur NuSOAP ici. Vous pouvez télécharger NuSOAP à cet emplacement.
Ensuite, il suffira de copier les classes qui sont dans le répertoire lib de l'archive sur notre serveur.
N. B. À l'heure de l'écriture de cet article, la dernière version est la 0.7.3.
Pour faire marcher NuSOAP avec Silverlight, il va falloir opérer deux petites modifications.
Dans le fichier nusoap.php, recherchez les lignes :
var $soap_defencoding
=
'
ISO-8859-1
'
;
//var $soap_defencoding = 'UTF-8';
Commentez la première et décommentez la deuxième pour avoir
//var $soap_defencoding = 'ISO-8859-1';
var $soap_defencoding
=
'
UTF-8
'
;
Faire de même dans le fichier class.nusoap_base.php
3.2.2.Créer un web service avec NuSoap, l'exemple de l'Hello World▲
Créer un fichier helloworld.php qui va contenir ce code :
<?php
require_once('nusoap/nusoap.php'
);
$server
=
new
soap_server();
// Initialize WSDL support
$server
->
configureWSDL('helloworldwsdl'
,
'urn:helloworldwsdl'
);
// Register the method to expose
$server
->
register('gethelloworld'
,
// method name
array
('nom'
=>
'xsd:string'
),
// input parameters
array
('return'
=>
'xsd:string'
),
// output parameters
'urn:helloworldwsdl'
,
// namespace
'urn:helloworldwsdl#addliste'
,
// soapaction
'rpc'
,
// style
'literal'
,
// use
'Le traditionnel Hello World'
// documentation
);
// Define the method as a PHP function
function
gethelloworld($nom
)
{
return
"Hello "
.
$nom
;
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA
=
isset($HTTP_RAW_POST_DATA
) ?
$HTTP_RAW_POST_DATA
:
''
;
$server
->
service($HTTP_RAW_POST_DATA
);
?>
Premièrement, il faut inclure la bibliothèque NuSOAP (que j'ai mise dans le sous-répertoire nusoap).
Ensuite on crée un nouveau serveur soap et on l'initialise.
La méthode register permet d'indiquer le nom de la méthode php qui va être appelée ainsi que les paramètres d'entrée et de sortie (nom et type).
Notez que pour fonctionner avec Silverlight, le style doit être rpc et l'usage literal.
Il ne reste qu'à créer la simple méthode gethelloworld qui, comme on le voit, retourne « hello » concaténé au nom passé en paramètre.
Le web service est consultable à cette adresse : https://nicopyright.developpez.com/tutoriel/vs2008/csharp/uploadsilverlightandphp/helloworld.php
Le WSDL généré est
<?xml version="1.0" encoding="ISO-8859-1"?>
<definitions
xmlns
:
SOAP-ENV
=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns
:
xsd
=
"http://www.w3.org/2001/XMLSchema"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
SOAP-ENC
=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns
:
tns
=
"urn:helloworldwsdl"
xmlns
:
soap
=
"http://schemas.xmlsoap.org/wsdl/soap/"
xmlns
:
wsdl
=
"http://schemas.xmlsoap.org/wsdl/"
xmlns
=
"http://schemas.xmlsoap.org/wsdl/"
targetNamespace
=
"urn:helloworldwsdl"
>
<types>
<
xsd
:
schema
targetNamespace
=
"urn:helloworldwsdl"
>
<
xsd
:
import
namespace
=
"http://schemas.xmlsoap.org/soap/encoding/"
/>
<
xsd
:
import
namespace
=
"http://schemas.xmlsoap.org/wsdl/"
/>
</
xsd
:
schema>
</types>
<message
name
=
"gethelloworldRequest"
>
<part
name
=
"nom"
type
=
"xsd:string"
/>
</message>
<message
name
=
"gethelloworldResponse"
>
<part
name
=
"return"
type
=
"xsd:string"
/>
</message>
<portType
name
=
"helloworldwsdlPortType"
>
<operation
name
=
"gethelloworld"
>
<documentation>
Le traditionnel Hello World</documentation>
<input
message
=
"tns:gethelloworldRequest"
/>
<output
message
=
"tns:gethelloworldResponse"
/>
</operation>
</portType>
<binding
name
=
"helloworldwsdlBinding"
type
=
"tns:helloworldwsdlPortType"
>
<
soap
:
binding
style
=
"rpc"
transport
=
"http://schemas.xmlsoap.org/soap/http"
/>
<operation
name
=
"gethelloworld"
>
<
soap
:
operation
soapAction
=
"urn:helloworldwsdl#addliste"
style
=
"rpc"
/>
<input><
soap
:
body
use
=
"literal"
namespace
=
"urn:helloworldwsdl"
/></input>
<output><
soap
:
body
use
=
"literal"
namespace
=
"urn:helloworldwsdl"
/></output>
</operation>
</binding>
<service
name
=
"helloworldwsdl"
>
<port
name
=
"helloworldwsdlPort"
binding
=
"tns:helloworldwsdlBinding"
>
<
soap
:
address
location
=
"http://nico-pyright.developpez.com/tutoriel/vs2008/csharp/uploadsilverlightandphp/helloworld.php"
/>
</port>
</service>
</definitions>
3.2.3.Consommer le web service avec Silverlight▲
Pour utiliser le web service dans une application Silverlight, il faut ajouter une référence service :
Bouton droit sur le projet => Add service reference.
Il faudra renseigner l'url du wsdl et y naviguer comme sur l'image ci-dessous.
Un clic sur le bouton OK permet à Visual Studio de générer toute la mécanique d'appel de ce web service.
L'appel à un tel web service se fait de manière asynchrone. Créons une petite application pour pouvoir tester ce web service.
<UserControl
x
:
Class
=
"DemoHelloWorld.Page"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns
:
x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
Width
=
"400"
Height
=
"300"
>
<Canvas>
<TextBlock
Text
=
"Saisissez votre nom :"
Canvas.
Top
=
"5"
/>
<TextBox
x
:
Name
=
"name"
Width
=
"200"
Canvas.
Left
=
"150"
/>
<Button
Content
=
"Appeler le web service"
Width
=
"200"
Height
=
"35"
Canvas.
Top
=
"35"
Click
=
"Button_Click"
/>
<TextBlock
x
:
Name
=
"result"
Canvas.
Top
=
"45"
Canvas.
Left
=
"220"
Foreground
=
"Red"
/>
</Canvas>
</UserControl>
Ce XAML permet de créer un champ de saisie et un bouton qui va appeler notre web service.
Le code associé au clic du bouton, ci-dessous, permet d'appeler le web service en lui passant le contenu du textbox en paramètre. Il faudra instancier la classe helloworldwsdlPortTypeClient.
On s'abonne ensuite à l'événement client_gethelloworldCompleted afin d'être notifié de la fin de l'appel au web service, ce qui nous permettra notamment de traiter la réponse de celui-ci.
private
void
Button_Click
(
object
sender,
RoutedEventArgs e)
{
helloworldwsdlPortTypeClient client =
new
helloworldwsdlPortTypeClient
(
);
client.
gethelloworldCompleted +=
client_gethelloworldCompleted;
client.
gethelloworldAsync
(
name.
Text);
}
void
client_gethelloworldCompleted
(
object
sender,
gethelloworldCompletedEventArgs e)
{
string
toDisplay;
try
{
toDisplay =
e.
Result;
}
catch
(
Exception ex)
{
toDisplay =
string
.
Format
(
"Impossible d'appeler le web service : {0}"
,
ex.
Message);
}
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
toDisplay;
}
);
}
On peut noter qu'on récupère le résultat de notre web service dans la variable e.Result. On l'affiche, ce qui nous donne :
Vous pouvez tester cet exemple à cette adresse.
3.3.Le web service d'envoi de fichier▲
Pour uploader un fichier, on va créer un web service qui prend en paramètre le nom du fichier ainsi que son contenu pour l'écrire sur le disque.
Bien sûr, envoyer le fichier en entier est une mauvaise idée, on va l'envoyer petit bout par petit bout en rajoutant un paramètre pour indiquer si on doit créer le fichier ou bien rajouter le contenu à la suite.
Les données envoyées pouvant être de toutes sortes, on va les encoder afin qu'elles puissent transiter tranquillement. À cet effet, j'ai choisi d'utiliser l'encodage en base 64.
Voici le web service qu'on pourrait écrire pour faire une telle chose (fileupload.php) :
<?php
require_once('nusoap/nusoap.php'
);
$server
=
new
soap_server();
// Initialize WSDL support
$server
->
configureWSDL('uploadwsdl'
,
'urn:uploadwsdl'
);
// Register the method to expose
$server
->
register('upload'
,
// method name
array
('mode'
=>
'xsd:int'
,
'filename'
=>
'xsd:string'
,
'filecontent'
=>
'xsd:string'
),
// input parameters
array
('return'
=>
'xsd:string'
),
// output parameters
'urn:uploadwsdl'
,
// namespace
'urn:uploadwsdl#upload'
,
// soapaction
'rpc'
,
// style
'literal'
,
// use
'Upload un fichier'
// documentation
);
// Define the method as a PHP function
function
upload($mode
,
$filename
,
$filecontent
)
{
if
($mode
==
0
)
{
$fh
=
fopen($filename
,
'w'
) or
die("can't open file"
);
}
if
($mode
==
1
)
{
$fh
=
fopen($filename
,
'a'
) or
die("can't open file"
);
}
$stringData
=
base64_decode($filecontent
);
fwrite($fh
,
$stringData
);
fclose($fh
);
return
'OK'
;
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA
=
isset($HTTP_RAW_POST_DATA
) ?
$HTTP_RAW_POST_DATA
:
''
;
$server
->
service($HTTP_RAW_POST_DATA
);
?>
Rien d'extraordinaire. Juste un détail, on passe un mode afin de lui indiquer si on écrit le fichier (cas du premier packet) ou si on ajoute au fichier (cas des paquets suivants).
L'ajout au fichier à partir du contenu se fait en ayant décodé la chaine passée en base 64.
N. B. Il pourrait être judicieux d'implémenter une compression pour réduire la taille de transfert du fichier.
3.4.Côté Silverlight▲
Nous allons donc appeler ce web service depuis Silverlight afin de faire notre upload de fichier. Pour ce faire, créons une petite application de démo :
<Canvas>
<TextBlock
x
:
Name
=
"textEnvoyer"
Text
=
"Choisir un fichier : "
Canvas.
Top
=
"5"
/>
<Button
x
:
Name
=
"btnParcourir"
Content
=
"Parcourir ..."
Canvas.
Left
=
"120"
Width
=
"90"
Height
=
"30"
Click
=
"Upload_Click"
/>
<TextBlock
x
:
Name
=
"result"
Canvas.
Top
=
"50"
/>
</Canvas>
Une interface très simple qui lors du clic sur le bouton va nous permettre d'utiliser le contrôle OpenFileDialog.
public
partial
class
Page :
UserControl
{
private
const
int
_packetSize =
32768
;
private
FileStream _fileStream;
private
string
_fileName;
public
Page
(
)
{
InitializeComponent
(
);
}
private
void
Upload_Click
(
object
sender,
RoutedEventArgs e)
{
try
{
OpenFileDialog dialog =
new
OpenFileDialog
{
Filter =
"Image files (*.gif;*.jpg;*.png)|*.gif;*.jpg;*.png"
,
Multiselect =
false
};
if
(
dialog.
ShowDialog
(
) ==
true
)
{
FileInfo fichier =
dialog.
File;
_fileName =
fichier.
Name;
_fileStream =
fichier.
OpenRead
(
);
btnParcourir.
IsEnabled =
false
;
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
"Envoi en cours ..."
;
}
);
uploadwsdlPortTypeClient client =
new
uploadwsdlPortTypeClient
(
);
client.
uploadCompleted +=
client_uploadCompleted;
string
toUpload =
GetNextPacket
(
);
client.
uploadAsync
(
0
,
_fileName,
toUpload);
}
}
catch
(
Exception ex)
{
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
string
.
Format
(
"Erreur d'envoi : {0}"
,
ex.
InnerException.
Message);
}
);
}
}
private
string
GetNextPacket
(
)
{
int
lengthToRead =
(
int
)(
_fileStream.
Length >
_packetSize ?
_packetSize :
_fileStream.
Length);
byte
[]
buffer =
new
byte
[
lengthToRead];
for
(
int
i =
0
;
i <
lengthToRead;
i++
)
{
int
value
=
_fileStream.
ReadByte
(
);
if
(
value
==
-
1
)
{
return
GetEncodedValue
(
buffer,
i);
}
buffer[
i]
=
(
byte
)value
;
}
return
GetEncodedValue
(
buffer,
lengthToRead);
}
private
static
string
GetEncodedValue
(
byte
[]
buffer,
int
i)
{
byte
[]
newBuffer =
new
byte
[
i];
Array.
Copy
(
buffer,
newBuffer,
i);
return
Convert.
ToBase64String
(
newBuffer);
}
void
client_uploadCompleted
(
object
sender,
uploadCompletedEventArgs e)
{
try
{
if
(
e.
Result !=
"OK"
)
{
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
"Erreur d'envoi !"
;
}
);
}
else
{
uploadwsdlPortTypeClient client =
new
uploadwsdlPortTypeClient
(
);
client.
uploadCompleted +=
client_uploadCompleted;
string
toUpload =
GetNextPacket
(
);
if
(!
string
.
IsNullOrEmpty
(
toUpload))
client.
uploadAsync
(
1
,
_fileName,
toUpload);
else
{
Dispatcher.
BeginInvoke
((
) =>
{
btnParcourir.
IsEnabled =
true
;
}
);
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
"Envoi terminé"
;
}
);
}
}
}
catch
(
Exception ex)
{
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
string
.
Format
(
"Erreur d'envoi : {0}"
,
ex.
InnerException.
Message);
}
);
}
}
}
Notez que pour l'exemple, j'ai choisi de limiter la sélection des fichiers aux extensions gif, jpg et png. On peut bien sûr envoyer tout type de fichier.
Le lecteur attentif aura aussi remarqué que je définis des paquets d'une taille maximale de 32 768 octets.
On peut voir dans la méthode Upload_Click, qu'on permet la sélection d'un unique fichier (Multiselect = false).
Ensuite on appelle notre web service avec le premier paquet à envoyer. Notons qu'en paramètre, on lui passe 0 pour dire que c'est le premier paquet et que donc le fichier est à créer. On passe également le nom du fichier et le contenu de ce paquet qui est encodé en base64 grâce à Convert.ToBase64String.
La méthode client_uploadCompleted est appelée après qu'un paquet soit fini d'être envoyé. Tant que ce n'est pas fini, on répète l'envoi. Notez le paramètre à 1 pour indiquer qu'il faut ajouter le contenu au fichier.
3.5.Améliorations▲
Notre envoi de fichier est désormais fonctionnel. Mais si on le teste un peu en conditions réelles (réseau saturé, fichier volumineux, etc.) on se rend compte que l'envoi se termine rarement.
De plus, qu'est-ce qui nous permet de vérifier que le fichier a correctement été transmis ?
Ce sont ces améliorations que nous allons prendre en compte.
3.5.1.Réenvoi de paquet en cas d'erreur de transfert▲
Ce qu'on peut faire, c'est si le web service renvoie une erreur, ou si une exception est levée, on peut retenter le transfert (en gérant un nombre maximum de tentatives).
Par exemple, en cas de retour incorrect (renvoi différent de OK) ou si une exception est catchée, on peut incrémenter un compteur de tentative et retenter un envoi.
Par exemple, vous pouvez voir ci-dessous l'événement uploadCompleted appelé lors de la fin d'envoi d'un paquet. La fonction ManageError va permettre de tenter de réenvoyer le paquet, en gérant un nombre maximum de tentatives.
private
int
_nbTry;
public
int
MaxTryPerPacket {
get
;
set
;
}
void
uploadCompleted
(
object
sender,
uploadCompletedEventArgs e)
{
try
{
if
(
e.
Result !=
"OK"
)
ManageError
(
e,
null
);
else
{
// [...] un peu de code enlevé pour plus de clarté [...]
UploadPacket
(
);
}
}
catch
(
Exception ex)
{
ManageError
(
null
,
ex);
}
}
private
void
ManageError
(
uploadCompletedEventArgs e,
Exception ex)
{
_nbTry++;
if
(
_nbTry <=
MaxTryPerPacket)
UploadPacket
(
_toReUploadIfError);
else
{
if
(
ex !=
null
)
UploadError
(
ex);
else
UploadError
(
new
Exception
(
e.
Result));
}
}
3.5.2.Vérification d'upload de fichier▲
Une fois le fichier complètement envoyé, on peut vérifier qu'il est bien conforme à celui envoyé, soit en le retéléchargeant et en le comparant à l'original, soit en comparant un crc.
J'ai choisi d'utiliser la deuxième méthode, en me basant sur l'algorithme Secure Hash Algorithm 1.
Voici la méthode PHP qui calcule le sha1 d'un fichier en utilisant cet algorithme :
$server
->
register('
getsha
'
,
// method name
array('
filename
'
=>
'
xsd:string
'
),
// input parameters
array('
return
'
=>
'
xsd:string
'
),
// output parameters
'
urn:uploadwsdl
'
,
// namespace
'
urn:uploadwsdl#upload
'
,
// soapaction
'
rpc
'
,
// style
'
literal
'
,
// use
'
Recupere le sha1 du fichier
'
// documentation
);
function getsha($filename
)
{
return sha1_file($filename
);
}
Pour faire de même en C#, on utilisera
public
static
string
GetChecksum
(
string
file)
{
using
(
FileStream stream =
File.
OpenRead
(
file))
{
SHA1 sha =
new
SHA1Managed
(
);
byte
[]
checksum =
sha.
ComputeHash
(
stream);
return
BitConverter.
ToString
(
checksum).
Replace
(
"-"
,
String.
Empty);
}
}
Je propose de permettre la vérification du fichier grâce à un paramètre lorsque l'événement de fin de téléchargement de fichier est levé, soit :
public
delegate
void
CurrentFileUploadCompletedHanlder
(
CurrentFileUploadCompletedEventArgs args,
ref
bool
verifyUpload);
private
event
CurrentFileUploadCompletedHanlder _currentFileUploadCompleted;
public
event
CurrentFileUploadCompletedHanlder OnCurrentFileUploadCompleted
{
add
{
_currentFileUploadCompleted +=
value
;
}
remove
{
_currentFileUploadCompleted -=
value
;
}
}
private
void
UploadPacket
(
string
toUpload)
{
// [...] un peu de code enlevé pour plus de clarté [...]
// envoi du fichier courant terminé
if
(
_currentFileUploadCompleted !=
null
)
{
bool
verifyUpload =
false
;
_currentFileUploadCompleted
(
new
CurrentFileUploadCompletedEventArgs
{
FileName =
_currentFileName,
FileSize =
_currentFileSize },
ref
verifyUpload);
if
(
verifyUpload)
{
uploadwsdlPortTypeClient service =
Client;
service.
getshaCompleted +=
service_getshaCompleted;
service.
getshaAsync
(
_currentFileName);
}
// [...] un peu de code enlevé pour plus de clarté [...]
}
// [...] un peu de code enlevé pour plus de clarté [...]
}
void
service_getshaCompleted
(
object
sender,
getshaCompletedEventArgs e)
{
if
(
_currentFileVerifyUpload !=
null
)
{
try
{
string
sha =
GetChecksum
(
_currentFileStream);
_currentFileVerifyUpload
(
new
VerifyUploadEventArgs
{
ResultOK =
string
.
Compare
(
e.
Result,
sha,
StringComparison.
InvariantCultureIgnoreCase) ==
0
,
FileName =
_currentFileName }
);
}
catch
(
Exception)
{
_currentFileVerifyUpload
(
new
VerifyUploadEventArgs
{
ResultOK =
false
,
FileName =
_currentFileName }
);
}
}
// [...] un peu de code enlevé pour plus de clarté [...]
}
Pour s'abonner à cet événement :
WSUploader wsUploader =
new
WSUploader
(
);
wsUploader.
OnCurrentFileUploadCompleted +=
wsUploader_OnCurrentFileUploadCompleted;
// [...] un peu de code enlevé pour plus de clarté [...]
void
wsUploader_OnCurrentFileUploadCompleted
(
CurrentFileUploadCompletedEventArgs args,
ref
bool
verifyUpload)
{
verifyUpload =
true
;
}
Donc, si on s'est abonné à l'événement de fin de téléchargement du fichier courant, et qu'on passe la variable verifyUpload à true, alors on appelle le web service qui permet de calculer le sha1 du fichier.
On le compare ainsi avec celui écrit en C# et basé sur le FileStream du fichier qu'on envoie afin de vérifier que le fichier a été correctement reçu.
3.5.3.Upload de plusieurs fichiers▲
Il pourrait aussi être intéressant de pouvoir uploader plusieurs fichiers d'un coup, et d'être notifié par événement lorsqu'un fichier est terminé ou que tout est terminé.
Il faudra prendre garde à la synchronisation. En effet, les appels au web service étant asynchrones, on risque d'envoyer tous les fichiers en même temps et de faire n'importe quoi.
On gérera la synchronisation avec un AutoResetEvent.
Je ne vais pas rentrer dans le détail dans cet article, mais sachez qu'on pourra utiliser cet objet pour attendre qu'un envoi de fichier soit terminé.
Ainsi, l'envoi de plusieurs fichiers pourra s'écrire ainsi (on passe en paramètre un dictionnaire contenant le stream et le nom de chaque fichier) :
private
static
AutoResetEvent autoEvent;
public
void
Upload
(
Dictionary<
FileStream,
string
>
fileDico)
{
// [...] un peu de code enlevé pour plus de clarté [...]
Thread thread =
new
Thread
(
delegate
(
)
{
foreach
(
KeyValuePair<
FileStream,
string
>
element in
fileDico)
{
autoEvent =
new
AutoResetEvent
(
false
);
// [...] un peu de code enlevé pour plus de clarté [...]
UploadPacket
(
);
autoEvent.
WaitOne
(
);
}
// [...] un peu de code enlevé pour plus de clarté [...]
}
);
thread.
Start
(
);
}
Ainsi, on démarre un thread qui va boucler sur tous les éléments du dictionnaire.
On déclare un AutoResetEvent et on envoie les paquets.
autoEvent.WaitOne(); va permettre d'attendre que l'opération soit complètement terminée. Plus précisément, WaitOne va attendre qu'une action se passe sur cet objet. Ceci implique que l'on fasse une action quand le fichier est terminé.
On utilisera :
autoEvent.
Set
(
);
Ceci implique qu'il faudra informer de la fin de l'envoi dans tous les cas possibles, c'est-à-dire lorsque le fichier est terminé ou qu'il y a eu une erreur, ou qu'on a choisi de prolonger le traitement avec une vérification de fichier, etc. (vous verrez ces différents cas d'utilisation dans le code complet plus bas).
3.5.4.Paramétrage de l'emplacement du web service▲
Par défaut, la génération du code utile pour le web service (voir § 3.2.3.Consommer le web service avec Silverlight stocke l'url du web service. Mais il peut être utile de passer cette url par code, si jamais l'url change, on n'a pas besoin de régénérer le code.
Pour ce faire, on pourra instancier le web service de cette façon :
new
uploadwsdlPortTypeClient
(
new
System.
ServiceModel.
BasicHttpBinding
(
System.
ServiceModel.
BasicHttpSecurityMode.
None),
new
System.
ServiceModel.
EndpointAddress
(
"adresse du web service"
));
Sachant que pour ma part, j'ai choisi de paramétrer ceci dans une classe statique.
3.5.5.Suppression du fichier▲
En cas d'erreur, on peut vouloir laisser le choix à l'utilisateur de supprimer le fichier (mal) uploadé.
public
delegate
void
CurrentFileErrorUploadHanlder
(
CurrentFileErrorUploadEventArgs args,
ref
bool
DeleteFileOnServer);
private
event
CurrentFileErrorUploadHanlder _currentFileUploadError;
public
event
CurrentFileErrorUploadHanlder OnCurrentFileUploadError
{
add
{
_currentFileUploadError +=
value
;
}
remove
{
_currentFileUploadError -=
value
;
}
}
private
void
UploadError
(
Exception exception)
{
if
(
_currentFileUploadError !=
null
)
{
bool
deleteFile =
false
;
_currentFileUploadError
(
new
CurrentFileErrorUploadEventArgs
{
InnerException =
exception,
FileName =
_currentFileName
},
ref
deleteFile);
if
(
deleteFile)
DeleteFile
(
);
}
// [...] un peu de code enlevé pour plus de clarté [...]
}
private
void
DeleteFile
(
)
{
uploadwsdlPortTypeClient service =
Client;
service.
deleteAsync
(
_currentFileName);
}
Avec un web service pour effacer le fichier :
$server->
register
(
'delete'
,
// method name
array
(
'filename'
=>
'xsd:string'
),
// input parameters
array
(
'return'
=>
'xsd:string'
),
// output parameters
'urn:uploadwsdl'
,
// namespace
'urn:uploadwsdl#upload'
,
// soapaction
'rpc'
,
// style
'literal'
,
// use
'Supprime un fichier'
// documentation
);
function delete
(
$filename)
{
if
(
unlink
(
$filename))
return
'OK'
;
return
'KO'
;
}
3.6.Nouvelles améliorations▲
Après avoir utilisé l'upload de fichier de manière plus poussée, je me suis rendu compte que tout n'était pas encore parfait. J'ai donc procédé à quelques nouvelles améliorations.
3.6.1.Envoi et vérification▲
Pourquoi attendre que tout soit envoyé pour vérifier que le transfert est OK ? C'est suite à l'envoi de très gros fichiers que je me suis posé cette question.
Pour ce faire, j'ai rajouté un paramètre lors de l'envoi de données qui contient le checksum des données envoyées. Ainsi, côté serveur on pourra vérifier que le contenu envoyé est bien celui escompté.
La méthode d'upload en php devient donc :
$server
->
register('
upload
'
,
array('
mode
'
=>
'
xsd:int
'
,
'
filename
'
=>
'
xsd:string
'
,
'
filecontent
'
=>
'
xsd:string
'
,
'
checksum
'
=>
'
xsd:string
'
),
array('
return
'
=>
'
xsd:string
'
),
'
urn:uploadwsdl
'
,
'
urn:uploadwsdl#upload
'
,
// soapaction
'
rpc
'
,
// style
'
literal
'
,
// use
'
Upload un fichier
'
// documentation
);
function upload($mode
,
$filename
,
$filecontent
,
$checksum
)
{
$stringData
=
base64_decode($filecontent
);
if (strcasecmp(sha1($stringData
),
$checksum
) !=
0
)
return '
KO
'
;
if ($mode
==
0
)
$fh
=
fopen($filename
,
'
wb
'
) or die("
can't open file
"
);
if ($mode
==
1
)
$fh
=
fopen($filename
,
'
ab
'
) or die("
can't open file
"
);
fwrite($fh
,
$stringData
);
fclose($fh
);
return '
OK
'
;
}
Côté Silverlight, l'envoi pourra se faire ainsi :
string
checkSum =
GetChecksum
(
new
MemoryStream
(
Convert.
FromBase64String
(
toUpload)));
service.
uploadAsync
((
int
)_uploadType,
_currentFileName,
toUpload,
checkSum);
3.6.2.Catch d'exception▲
Je me suis rendu compte aussi empiriquement que lorsque je recevais une exception de type TargetInvocationException qui contenait une InnerException de type ProtocolException après un envoi de données, lors de la lecture du résultat, alors le résultat était correct.
C'est une exception que je ne m'explique pas, mais il se trouve que si je ne l'attrapais pas, alors le client croyait que la donnée était à renvoyer alors qu'elle avait été correctement ajoutée au fichier sur le serveur.
Ce qui faisait que les fichiers uploadés avaient parfois des données en double, et ne passaient pas à la vérification.
J'ai donc modifié mon code en conséquence.
3.7.Le WSUploader▲
Voici la classe WSUploader, avec les nouvelles améliorations, qui fait ce travail d'upload :
namespace
DemoUpload
{
public
class
WSUploader
{
private
enum
UploadType
{
CREATE =
0
,
APPEND =
1
}
private
static
AutoResetEvent autoEvent;
private
static
UploadType _uploadType;
private
string
_currentFileName;
private
Stream _currentFileStream;
private
long
_currentFileSize;
private
long
_totalSizeToUpload;
private
long
_currentSizeUploaded;
private
long
_totalSizeUploaded;
private
string
_toReUploadIfError;
private
int
_nbTry;
private
bool
_hasError;
private
List<
string
>
_successfullFiles;
private
List<
string
>
_errorFiles;
#region current file handler
public
delegate
void
CurrentFileErrorUploadHanlder
(
CurrentFileErrorUploadEventArgs args,
ref
bool
DeleteFileOnServer);
private
event
CurrentFileErrorUploadHanlder _currentFileUploadError;
public
event
CurrentFileErrorUploadHanlder OnCurrentFileUploadError
{
add
{
_currentFileUploadError +=
value
;
}
remove
{
_currentFileUploadError -=
value
;
}
}
public
delegate
void
CurrentFileUploadProgressHanlder
(
CurrentFileProgressEventArgs args);
private
event
CurrentFileUploadProgressHanlder _currentFileUploadProgress;
public
event
CurrentFileUploadProgressHanlder OnCurrentFileUploadProgress
{
add
{
_currentFileUploadProgress +=
value
;
}
remove
{
_currentFileUploadProgress -=
value
;
}
}
public
delegate
void
CurrentFileUploadCompletedHanlder
(
CurrentFileUploadCompletedEventArgs args,
ref
bool
verifyUpload);
private
event
CurrentFileUploadCompletedHanlder _currentFileUploadCompleted;
public
event
CurrentFileUploadCompletedHanlder OnCurrentFileUploadCompleted
{
add
{
_currentFileUploadCompleted +=
value
;
}
remove
{
_currentFileUploadCompleted -=
value
;
}
}
public
delegate
void
CurrentFileVerifyUploadHanlder
(
VerifyUploadEventArgs args);
private
event
CurrentFileVerifyUploadHanlder _currentFileVerifyUpload;
public
event
CurrentFileVerifyUploadHanlder OnCurrentFileVerifyUpload
{
add
{
_currentFileVerifyUpload +=
value
;
}
remove
{
_currentFileVerifyUpload -=
value
;
}
}
#endregion
#region all files handler
public
delegate
void
ErrorUploadHanlder
(
ErrorUploadEventArgs args);
private
event
ErrorUploadHanlder _uploadError;
public
event
ErrorUploadHanlder OnUploadError
{
add
{
_uploadError +=
value
;
}
remove
{
_uploadError -=
value
;
}
}
public
delegate
void
UploadProgressHanlder
(
ProgressEventArgs args);
private
event
UploadProgressHanlder _uploadProgress;
public
event
UploadProgressHanlder OnUploadProgress
{
add
{
_uploadProgress +=
value
;
}
remove
{
_uploadProgress -=
value
;
}
}
public
delegate
void
UploadCompletedHanlder
(
UploadCompletedEventArgs args);
private
event
UploadCompletedHanlder _uploadCompleted;
public
event
UploadCompletedHanlder OnUploadCompleted
{
add
{
_uploadCompleted +=
value
;
}
remove
{
_uploadCompleted -=
value
;
}
}
#endregion
public
WSUploader
(
)
{
MaxTryPerPacket =
5
;
PacketSize =
32768
;
}
private
static
uploadwsdlPortTypeClient Client
{
get
{
return
new
uploadwsdlPortTypeClient
(
new
BasicHttpBinding
(
BasicHttpSecurityMode.
None),
new
EndpointAddress
(
Config.
UPLOAD_WEB_SERVICE_PATH));
}
}
public
int
PacketSize {
get
;
set
;
}
public
int
MaxTryPerPacket {
get
;
set
;
}
public
void
Upload
(
Dictionary<
FileStream,
string
>
fileDico)
{
_successfullFiles =
new
List<
string
>(
);
_errorFiles =
new
List<
string
>(
);
_totalSizeToUpload =
fileDico.
Sum
(
kp =>
kp.
Key.
Length);
Thread thread =
new
Thread
(
delegate
(
)
{
foreach
(
KeyValuePair<
FileStream,
string
>
element in
fileDico)
{
_uploadType =
UploadType.
CREATE;
autoEvent =
new
AutoResetEvent
(
false
);
KeyValuePair<
FileStream,
string
>
currentElement =
element;
_currentSizeUploaded =
0
;
_currentFileStream =
currentElement.
Key;
_currentFileName =
currentElement.
Value;
_currentFileSize =
_currentFileStream.
Length;
UploadPacket
(
);
autoEvent.
WaitOne
(
);
}
// tous les envois sont terminés
if
(
_hasError)
{
if
(
_uploadError !=
null
)
_uploadError
(
new
ErrorUploadEventArgs
{
FileNameWithError =
_errorFiles,
SuccessFullFileName =
_successfullFiles }
);
}
else
{
if
(
_uploadCompleted !=
null
)
_uploadCompleted
(
new
UploadCompletedEventArgs
{
UploadedFiles =
_successfullFiles,
TotalUploadedSize =
_totalSizeToUpload }
);
}
}
);
thread.
Start
(
);
}
void
uploadCompleted
(
object
sender,
uploadCompletedEventArgs e)
{
try
{
if
(
e.
Result !=
"OK"
)
ManageError
(
e,
null
);
else
ContinueUpload
(
);
}
catch
(
TargetInvocationException ex)
{
if
(
ex.
InnerException !=
null
&&
ex.
InnerException is
ProtocolException)
ContinueUpload
(
);
else
ManageError
(
null
,
ex);
}
catch
(
Exception ex)
{
ManageError
(
null
,
ex);
}
}
private
void
ContinueUpload
(
)
{
_uploadType =
UploadType.
APPEND;
if
(
_currentFileUploadProgress !=
null
)
_currentFileUploadProgress
(
new
CurrentFileProgressEventArgs
{
ProgressPercentage =
((
double
)_currentSizeUploaded *
100
) /
_currentFileStream.
Length,
FileName =
_currentFileName }
);
if
(
_uploadProgress !=
null
)
_uploadProgress
(
new
ProgressEventArgs
{
ProgressPercentage =
((
double
)_totalSizeUploaded *
100
) /
_totalSizeToUpload }
);
UploadPacket
(
);
}
private
void
UploadPacket
(
)
{
string
toUpload =
GetNextPacket
(
);
_toReUploadIfError =
toUpload;
_nbTry =
0
;
UploadPacket
(
toUpload);
}
private
void
UploadPacket
(
string
toUpload)
{
if
(!
string
.
IsNullOrEmpty
(
toUpload))
{
try
{
uploadwsdlPortTypeClient service =
Client;
service.
uploadCompleted +=
uploadCompleted;
string
checkSum =
GetChecksum
(
new
MemoryStream
(
Convert.
FromBase64String
(
toUpload)));
service.
uploadAsync
((
int
)_uploadType,
_currentFileName,
toUpload,
checkSum);
}
catch
(
Exception ex)
{
UploadError
(
ex);
}
}
else
{
// envoi du fichier courant terminé
if
(
_currentFileUploadCompleted !=
null
)
{
bool
verifyUpload =
false
;
_currentFileUploadCompleted
(
new
CurrentFileUploadCompletedEventArgs
{
FileName =
_currentFileName,
FileSize =
_currentFileSize },
ref
verifyUpload);
if
(
verifyUpload)
{
_nbTry =
0
;
DoCheckSum
(
);
}
else
{
_successfullFiles.
Add
(
_currentFileName);
autoEvent.
Set
(
);
}
}
else
{
_successfullFiles.
Add
(
_currentFileName);
autoEvent.
Set
(
);
}
}
}
private
void
DoCheckSum
(
)
{
uploadwsdlPortTypeClient service =
Client;
service.
getshaCompleted +=
service_getshaCompleted;
service.
getshaAsync
(
_currentFileName);
}
private
static
string
GetChecksum
(
Stream fileStream)
{
fileStream.
Seek
(
0
,
SeekOrigin.
Begin);
SHA1 sha =
new
SHA1Managed
(
);
byte
[]
checksum =
sha.
ComputeHash
(
fileStream);
return
BitConverter.
ToString
(
checksum).
Replace
(
"-"
,
String.
Empty);
}
void
service_getshaCompleted
(
object
sender,
getshaCompletedEventArgs e)
{
Stream currentFileStream =
_currentFileStream;
string
currentFileName =
_currentFileName;
if
(
_currentFileVerifyUpload !=
null
)
{
try
{
string
sha =
GetChecksum
(
currentFileStream);
bool
resultOK =
string
.
Compare
(
e.
Result,
sha,
StringComparison.
InvariantCultureIgnoreCase) ==
0
;
_currentFileVerifyUpload
(
new
VerifyUploadEventArgs
{
ResultOK =
resultOK,
FileName =
currentFileName
}
);
if
(
resultOK)
_successfullFiles.
Add
(
currentFileName);
else
{
_hasError =
true
;
_errorFiles.
Add
(
currentFileName);
}
autoEvent.
Set
(
);
}
catch
(
Exception)
{
_nbTry++;
if
(
_nbTry <=
MaxTryPerPacket)
DoCheckSum
(
);
else
{
_currentFileVerifyUpload
(
new
VerifyUploadEventArgs
{
ResultOK =
false
,
FileName =
currentFileName }
);
_errorFiles.
Add
(
currentFileName);
_hasError =
true
;
autoEvent.
Set
(
);
}
}
}
else
{
_successfullFiles.
Add
(
currentFileName);
autoEvent.
Set
(
);
}
}
private
void
ManageError
(
uploadCompletedEventArgs e,
Exception ex)
{
_nbTry++;
if
(
_nbTry <=
MaxTryPerPacket)
UploadPacket
(
_toReUploadIfError);
else
{
if
(
ex !=
null
)
UploadError
(
ex);
else
UploadError
(
new
Exception
(
string
.
Format
(
"Nombre maxi de tentatives atteintes : {0}"
,
e.
Result)));
}
}
private
void
UploadError
(
Exception exception)
{
if
(
_currentFileUploadError !=
null
)
{
bool
deleteFile =
false
;
_currentFileUploadError
(
new
CurrentFileErrorUploadEventArgs
{
InnerException =
exception,
FileName =
_currentFileName },
ref
deleteFile);
if
(
deleteFile)
DeleteFile
(
);
}
_errorFiles.
Add
(
_currentFileName);
_hasError =
true
;
autoEvent.
Set
(
);
}
private
void
DeleteFile
(
)
{
uploadwsdlPortTypeClient service =
Client;
service.
deleteAsync
(
_currentFileName);
}
private
string
GetNextPacket
(
)
{
int
lengthToRead =
(
int
)(
_currentFileStream.
Length >
PacketSize ?
PacketSize :
_currentFileStream.
Length);
byte
[]
buffer =
new
byte
[
lengthToRead];
for
(
int
i =
0
;
i <
lengthToRead;
i++
)
{
int
value
=
-
1
;
int
nbTry =
0
;
bool
notok =
true
;
while
(
notok &&
nbTry <
5
)
{
try
{
value
=
_currentFileStream.
ReadByte
(
);
notok =
false
;
}
catch
(
IndexOutOfRangeException)
{
nbTry++;
Thread.
Sleep
(
300
);
}
}
if
(
nbTry ==
5
)
value
=
-
1
;
if
(
value
==
-
1
)
{
_currentSizeUploaded +=
i;
_totalSizeUploaded +=
i;
return
GetEncodedValue
(
buffer,
i);
}
buffer[
i]
=
(
byte
)value
;
}
_currentSizeUploaded +=
lengthToRead;
_totalSizeUploaded +=
lengthToRead;
return
GetEncodedValue
(
buffer,
lengthToRead);
}
private
static
string
GetEncodedValue
(
byte
[]
buffer,
int
i)
{
byte
[]
newBuffer =
new
byte
[
i];
Array.
Copy
(
buffer,
newBuffer,
i);
return
Convert.
ToBase64String
(
newBuffer);
}
}
public
class
VerifyUploadEventArgs
{
public
bool
ResultOK {
get
;
set
;
}
public
string
FileName {
get
;
set
;
}
}
public
class
UploadCompletedEventArgs
{
public
List<
string
>
UploadedFiles {
get
;
set
;
}
public
long
TotalUploadedSize {
get
;
set
;
}
}
public
class
CurrentFileUploadCompletedEventArgs
{
public
string
FileName {
get
;
set
;
}
public
long
FileSize {
get
;
set
;
}
}
public
class
CurrentFileProgressEventArgs
{
public
double
ProgressPercentage {
get
;
set
;
}
public
string
FileName {
get
;
set
;
}
}
public
class
ProgressEventArgs
{
public
double
ProgressPercentage {
get
;
set
;
}
}
public
class
ErrorUploadEventArgs
{
public
List<
string
>
SuccessFullFileName {
get
;
set
;
}
public
List<
string
>
FileNameWithError {
get
;
set
;
}
}
public
class
CurrentFileErrorUploadEventArgs
{
public
string
FileName {
get
;
set
;
}
public
Exception InnerException {
get
;
set
;
}
}
}
4.Exemple d'application▲
4.1.Lier l'upload à un contrôle ProgressBar▲
On pourra désormais utiliser simplement l'upload, et le lier par exemple à une ProgressBar.
Pour cela, rien de plus simple :
<UserControl
x
:
Class
=
"DemoUpload.Page"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns
:
x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
>
<Canvas>
<TextBlock
x
:
Name
=
"textEnvoyer"
Text
=
"Choisir un fichier : "
Canvas.
Top
=
"5"
/>
<Button
x
:
Name
=
"btnParcourir"
Content
=
"Parcourir ..."
Canvas.
Left
=
"120"
Width
=
"90"
Height
=
"30"
Click
=
"Upload_Click"
/>
<TextBlock
x
:
Name
=
"result"
Canvas.
Top
=
"5"
Canvas.
Left
=
"250"
/>
<ProgressBar
x
:
Name
=
"GlobalProgressBar"
Width
=
"200"
Height
=
"20"
Canvas.
Top
=
"50"
Minimum
=
"0"
Maximum
=
"100"
/>
<ProgressBar
x
:
Name
=
"IndividualProgressBar"
Width
=
"200"
Height
=
"20"
Canvas.
Top
=
"90"
Minimum
=
"0"
Maximum
=
"100"
/>
<TextBlock
x
:
Name
=
"verif"
Canvas.
Top
=
"130"
/>
</Canvas>
</UserControl>
public
partial
class
Page :
UserControl
{
private
readonly
List<
FrameworkElement>
controlProgressBar;
public
Page
(
)
{
InitializeComponent
(
);
controlProgressBar =
new
List<
FrameworkElement>
{
GlobalProgressBar,
IndividualProgressBar,
verif };
SetVisibilityOnControls
(
controlProgressBar,
false
);
}
private
void
SetVisibilityOnControls
(
IEnumerable<
FrameworkElement>
elements,
bool
visible)
{
Dispatcher.
BeginInvoke
((
) =>
{
foreach
(
FrameworkElement element in
elements)
element.
Visibility =
visible ?
Visibility.
Visible :
Visibility.
Collapsed;
}
);
}
private
void
Upload_Click
(
object
sender,
RoutedEventArgs e)
{
try
{
OpenFileDialog dialog =
new
OpenFileDialog
{
Filter =
"Image files (*.gif;*.jpg;*.png)|*.gif;*.jpg;*.png"
,
Multiselect =
true
};
if
(
dialog.
ShowDialog
(
) ==
true
)
{
var
fichiers =
dialog.
Files;
GlobalProgressBar.
Value =
0
;
IndividualProgressBar.
Value =
0
;
SetVisibilityOnControls
(
controlProgressBar,
true
);
btnParcourir.
IsEnabled =
false
;
Dispatcher.
BeginInvoke
((
) =>
{
verif.
Text =
""
;
}
);
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
"Envoi en cours ..."
;
}
);
WSUploader wsUploader =
new
WSUploader {
MaxTryPerPacket =
10
};
wsUploader.
OnCurrentFileUploadCompleted +=
wsUploader_OnCurrentFileUploadCompleted;
wsUploader.
OnCurrentFileUploadError +=
wsUploader_OnCurrentFileUploadError;
wsUploader.
OnCurrentFileUploadProgress +=
wsUploader_OnCurrentFileUploadProgress;
wsUploader.
OnCurrentFileVerifyUpload +=
wsUploader_OnCurrentFileVerifyUpload;
wsUploader.
OnUploadCompleted +=
wsUploader_OnUploadCompleted;
wsUploader.
OnUploadError +=
wsUploader_OnUploadError;
wsUploader.
OnUploadProgress +=
wsUploader_OnUploadProgress;
wsUploader.
Upload
(
fichiers.
Select
(
f =>
f).
ToDictionary
(
f =>
f.
OpenRead
(
),
f =>
f.
Name));
}
}
catch
(
Exception ex)
{
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
ex.
InnerException.
Message;
}
);
}
}
void
wsUploader_OnCurrentFileVerifyUpload
(
VerifyUploadEventArgs args)
{
Dispatcher.
BeginInvoke
((
) =>
{
verif.
Text =
args.
ResultOK ?
"Vérification OK"
:
"Vérification KO"
;
}
);
}
void
wsUploader_OnCurrentFileUploadProgress
(
CurrentFileProgressEventArgs args)
{
Dispatcher.
BeginInvoke
((
) =>
{
IndividualProgressBar.
Value =
args.
ProgressPercentage;
}
);
}
void
wsUploader_OnCurrentFileUploadError
(
CurrentFileErrorUploadEventArgs args,
ref
bool
DeleteFileOnServer)
{
DeleteFileOnServer =
true
;
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
string
.
Format
(
"Erreur dans l'envoi du fichier {0} => {1}"
,
args.
FileName,
args.
InnerException.
Message);
}
);
}
void
wsUploader_OnCurrentFileUploadCompleted
(
CurrentFileUploadCompletedEventArgs args,
ref
bool
verifyUpload)
{
Dispatcher.
BeginInvoke
((
) =>
{
IndividualProgressBar.
Value =
100
;
}
);
verifyUpload =
true
;
}
void
wsUploader_OnUploadProgress
(
ProgressEventArgs args)
{
Dispatcher.
BeginInvoke
((
) =>
{
GlobalProgressBar.
Value =
args.
ProgressPercentage;
}
);
}
void
wsUploader_OnUploadError
(
ErrorUploadEventArgs args)
{
StringBuilder stringBuilder =
new
StringBuilder
(
);
foreach
(
string
file in
args.
FileNameWithError)
{
if
(
stringBuilder.
Length !=
0
)
stringBuilder.
Append
(
", "
);
stringBuilder.
Append
(
file);
}
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
string
.
Format
(
"Erreur dans l'envoi des fichiers : {0}"
,
stringBuilder);
}
);
}
void
wsUploader_OnUploadCompleted
(
UploadCompletedEventArgs args)
{
Dispatcher.
BeginInvoke
((
) =>
{
GlobalProgressBar.
Value =
100
;
}
);
Dispatcher.
BeginInvoke
((
) =>
{
IndividualProgressBar.
Value =
100
;
}
);
Dispatcher.
BeginInvoke
((
) =>
{
btnParcourir.
IsEnabled =
true
;
}
);
Dispatcher.
BeginInvoke
((
) =>
{
result.
Text =
"Envoi terminé"
;
}
);
}
}
Ce qui nous donne :
4.2.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>
4.3.Téléchargement▲
Vous pouvez télécharger ici les sources du projet (ws php inclus) : version rar (180 Ko) , version zip (238 Ko).
5.Conclusion▲
Cet article montre comment utiliser NuSOAP et Silverlight pour faire de l'upload de fichiers par web service sur un serveur utilisant PHP.
Remerciements▲
Je remercie Djé ainsi que l'équipe Dotnet pour leurs relectures attentives du document.
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.