Tutorial Silverlight 4: ajout d’une image dans le profile d’authentification utilisé par WCF RIA Services

For non-French speakers, you’ll find an English version of this article here .

Je vous propose de bénéficier de certaines des nouveautés de Silverlight 4 pour enrichir le template de base “Silverlight Business Application” de WCF RIA Services (nouveau nom de .NET RIA Services). Nous allons donc partir d’une application vierge et voir comment la modifier à la fois côté serveur et client pour que l’on puisse voir la tête de la personne qui s’est loguée en plus de son nom.

Silverlight 4 en lui-même a déjà été parfaitement couvert par nos amis de la communauté comme par exemple :

- le dossier MSDN sur Silverlight 4 réalisé par Access-IT : dossier Silverlight 4
- le livre blanc de Julien Dollon de la communauté Dotnet France : livre blanc sur Silverlight 4
- l’article Benjamin Roux de la communauté développez.com : les nouveautés de Silverlight 4
- l’article d’Olivier Dahan : Silverlight 4 : Mamma mia !!!
- Quelques articles de Laurent Kempé : Silverlight 4 Beta, Webcam & Silverlight 4 Beta, Drop Target et MVVM

Je ne reviendrais donc pas sur l’ensemble des nouveautés apportées par cette version dévoilée à la PDC09. Je vous laisse le soin de consulter ces excellentes ressources si vous en avez besoin.

Article mis à jour : mai 2010 pour la sortie de Silverlight 4 RTW et WCF RIA Services RTW

Le cahier des charges

Mon but à travers ce tutorial est d’arriver aux 2 écrans ci-dessous. Lors de la création du compte, proposer ce nouvel écran d’enregistrement :

RIAServicesPictureProfile001

Illustration 1 - Nouvel écran d’enregistrement

Une fois logué, simplement ajouter la photo précédemment fournie :

RIAServicesPictureProfile002

Illustration 2 – Nouveau contrôle affichant le statut de login

Il y a donc 2 grandes parties:

1 – Modifier la partie serveur notamment les classes RegistrationData et User pour le support de l’image qui sera envoyée via le réseau par le client Silverlight
2 – Modifier le client Silverlight en lui-même à 2 niveau:
  a – le contrôle LoginStatus qui affichera la photo de l’utilisateur logué
  b – le contrôle RegistrationForm qui proposera 3 moyens différents de fournir sa photo : depuis le disque via OpenFileDialog, via drag’n’drop ou finalement via la Webcam.

Pour les trop curieux (et/ou fainéants), retrouvez le résultat final à la fin de ce billet où je présente en vidéo le résultat ainsi que le code source à télécharger.

Pré-requis : il vous faudra Visual Studio 2010 RC, Silverlight 4 RC et WCF RIA Services RC pour pouvoir réaliser ce tutorial ou charger le projet final. Retrouver toutes ces ressources à télécharger ici : https://www.silverlight.net/getstarted/silverlight-4/#tools

Etape 1 : création de l’application et modification de la logique serveur

1. Lancez Visual Studio 2010 et sous la partie Silverlight, créez un projet de type “Silverlight Business Application”. Nommez le projet “RiaSvcProfilePicture” :

RIAServicesPictureProfile003

Voici la logique globale que l’on va mettre en place. D'une part, on va ajouter une propriété au profil de l’utilisateur qui contiendra l’URL de son avatar. Cette propriété sera ensuite utilisée par Silverlight pour afficher l’avatar dans le nouveau contrôle de login (illustration 2). Cette URL sera fabriquée par la couche ASP.NET lorsqu’elle recevra l’image depuis Silverlight.

Pour laisser Silverlight nous renvoyer une image via la couche WCF RIA Services, nous allons partir sur un tableau de bytes. Pour cela, nous allons donc ajouter une propriété à la classe RegistrationData qui sera projetée côté Silverlight par RIA Services. Implémentons tout cela maintenant.

2. Commençons par le plus simple: créez un nouveau répertoire à la racine RiaSvcProfilePicture.Web et nommez le UserPictures. C’est ici que nous stockerons les images des utilisateurs.

