Michaël Gallego

This is my blog. What can you expect here? Well... Zend Framework 2, Amazon AWS...

Twitter

Google+

LinkedIn

Github

Last.fm

Utiliser SQL Azure avec Windows Phone 7.5

Je participe cette année à l’Imagine Cup, célèbre compétition organisée par Microsoft et, par conséquent, nos choix techniques ont été… comment dire… orientés ! Afin de pouvoir créer à la fois un site Internet et une application Windows Phone, nous avons décidé de choisir SQL Azure comme système de bases de données, qui a au moins l’avantage d’être peu cher et d’être entièrement géré par Microsoft.

Toutefois, j’ai un peu souffert pour faire fonctionner SQL Azure avec une application Silverlight pour Windows Phone 7.1 (aka Mango). J’ai donc décidé d’écrire cet article qui, j’espère, saura faire gagner un peu de temps aux suivants !

Je tiens à préciser par ailleurs que je suis tout nouveau dans l’environnement .NET (et donc Windows Phone) - cela fait deux semaines que je m’y suis mis -, n’hésitez pas à me corriger si je dis quelques bêtises ou s’il y a de meilleures façons de faire.

Installation des outils

Première étape primordiale : l’installation des outils. Rigolez pas, mais j’ai du m’y prendre à plusieurs fois ! Entre ma première installation foireuse de la version Ultimate et ma seconde installation de Visual Studio Express for Windows Phone avec laquelle je n’ai pas réussi à créer un projet WCF, autant dire que j’ai luté ! Et vu les dizaines de composants qu’installent ces logiciels, impossible d’y désigner un coupable…

Voici donc ce que j’ai installé, dans cet ordre :

  1. Visual Studio 2010 Ultimate (gratuit sur MSDNAA pour les étudiants).
  2. Visual Studio 2010 SP1.
  3. Windows Phone SDK (ici).
  4. Silverlight for Windows Phone Toolkit (ici) : ceci permet d’ajouter entre autre des contrôles supplémentaires pour vos applications Windows Phone tels que ListPicker ou une ProgressBar.
  5. Windows Azure SDK (ici).

Voilà. Vous venez de perdre 10 Go sur votre disque dur. Facile.

Création de votre compte SQL Azure

Avant de pouvoir utiliser SQL Azure dans votre application, il vous faut créer un compte à cette adresse, puis de créer votre base de données. Honnêtement, vous n’aurez pas envie d’y aller. Le site est codé en Silverlight et est extrêmement lent, plante régulièrement… Je vous conseille d’installer SQL Server 2008 R2 histoire d’administrer votre base de données autrement que par cette interface web, mais dans le cadre de cet article, c’est elle que je vais utiliser.

Architecture d’une application Windows Phone + SQL Azure

Commençons à nous amuser un peu ! L’architecture d’une application Windows Phone et SQL Azure est un peu particulière, particulièrement si, comme moi, vous venez du monde du PHP.

Rappelons qu’au sens de Visual Studio, une solution peut contenir plusieurs projets. Une application minimale va ainsi être constituée de deux projets : un projet WCF et un projet Silverlight Windows Phone.

WCF, pour Windows Communication Fondation, est un composant du framework .NET, et qui permet de créer des services qui vont communiquer avec le web. De ce fait, le projet WCF va nous servir à :

  • Créer nos tables grâce à l’Entity Framework (j’y reviens plus tard).
  • Générer nos modèles.
  • Écrire les services permettant d’effectuer les opérations CRUD auprès de la base SQL Azure.

Quant à l’application Silverlight Windows Phone, elle va consommer les services WCF pour ainsi effectuer les différentes tâches. De ce fait, l’application Windows Phone est assez légère dans le sens ou toutes les tâches liées à l’insertion, récupération, modification et suppression se font du côté WCF.

Cette “découpe” de la solution en deux projets différents est assez surprenant lorsque l’on a pas l’habitude, mais je suppose qu’on s’y fait.

Application WCF

L’application que nous allons réaliser ici sera extrêmement simple : notre base contiendra une table de commentaires que nous allons afficher dans notre application Windows Phone, avec la possibilité de les supprimer via le bouton idoine. Rien de plus, rien de moins.