3. Rendez-vous dans la classe RegistrationData.cs et ajoutez cette nouvelle propriété en dessous de Answer :

 [Display(AutoGenerateField=false)]
public byte[] ProfilePicture { get; set; }

RegistrationData est un type qui sera automatiquement projeté par RIA Service côté Silverlight car il est par exemple utilisé dans la méthode :

 public void CreateUser(RegistrationData user)

de la classe UserRegistrationService décorée de l’attribut [EnableClientAccess].

En résumé, grâce à cet ajout, nous aurons une nouvelle propriété côté client Silverlight où nous pourrons y stocker notre image sous forme d’un tableau de bytes.

4. Rendez-vous maintenant dans la classe User.cs et ajoutez cette nouvelle propriété:

 [DataMember]
public string PictureURL { get; set; }

User est quand à lui un type utilisé dans la classe AuthenticationService également décorée de l’attribut magique. Nous aurons donc également accès à cette nouvelle propriété côté Silverlight.

Pour qu’elle soit automatiquement persistable au niveau du profil par les MemberShip Provider d’ASP.NET, rendez-vous dans le web.config et identifiez cette zone :

 <profile>
  <properties>
    <add name="FriendlyName"/>
  </properties>
</profile>

Et ajoutez-y ce XML :

 <add name="PictureURL"/>

5. Maintenant que nous nous sommes occupés des types, occupons-nous des méthodes qui vont les manipuler. Le but du jeu ? Récupérer la propriété ProfilePicture de la classe RegistrationData qui nous sera retournée et écrire le flux sur le disque. Pour terminer, il faut alors renvoyer l’URL correspondante à ce nouveau flux sur le disque. Pour faire cela, ajoutez les méthodes suivantes dans la classe UserRegistrationService :

 private Uri GetPictureUrl(byte[] imageToSave, string userName)
{
    Uri pictureUrl;

    if (imageToSave == null)
    {
        pictureUrl = ConstructUri("UserPictures/default.jpg");
    }
    else
    {
        try
        {
            string path = HttpContext.Current.Server.MapPath("~/UserPictures");
            string filePath = path + "/" + userName + ".jpg";
            FileStream pictureStream = new FileStream(filePath, FileMode.Create);
            
            pictureStream.Write(imageToSave, 0, imageToSave.Length);
            pictureStream.Close();

            pictureUrl = ConstructUri("UserPictures/" + userName + ".jpg");
        }
        catch (Exception ex)
        {
            pictureUrl = ConstructUri("UserPictures/default.jpg");
        }
    }

    return pictureUrl;
}

private static Uri ConstructUri(string path)
{
    return new Uri(GenerateUrl(path));
}

private static string GenerateUrl(string suffix)
{
    Uri currentHost = new Uri("https://" + HttpContext.Current.Request.Headers["Host"]);
    return new Uri(currentHost, suffix).ToString();
}

Pour cela, vous aurez besoin également de ces 2 using :

 using System.Web;
using System.IO;

On voit ici que le chemin sera constitué du répertoire UserPictures suivi du nom de l’utilisateur.jpg ou default.jpg s’il y a eu une erreur ou si l’utilisateur n’a fourni aucune image. Du coup, il vous faut créer une image par défaut dans le répertoire UserPictures. Personnellement, j’ai pris celle-là :

default

Vous pouvez donc la récupérer si vous la souhaitez. Elle venait à l’origine de mes expérimentations ActiveDirectory.Show que j’avais faites avec le sample Family.Show de Vertigo.

Pour terminer sur cette étape, il nous reste à persister cette URL dans le profile. Pour cela, rendez-vous dans la méthode CreateUser()   et sous cette ligne de code:

 profile.SetPropertyValue("FriendlyName", user.FriendlyName);

Ajoutez celles-là :

 Uri pictureUrl = GetPictureUrl(user.ProfilePicture, user.UserName);
profile.SetPropertyValue("PictureURL", pictureUrl.AbsoluteUri);

Voilà ! Nous avons fini le boulot côté serveur. Le reste va se passer au sein du projet Silverlight.

Etape 2 : Modification du contrôle LoginStatus

Pour info, tous les écrans qui nous intéressent se trouvent sous “Views\Login”.