Commençons avant toute chose, à créer une solution vide. Pour cela, rien de plus simple : Fichier > Nouveau > Projet > Nouvelle solution. Puis ajoutez un nouveau projet à la solution. Dans la catégorie “Cloud”, choisissez “Windows Azure Project” (le seul modèle disponible, logiquement). Une boîte de dialogue devrait s’afficher : double-cliquez sur “WCF Service Web Role”, puis validez.

Création des tables et des entités

La première chose que nous allons faire est créer une table pour notre projet. Pour ceci, il y a plusieurs solutions : on peut bien sûr directement créer les tables directement sous l’interface web SQL Azure, mais cela est très peu pratique. Nous allons pour cela utiliser l’Entity Framework, qui est un ORM qui semble très puissant, et qui gère notamment les différents types de relation d’une base de données relationnelles (OneToMany, ManyToMany…). L’avantage d’un tel framework est de pouvoir partir d’un modèle conceptuel (MCD par exemple), et de laisser le framework générer automatiquement les tables et les modèles, sans le moindre effort. A noter que l’Entity Framework permet également d’aller dans l’autre sens, à savoir partir d’une base de données déjà constituée et générer les entités.

Pour ceci, faites un clique droit sur le projet WCF (si vous avez laissé les noms par défaut, il devrait s’appeler WCFServiceWebRole1), puis Ajouter > Nouvel Élément. Dans la rubrique “Données”, choisissez “ADO.NET Entity Data Model”, renommez le fichier “Table.edmx”, et validez. Dans la boîte de dialogue qui apparaît, choisissez “Modèle vide”. Voici ce que vous devriez obtenir :

Création de la table

Double-cliquez sur Table.edmx afin d’ouvrir l’Entity Data Model Designer. Ajoutez une entité grâce à la boîte à outils, et ajoutez quelques champs afin d’obtenir quelque chose similaire à ça :

Structure de la table

Quelques remarques : en faisant clic droit sur l’entité > Propriété, une nouvelle fenêtre devrait s’ouvrir à droite. Par défaut, Visual Studio nomme les jeux d’entités (les collections d’une entité) le nom de l’entité suffixé par “Jeu” (CommentaireJeu” par exemple). Cela me semble un peu bizarre, je vous conseille donc de tout simplement renommer ça au pluriel, comme ceci :

Propriétés

Enfin, pensez à changer le type de Date à “DateTime” plutôt que String.

Comme vous le voyez, il est extrêmement pratique de créer ses entités avec cet outil. Avec plusieurs entités, il suffit d’y ajouter correctement les relations, et l’Entity Framework génèrera automatiquement le code qui va bien, les clés étrangères et compagnie…

Reste une dernière étape : ajouter la table en base. Pour ceci, faites un clic droit sur un espace vide de l’éditeur, et sélectionnez l’option “Générer la base de données à partir du modèle…”.

L’assistant de génération de la base de données devrait s’ouvrir. Faites “Nouvelle connexion”, puis choisissez “Microsoft SQL Server” comme source de données. Cet assistant supportant nativement SQL Azure, il suffit d’y renseigner les différents champs (le nom du serveur s’obtient sur l’application web de SQL Azure).

Propriétés de connexion

N’oubliez pas de choisir la base de données dans la partie “Connexion à la base de données” ! La fenêtre suivante va vous demander de choisir comment stocker les informations sensibles (le mot de passe, en l’occurrence). Restons simple, choisissez “Oui”, puis “Suivant” et “Terminer”.

Génial, nous obtenons à présent un script SQL qui a automatiquement été généré par l’Entity Framework. En effet, le framework ne créé pas automatiquement les tables dans la base de données, mais ne vous fournit que le code SQL. Nous allons donc devoir aller dans l’application web permettant de gérer la base de données (vous vous souvenez, l’infâme application Silverlight !) OU alors d’utiliser SQL Server 2008 R2.

Si vous avez choisi l’application web, cliquez sur le bouton “Nouvelle Requête” et copiez/collez y le script.

ATTENTION : SQL Azure ne supporte pas l’instruction “USE” généré par l’Entity Framework. De ce fait, supprimez la ligne USE [NomDeVotreBDD] tout en haut du script, puis exécutez le :

Interface de SQL Azure

Bravo ! La table a été générée.