1. Commençons à nouveau par le plus simple, le contrôle LoginStatus.xaml. En dessous du XAML suivant:

 <TextBlock x:Name="welcomeText" Style="{StaticResource WelcomeTextStyle}"/>

Ajoutez ce morceau:

 <Image x:Name="welcomeImage" Margin="10,0,0,0" Height="55"/>

C’est le contrôle Image qui affichera la tête de notre avatar comme dans l’illustration 2.

2. Maintenant, il nous faut augmenter la hauteur qui nous est autorisée pour afficher les informations de status de login. En effet, ce n’est pas assez haut pour afficher décemment notre tête. Pour cela, nous allons faire une légère retouche du style en nous dirigeant dans le fichier Styles.xaml du répertoire Assets. Identifiez le style LoginContainerStyle et modifiez cette propriété pour la passer de 21 à 55 :

 <Setter Property="Height" Value="55"/>

Profitons-en également pour régler un petit problème de cosmétique dans le style WelcomeTextStyle modifiez la propriété VerticalAlignment à Center :

 <Setter Property="VerticalAlignment" Value="Center"/>

Modifiez enfin la propriété Margin du style ContentBorderStyle pour faire passer la hauteur de 62 à 96 :

 <Setter Property="Margin" Value="0,96,0,0"/>

3. Pour alimenter l’image, ajoutez dans le constructeur de LoginStatus  en dessous cette ligne de code:

 this.welcomeText.SetBinding(TextBlock.TextProperty, 
  WebContext.Current.CreateOneWayBinding("User.DisplayName", 
  new StringFormatValueConverter(ApplicationStrings.WelcomeMessage)));

Cette nouvelle ligne:

 this.welcomeImage.SetBinding(Image.SourceProperty, 
  WebContext.Current.CreateOneWayBinding("User.PictureURL"));

A ce niveau là, vous pouvez déjà tester que notre application commence à fonctionner. Lancez l’application, cliquez sur login, cliquez sur “Register now”, remplissez tous les champs en respectant les règles de validation. Vous devriez alors arriver sur cette page :

RIAServicesPictureProfile004

En effet, nous ne pouvons pas encore soumettre une photo personnalisée donc le code côté serveur a pris l’image default.jpg comme avatar.

Etape 3 : Modification du contrôle RegistrationForm pour récupérer une image depuis le disque

1. Récuperez l’image default.jpg depuis la partie serveur et copiez-là dans le projet Silverlight dans le répertoire Assets.

2. Rendez-vous dans RegistrationForm.xaml et remplacez le contenu local:BusyIndicator par celui-ci:

<local:BusyIndicator x:Name="busyIndicator"
                        BusyContent="{Binding Path=ApplicationStrings.BusyIndicatorRegisteringUser, Source={StaticResource ResourceWrapper}}"
                        IsBusy="{Binding IsRegistering}">
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
            VerticalAlignment="Center">
<TextBlock Text="Profile picture :" VerticalAlignment="Center"/>
<Image x:Name="ImageProfileControl" Source="../../Assets/default.jpg"
            Height="55" Margin="10,0,0,0" />
<Button x:Name="btnWebCam" Content="from webcam" Margin="10" Click="btnWebCam_Click" />
<Button x:Name="btnDisk" Content="from disk" Margin="0,10" Click="btnDisk_Click" />
</StackPanel>
<Line Margin="5" />
<local:CustomDataForm x:Name="registerForm"
                            CurrentItem="{Binding}"
                            IsEnabled="{Binding IsRegistering, Converter={StaticResource NotOperatorValueConverter}}"
                            AutoEdit="True" CommandButtonsVisibility="None"
                            Header="{Binding Path=ApplicationStrings.RegistrationFormHeader, Source={StaticResource ResourceWrapper}}"
                            AutoGeneratingField="RegisterForm_AutoGeneratingField"
                            Style="{StaticResource DataFormStyle}" />
</StackPanel>
</local:BusyIndicator>

Sous Visual Studio, le designer devrait vous afficher quelque chose comme ça :

RIAServicesPictureProfile005