Le truc vraiment sympa avec Entity Framework, c’est que si vous ouvrez le fichier Table.Designer.cs, vous verrez qu’il n’a pas fait que générer un vulgaire fichier SQL : il a aussi créer pour nous le code C# correspondant à notre modèle conceptuel, avec les propriétés et les constructeurs qui vont bien !

Dernière étape : ajoutez quelques lignes dans la table “pour tester”. Ajoutez en cinq ou six, ça devrait suffire, et puis on a pas que ça à faire ;-).

Création du service WCF

Nous allons maintenant modifier deux fichiers. Commençons par le fichier IService1.cs. Il s’agit ni plus ni moins d’une interface qui va exposer les différentes opérations possibles. Je n’ai pas encore bien saisi comment tout cela fonctionne sur une application plus grosse, mais j’imagine qu’il est possible de créer plusieurs services spécialisés (service d’authentification, service pour les articles, service pour les commentaires… par exemple).

Dans notre cas, je rappelle que notre application ne va faire que deux actions : récupérer tous les commentaires et supprimer un commentaire. Voici donc le contenu du fichier IService1.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFServiceWebRole1
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        List<Commentaire> GetAllComments();

        [OperationContract]
        void DeleteComment(int id);
    }
}

Puis le fichier Service.svc.cs, qui implémente l’interface précédente :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Data.Objects;
using System.Data;

namespace WCFServiceWebRole1
{
    public class Service1 : IService1
    {
        public List<Commentaire> GetAllComments()
        {
            using (var context = new TableContainer())
            {
                return (from p in context.Commentaires select p).ToList();
            }
        }

        public void DeleteComment(int id)
        {
            EntityKey key = new EntityKey("TableContainer.Commentaires", "ID", id);

            using (var context = new TableContainer())
            {
                object commentToDelete;

                if (context.TryGetObjectByKey(key, out commentToDelete))
                {
                    context.DeleteObject(commentToDelete);
                    context.SaveChanges();
                }
                else
                {
                    throw new InvalidOperationException("Aucun objet n'existe avec cet identifiant");
                }
            }
        }
    }
}

A vrai dire, je découvre avec vous la flexibilité du C#… Quelques remarques :

  • La syntaxe particulière de using permet d’appeler automatiquement la fonction “Dispose” en sortant du bloc, pour les classes implémentant l’interface IDisposable (ce qui est le cas de la classe TableContainer).
  • La classe TableContainer a été générée automatiquement pour nous dans le fichier Table.Designer.cs (merci l’Entity Framework !). Vous pouvez allez voir le code en appuyant sur la petite croix pour déplier le code.
  • La fonction GetAllComments se contente de créer une requête LINQ (c’est d’ailleurs assez puissant ce truc ôÔ !) et d’appeler la fonction ToList() pour récupérer le résultat sous forme d’une liste de Commentaire.
  • La fonction DeleteComment créé un objet EntityKey (qui prend en premier paramètre le nom de la collection, en l’occurrence TableContainer.Commentaires - regardez le code de Table.Designer.cs une nouvelle fois pour comprendre ça -, le nom de la colonne - en l’occurrence par l’identifiant -, et la valeur). Puis on vérifie si un objet avec cet identifiant existe et, si c’est le cas, on le supprime.

Voilà ! La partie dédiée à WCF est terminée. Il ne nous reste plus qu’à créer l’application Windows Phone qui va consommer ces services.

Application Windows Phone

Commencez par créer un nouveau projet au sein de notre solution (on aura donc deux projets qui vont coexister) : Ajouter > Nouveau projet, dans la partie “Silverlight for Windows Phone”, choisissez “Application Windows Phone”, validez puis dans le popup choisissez Windows Phone 7.1.

Ajouter le service créé

Pour le moment, notre projet Windows Phone ne “connaît pas” notre service que nous avons créé, et il va donc falloir ajouter la référence. Pour cela, faites clic droit sur l’application Windows Phone (PhoneApp1 dans mon cas), puis “Ajouter une référence de service”.

A droite, cliquez sur “Découvrir”, ce qui devrait ajouter dans le champ “Adresse” l’adresse du service que l’on a créé auparavant, puis Ok.

Création du modèle

Afin d’avoir une architecture “propre”, nous allons utiliser le modèle MVVM (Model - View - ViewModel). D’ailleurs, question existentielle : leur ViewModel ressemble furieusement au Controller, pourquoi avoir fait les originaux en choisissant un autre nom ?

Bref, l’aparté étant dite, créez un dossier “Model” au sein de votre application, puis ajoutez un fichier “Commentaire.cs”.

Voici le code de cette classe :

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace PhoneApp1.Model
{
    public class Commentaire : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private int _id;
        private string _auteur;
        private DateTime _date;
        private string _contenu;

        public string Id
        {
            get;
            set;
        }

        public string Auteur
        {
            get { return _auteur; }
            set
            {
                _auteur = value;
                NotifyPropertyChanged("Auteur");
            }
        }

        public DateTime Date
        {
            get { return _date; }
            set
            {
                _date = value;
                NotifyPropertyChanged("Date");
            }
        }

        public string Contenu
        {
            get { return _contenu; }
            set
            {
                _contenu = value;
                NotifyPropertyChanged("Contenu");
            }
        }

        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Comme vous pouvez le voir, il s’agit d’une classe ultra basique (mais super rébarbative à écrire !), qui contient les champs de notre entité, et des propriétés pour y accéder. On implémente également l’interface INotifyPropertyChanged afin de pouvoir s’inscrire à l’évènement et être notifié dès qu’une propriété a été modifiée.

**EDIT : **Il semblerait qu’en fait, il ne soit pas obligatoire de réécrire tout ça, et que l’on peut utiliser directement les entités générées par l’Entity Framework. Ceci est toléré même si la bonne pratique voudrait que l’on redéfinisse les modèles comme ci-dessous, et que les objets de l’Entity Framework (qui a donc une dépendance avec la base de données) soient convertis dans des objets “simples”. Voici cette discussion : http://social.msdn.microsoft.com/Forums/en/wpf/thread/2fda1bd8-5010-4ecd-bb4b-1abb6d426136

Création du View Model

Comme dit plus haut, le view model est l’équivalent (tout du moins comme je le comprends) de la notion de contrôleur. C’est donc lui qui va être responsable de faire la relation entre le service WCF (que nous avons écrit précédemment) et la vue (un fichier XAML). Entre temps, il pourra également peupler nos modèles (que nous avons écrit juste avant).

Le view model ne doit donc pas faire beaucoup plus que : appeler des fonctions du service, créer des instances de modèle, ou changer des valeurs de la vue. Du point de vue du Windows Phone, la vue sera évidemment un fichier XAML, tandis que le view model sera le fichier C# associé.

Puisque notre application est extrêmement simple et qu’elle ne consiste que d’un seul écran, nous allons réutiliser le fichier XAML et le fichier .cs associé, respectivement appelés MainPage.xaml et MainPage.xaml.cs.

Il est important de comprendre que, lors de l’importation de notre service dans l’application Windows Phone, deux évènements ont été créés automatiquement pour nous : GetAllCommentsCompleted et DeleteCommentCompleted. Ils correspondent donc aux deux méthodes de notre service, qui ont été suffixés de Completed.

Deux fonctions ont été également été créés : GetAllCommentsAsync et DeleteCommentAsync.

A partir de ça, il devient assez “clair” sur la manière de procéder. Concentrons nous pour l’instant sur la première action, à savoir charger tous les commentaires et les afficher dans la vue. Voici ainsi le contenu du fichier MainPage.xaml.cs (qui est, je le rappelle, notre view model) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using PhoneApp1.ServiceReference1;

namespace PhoneApp1
{
    public partial class MainPage : PhoneApplicationPage
    {
        private Service1Client _serviceClient;

        // Constructeur
        public MainPage()
        {
            InitializeComponent();

            _serviceClient = new Service1Client();
            _serviceClient.GetAllCommentsCompleted +=
                new EventHandler(_serviceClient_GetAllCommentsCompleted);
        }

        void _serviceClient_GetAllCommentsCompleted(object sender, GetAllCommentsCompletedEventArgs e)
        {
            var element = e.Result;
            var comments = from comment in element
                           select new Model.Commentaire
                           {
                               Id = Convert.ToInt32(comment.ID),
                               Auteur = comment.Auteur,
                               Date = comment.Date,
                               Contenu = comment.Contenu
                           };

            ListBox1.ItemsSource = comments.ToList();
        }
}

La première étape consiste à définir notre variable membre, qui est le Service1Client (qui réside dans le namespace Phone1.ServiceReference1).

La fonction MainPage (appelée à la création de la vue) va tout d’abord instancier le service client puis s’abonner à l’évènement GetAllCommentsCompleted. La fonction qui réagit à l’évènement, _serviceClient_GetAllCommentsCompleted, se contente de récupérer le résultat de la fonction GetAllComments du service (rappelez-vous, nous avions effectué une requête LINQ pour récupérer tous les commentaires de la base SQL Azure), et d’en construire nos modèles (encore une fois, je parle bien ici des modèles du projet Windows Phone !). Puis on initialise la propriété ItemsSource de la ListBox de notre vue (voir plus loin) avec ces données.

Evidemment, cette classe n’a pour l’instant pas beaucoup d’intérêt : on a bien une fonction qui est attachée à l’évènement, et une fonction pour y répondre (en l’occurrence peupler la liste de commentaires), mais l’évènement n’est pour l’instant jamais lancé.

En fait, il suffit d’utiliser la fonction GetAllCommentsAsync (voir plus haut), directement depuis notre constructeur :

// Constructeur
public MainPage()
{
    InitializeComponent();

    _serviceClient = new Service1Client();
    _serviceClient.GetAllCommentsCompleted +=
        new EventHandler(_serviceClient_GetAllCommentsCompleted);
    _serviceClient.GetAllCommentsAsync();
}

Que se passe t-il donc ?

  1. Le service WCF est d’abord créé.
  2. Une fonction s’abonne à l’évènement GetAllCommentsCompleted.
  3. La fonction GetAllCommentsAsync() est appelée, ce qui a pour effet d’appeler la fonction correspondante GetAllComments() de notre service, afin de récupérer les commentaires en base.
  4. Une fois la fonction GetAllComments() terminé, l’évènement est levé, et la fonction _serviceClient_GetAllCommentsCompleted est appelée.
  5. Cette dernière va créer les modèles et peupler la propriété ItemsSource, qui va être utilisée par notre vue (le fichier XAML).

Notre vue

Maintenant, il ne reste plus qu’à écrire notre vue. Pour cela, on va juste se contenter d’utiliser le mécanisme de Data Binding du XAML : http://pastebin.com/TLXS7Q3k (pour une raison que j’ignore, WordPress n’autorise pas mon code XML :/).

Déploiement de l’application

Il reste une chose à régler : à l’heure actuelle, si vous lancez le programme, vous vous apercevrez qu’il ouvre le navigateur. En fait, il tente de travailler en local, alors que nos bases sont, par définition, sur le cloud. Il est donc nécessaire de “publier” les services que nous avons créés dans la première partie sur le cloud. Heureusement, ce n’est pas bien compliqué.

Faites un clic droit sur le projet WCF (WCFServiceWebRole1), puis “Publish to Windows Azure”. Cliquez sur le lien “Sign in to download credentials”, ce qui va ouvrir le navigateur et télécharger un petit fichier texte. Cliquez sur “Import” et sélectionnez le fichier en question. Choisissez le nom de votre base comme “Hosting Service”, “Staging” pour Environnement, et “Cloud” comme Service Configuration, puis Suivant, et enfin validez.

Laissez Visual travailler, il va s’occuper de créer le service hébergé. Chez moi, cela a pris près d’une dizaine de minutes. Une fois que c’est terminé, allez dans l’espace administration Windows Azure ici : https://windows.azure.com, loguez vous, et normalement, vous devriez voir apparaître dans la partie “Services hébergés” de gauche, ce que vous venez de créer. Cliquez sur WindowsAzureProject1 et, à droite, copiez le nom DNS.

Windows Azure

Revenez sous Visual et dans le projet Windows Phone (PhoneApp1), ouvrez le fichier ServiceReferences.ClientConfig, et remplacez l’adresse de “endpoint” par l’adresse DNS que vous venez de copier, suivi du nom du service. En l’occurrence, si vous avez suivi les noms depuis le début de ce tutoriel, Service1.svc, ce qui devrait donner http://uneLongueChaine.cloudapp.net/Service1.svc.

ET VOICI LE RESULTAT !

Le résultat sur le simulateur Windows Phone !

Conclusion

I have a headache…

Note : je n’ai finalement pas eu le temps/courage d’ajouter la fonctionnalité de suppression… Mais c’est sûrement relativement facile, je pense. Je mettrai à jour cet article plus tard.