3. Allons dans le code C# behind et ajoutez ces membres privés :

 // bool used to indicate if the user wants to use the default 'default.jpg' 
// profile picture or not
private bool UseDefaultProfilePicture = true;
// stream cache element to send the appropriate 
// bytes[] back to the server
private Stream lastStreamProfilePicture = null;

N’oubliez pas un petit coup de using pour le Stream :

 using System.IO;

Ensuite, ajoutez ce gestionnaire d’évènement pour le click sur le bouton “from disk” :

 // Getting a profile picture directly from the disk
private void btnDisk_Click(object sender, RoutedEventArgs e)
{
    Stream profileImageStream = null;
    OpenFileDialog openWindow = new OpenFileDialog();
    openWindow.Filter = "Jpeg image files (*.jpg)|*.jpg|All files (*.*)|*.*";
    if (openWindow.ShowDialog() == true)
    {
        FileInfo targetFile = openWindow.File;
        profileImageStream = targetFile.OpenRead();
        // Saving a copy of the image's stream provided for future byte[] generation
        lastStreamProfilePicture = profileImageStream;

        System.Windows.Media.Imaging.BitmapImage image = 
           new System.Windows.Media.Imaging.BitmapImage();
        image.SetSource(profileImageStream);

        // Filling the XAML image control
        ImageProfileControl.Source = image;
        UseDefaultProfilePicture = false;
    }
}

Ce code s’occupe d’ouvrir une boîte de dialogue pour vous laisser choisir le fichier Jpeg qui va vous servir d’avatar. Brancher ce gestionnaire à l’évènement click du bouton côté XAML :

 <Button x:Name="btnDisk" Content="from disk" Margin="0,10" Click="btnDisk_Click" />

4. Avant de remplir avec notre image l’objet this.registrationData qui sera retourné par WCF RIA Services à notre couche serveur, nous allons mettre un place un petit traitement supplémentaire. Il nous juste une méthode retournant un tableau de bytes à partir du stream que nous avons caché en mémoire. La voici :

 private byte[] GetPictureAsBytes()
{
    if (lastStreamProfilePicture != null)
    {
        byte[] binaryData = new Byte[lastStreamProfilePicture.Length];
        lastStreamProfilePicture.Seek(0, SeekOrigin.Begin);
        long bytesRead = lastStreamProfilePicture.Read(binaryData, 0, 
               (int)lastStreamProfilePicture.Length);
        return binaryData;
    }

    return null;
}

5. Il ne nous reste plus qu’à alimenter l’objet qui va retourné vers la couche serveur. Pour cela, rendez-vous dans la méthode RegisterButton_Click et ajoutez le code suivant :

 // if the user has provided his own picture
// (via webcam, file access or drag'n'drop)
if (!UseDefaultProfilePicture)
{
    // Getting the picture as bytes
    byte[] profilePictureAsBytes = GetPictureAsBytes();

    // Pushing the result into the registrationData's
    // ProfilePicture member that will be sent back
    // automatically by Ria Services during the SubmitChanges op
    if (profilePictureAsBytes != null)
        this.registrationData.ProfilePicture = profilePictureAsBytes;

}

Avant cette ligne de code:

 this.registrationData.CurrentOperation = this.userRegistrationContext.CreateUser(
    this.registrationData,
    this.registrationData.Password,
    this.RegistrationOperation_Completed, null);

Vous pouvez à nouveau tester si l’application fonctionne bien à ce stade là. Lancez là, créez un nouvel utilisateur et choisissez une image en cliquant sur “from disk”. Logiquement, vous devriez alors avoir ce résultat (mais pas forcément avec la même image ;-)) :

RIAServicesPictureProfile006

Vous pouvez également noter que les images sont enregistrées sous la forme UserName.jpg dans le répertoire UserPictures.

Etape 4 : Modification du contrôle RegistrationForm pour récupérer une image en drag’n’drop

Jusqu’à présent, tout ce que nous avons fait, nous aurions pu le faire avec Silverlight 3. Commençons à utiliser une des nouveautés de la 4ème version : le support du drag’n’drop. Nous allons laisser l’utilisateur pouvoir glisser une image directement sur le formulaire.

1. Voici la méthode que nous allons appeler lorsque l’utilisateur déposera une image sur le formulaire :

 private void StackPanel_Drop(object sender, DragEventArgs e)
{
    // Get FileInfo array from DragEventArgs
    IDataObject dataObject = e.Data;
    var files = (FileInfo[])dataObject.GetData(DataFormats.FileDrop);

    // taking arbitrary the first file of the dropped files collection
    FileInfo file = files[0];

    if (file != null && IsImageFile(file.Extension))
    {
        Stream stream = file.OpenRead();
        string name = file.Name;

        // Saving a copy of the image's stream provided for future byte[] generation
        lastStreamProfilePicture = stream;

        BitmapImage droppedImage = new BitmapImage();
        droppedImage.SetSource(stream);

        ImageProfileControl.Source = droppedImage;
        UseDefaultProfilePicture = false;
    }
}

Cette méthode utilise celle-ci :

 // Checking if image is in JPG format
private bool IsImageFile(string extension)
{
    return (extension.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase)
        || extension.Equals(".jpeg", StringComparison.InvariantCultureIgnoreCase));
}

Elle a simplement pour but de vérifier que l’extension du fichier déposé n’est uniquement que de type “.JPG”. C’est une maigre vérification mais c’est toujours ça.

2. Bah, il ne reste plus qu’à décider qui écoute les évènements de type drag’n’drop. Nous allons le positionner tout en haut de notre arbre de contrôles de RegistrationForm en mettant à vrai la propriété AllowDrop et en positionnant la méthode Drop sur celle que venons de voir juste au-dessus :

 x:Class="RiaSvcProfilePicture.LoginUI.RegistrationForm"
…
d:DataContext="{d:DesignInstance Type=web:RegistrationData}" AllowDrop="True" Drop ="StackPanel_Drop" >

C’est tout ! Simple non ? Testez à nouveau. Vous aurez le même résultat qu’à l’étape précédente mais en pouvant cette fois-ci choisir un fichier depuis d’explorateur de fichier et le glisser/déposer sur le formulaire d’enregistrement.

Etape 5 : Modification du contrôle RegistrationForm pour récupérer une image depuis la Webcam

Cette étape demande un peu plus de boulot. Non pas que l’utilisation de la Webcam dans Silverlight 4 soit compliquée (bien au contraire !). Non, cela est dû au fait que nous aurons pour la première fois une image entièrement générée par Silverlight. Dans les cas précédents, l’image venait du disque et était déjà au format Jpeg. Ici, l’image sera au format “RAW” (native) et il va falloir la travailler pour la transformer au format Jpeg. Silverlight n’est pas capable nativement d’encoder une image au format PNG, Jpeg ou autre. Il lui faut l’aide de librairies tierces pour cela. Voici 2 exemples que je vous recommande :

- un encodeur au format PNG réalisé par Joe Stegman : Dynamic image generation in Silverlight. Le code source se trouve ici.
- la librairie FJCore qui une est librairie Open Source permettant d’encoder des images au format Jpeg depuis Silverlight. Le projet se trouve ici : https://code.google.com/p/fjcore/ et vous pouvez télécharger le source directement depuis ici : https://fjcore.googlecode.com/svn/trunk/ où à l’aide d’un client Subversion (SVN).

Nous allons partir sur la 2ème solution.

1. Récupérez le projet FJCore et ajoutez-le au projet actuel. Vérifiez qu’il compile bien. Vous le trouverez sinon dans la solution finale proposée à la fin de ce billet.

2. Ajoutez une référence vers le projet FJCore depuis le projet Silverlight RiaSvcProfilePicture :

RIAServicesPictureProfile007 RIAServicesPictureProfile008

3. Ajoutez un nouvel élément de type “Silverlight Child Window” sous le répertoire Login et nommez le WebCamWindow.xaml :

RIAServicesPictureProfile009

4. Dans la partie XAML, passez la hauteur de la valeur par défaut (300) à 340 puis ajoutez ce rectangle au-dessus du bouton Cancel :

 <Rectangle x:Name="PreviewScreen" RadiusX="5" RadiusY="5" Fill="Black" />

Cela sera la zone que nous allons peintre avec la Webcam.

5. Remplacez le code de la classe WebCamWindow par celui-ci:

 public CaptureSource captureSource;
// event used to notify callers that the image captured from
// the webcam is available
public event EventHandler<System.Windows.Media.CaptureImageCompletedEventArgs> ImageAvailable;

public WebCamWindow()
{
    InitializeComponent();
}

protected override void OnOpened()
{
    base.OnOpened();
    StartWebCam();
}

private void StartWebCam()
{
    if (captureSource == null)
    {
        // Create new CaptureSource if first run
        captureSource = new CaptureSource();
        captureSource.CaptureImageCompleted += 
          new EventHandler<CaptureImageCompletedEventArgs>(captureSource_CaptureImageCompleted);
    }
    else
    {
        // Stops old source if second run
        captureSource.Stop();
    }

    // Set VideoCaptureDevice to selected WebCam
    captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();

    // Create VideoBrush to paint WebCam preview
    VideoBrush PreviewBrush = new VideoBrush();
    PreviewBrush.SetSource(captureSource);
    PreviewScreen.Fill = PreviewBrush;

    // Start capture source
    captureSource.Start();
}

void captureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
    ImageAvailable(this, e);
    captureSource.Stop();
}

private void OKButton_Click(object sender, RoutedEventArgs e)
{
    PreviewScreen.Fill = null;
    captureSource.CaptureImageAsync();
    this.DialogResult = true;
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    captureSource.Stop();
    this.DialogResult = false;
}

Ainsi, nous en avons fini avec cette child window. Revenons à la fenêtre de registration.

6. De retour dans la classe RegistrationForm.xaml.cs, ajoutez ces 2 nouveaux membres :

 // cache element for Jpeg generation to send 
// the appropriate bytes[] back to the server
private WriteableBitmap lastWriteableWebCamProfilePicture = null;
// custom child window embedding the webcam 
private Views.Login.WebCamWindow webCamWindow;

Dans le constructeur, ajoutez ce code :

 webCamWindow = new Views.Login.WebCamWindow();
webCamWindow.Closed += new EventHandler(webCamWindow_Closed);
webCamWindow.ImageAvailable += 
   new EventHandler<CaptureImageCompletedEventArgs>(webCamWindow_ImageAvailable);

Du coup, il vous faut ces 2 gestionnaires d’évènements :

 // Event raised by the Web Cam Child Window once
// the capture is available
void webCamWindow_ImageAvailable(object sender, CaptureImageCompletedEventArgs e)
{
    lastWriteableWebCamProfilePicture = e.Result;
    ImageProfileControl.Source = lastWriteableWebCamProfilePicture;
    lastStreamProfilePicture = null;
}

// handling the closed event of the Web Cam child window
void webCamWindow_Closed(object sender, EventArgs e)
{
    // If the user has clicked 'Ok', we'll use webcam picture
    // as its avatar
    if (webCamWindow.DialogResult == true)
    {
        UseDefaultProfilePicture = false;
    }
}

7. Il nous reste à demander l’autorisation à l’utilisateur de faire usage de sa Webcam et d’afficher alors ce formulaire WebCamWindow pour le laisser prendre une photo :

 private void btnWebCam_Click(object sender, RoutedEventArgs e)
{
    // Determine if we have access to webcams
    if (!CaptureDeviceConfiguration.AllowedDeviceAccess)
    {
        // if not, request access
        if (!CaptureDeviceConfiguration.RequestDeviceAccess())
        {
            // denied! 
            return;
        }
    }

    // Displaying the WebCam child window to take a picture from the webcam
    webCamWindow.Show();
}

Vous aurez alors besoin de ce using :

 using System.Windows.Media;

Branchez ce gestionnaire au bouton associé dans le XAML :

 <Button x:Name="btnWebCam" Content="from webcam" Margin="10" Click="btnWebCam_Click" />

Si vous testez l’application à ce stade, vous devriez alors être en mesure de faire fonctionner correctement la Webcam et de prendre une photo qui apparaîtra alors sous forme de vignette dans le formulaire d’enregistrement. Voici les étapes que vous devriez pouvoir déjà suivre :

RIAServicesPictureProfile010

RIAServicesPictureProfile011 RIAServicesPictureProfile012

Cependant, il reste une ultime étape pour pouvoir aider le client Silverlight à renvoyer cette photo au serveur via WCF RIA Services.

Etape 6 : l’encodage en Jpeg

Pour cela, nous allons modifier la méthode GetPictureAsBytes() pour transformer le WriteableBitmap qui nous est fourni par la méthode AsyncCaptureImage() en un tableau de Bytes contenant une image encodée au format Jpeg.

Avant cette ligne de la méthode private byte[] GetPictureAsBytes() :

 return null;

Ajoutez ce gros pavé de code :

 if (lastWriteableWebCamProfilePicture != null)
{
    int width = lastWriteableWebCamProfilePicture.PixelWidth;
    int height = lastWriteableWebCamProfilePicture.PixelHeight;
    int bands = 3;
    byte[][,] raster = new byte[bands][,];

    //Convert the Image to pass into FJCore
    //Code From https://stackoverflow.com/questions/1139200/using-fjcore-to-encode-silverlight-writeablebitmap
    for (int i = 0; i < bands; i++)
    {
        raster[i] = new byte[width, height];
    }

    for (int row = 0; row < height; row++)
    {
        for (int column = 0; column < width; column++)
        {
            int pixel = lastWriteableWebCamProfilePicture.Pixels[width * row + column];
            raster[0][column, row] = (byte)(pixel >> 16);
            raster[1][column, row] = (byte)(pixel >> 8);
            raster[2][column, row] = (byte)pixel;
        }
    }

    ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
    FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

    //Encode the Image as a JPEG
    MemoryStream stream = new MemoryStream();
    FluxJpeg.Core.Encoder.JpegEncoder encoder = new FluxJpeg.Core.Encoder.JpegEncoder(img, 100, stream);
    encoder.Encode();

    //Back to the start
    stream.Seek(0, SeekOrigin.Begin);

    //Get the Bytes and write them to the stream
    byte[] binaryData = new Byte[stream.Length];
    long bytesRead = stream.Read(binaryData, 0, (int)stream.Length);
    return binaryData;
}

Ce dernier using vous sera nécessaire :

 using FluxJpeg.Core;

Et le tour est joué ! Nous avons fini l’implémentation complète du cahier des charges. Vous pouvez désormais récupérer une photo depuis le disque via l’explorateur de fichiers, via drag’n’drop ou via la webcam pendant la phase d’inscription d’un utilisateur sur le disque. Cette photo sera alors enregistrée dans le répertoire UserPictures côté serveur Web et réutilisée à chaque login effectué par l’utilisateur sur le site. Silverlight 4 nous a drôlement simplifié la vie pour gérer le drag’n’drop et la Webcam. WCF RIA Services nous a également facilité les choses pour mettre en place la logique de communication client/serveur. Un couple qui va donc très bien ensemble ! ;-)

Note fun : si vous avez une caméra Microsoft comme la mienne (LifeCam VX-6000), vous pouvez même ajouter des effets très professionnel pour générer votre avatar dans cette application. Voici un exemple :

RIAServicesPictureProfile013 RIAServicesPictureProfile014

Démonstration en vidéo et code source final à télécharger

Les autres formats:

Le code source :

Je ferais surement un prochain billet pour montrer comment modifier cette application pour la pousser dans Windows Azure. Il y a 2 trucs qui coincent pour l’instant :

- la gestion du profile basée sur ASP.NET Membership Provider qui va écrire par défaut dans aspnetdb. C’est facile à gérer en utilisant soit SQL Azure soit en branchant les providers vers le storage d’Azure comme je l’ai fait avec Cloudy Pizza
- il ne faut pas écrire les images directement sur le serveur web car cela empêche de monter le nombre d’instances à plus que… 1. :) Il faudra donc écrire dans une zone accessible par toutes les instances : le storage Azure à nouveau, accessible en HTTP REST.

Voilà, en attendant, je vous souhaite à tous d’excellentes fêtes de fin d’années. Que le Père Noël nous apporte plein de cadeaux geek et que la force soit avec vous !

David