Wilfried Woivré & .Net

Utilisation avancée de Windows Azure Table Storage

SEPT11

Le Table Storage est selon moi l’un des composants essentiels de tout bon projet fonctionnant sur Windows Azure, en effet il permet pour un faible cout de stocker des données  utilisable par la suite au sein de votre application. De plus celui-ci est hautement scalable, il est donc parfait pour une architecture de type Cloud !

A part les cas d’usages standards du Table Storage, c’est à dire du CRUD pur et dur, il est possible de modifier son comportement de façon à nous aider dans nos différentes actions, prenons le cas simple, je veux que pour chaque entité que je sauvegarde dans mon Table Storage soit indiqué une date de création, et une date de modification, cependant je ne veux pas que ces dernières viennent polluer mon coder dans chacune de mes classes.

 

On va donc commencer par créer une entité basique pour la démonstration :

public class MyEntity : TableServiceEntity
{
}

Cette classe a donc 3 propriétés qui sont PartitionKey, RowKey et TimeStamp, or je voudrais y ajouter des propriétés pour savoir la date de création de modification de chaque ligne ce qui peut mettre utile lorsque je consulte les données via des outils tels que Cloud Storage Studio ou Azure Storage Explorer.

Pour réaliser cela, il vous faut réaliser l’opération suivante :

private static XNamespace _atomNs = "http://www.w3.org/2005/Atom";
private static XNamespace _dataNs = "http://schemas.microsoft.com/ado/2007/08/dataservices";
private static XNamespace _metadataNs = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

public MyServiceContext(CloudStorageAccount storageAccount)
    : base(storageAccount.TableEndpoint.ToString(), storageAccount.Credentials)
{
    this.IgnoreMissingProperties = true;
    this.WritingEntity += GenericServiceContext_WritingEntity;

    var tableClient = storageAccount.CreateCloudTableClient();

    tableClient.CreateTableIfNotExist("TestTable");
}

private void GenericServiceContext_WritingEntity(object sender, System.Data.Services.Client.ReadingWritingEntityEventArgs e)
{
    MyEntity entity = e.Entity as MyEntity;

    if (entity == null)
    {
        return;
    }

    XElement properties = e.Data.Descendants(_metadataNs + "properties").First();

    XElement id = e.Data.Descendants(_atomNs + "id").First();
    if (String.IsNullOrWhiteSpace(id.Value))
    {
        var creationProperty = new XElement(_dataNs + "CreationDate", DateTime.Now);
        creationProperty.Add(new XAttribute(_metadataNs + "type", "Edm.DateTime"));
        properties.Add(creationProperty);
    }

    var modificationProperty = new XElement(_dataNs + "ModificationDate", DateTime.Now);
    modificationProperty.Add(new XAttribute(_metadataNs + "type", "Edm.DateTime"));
    properties.Add(modificationProperty);
}

 

Il vous faut avant tout dire que les propriétés manquantes de votre ServiceContext sont ignorées que ce soit lors de la lecture ou de l’écriture. Il faut par la suite s’abonner à l’évènement Writing Entity afin de modifier le XML envoyé à votre storage.

Ainsi on va pouvoir passer de ce XML :

<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
       xmlns="http://www.w3.org/2005/Atom">
  <title />
  <author>
    <name />
  </author>
  <updated>2012-09-10T13:08:42.4981763Z</updated>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>42</d:PartitionKey>
      <d:RowKey>106564e8-5093-4c5b-b059-e02ac75a59d4</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2012-09-10T15:08:42.4942455+02:00</d:Timestamp>
    </m:properties>
  </content>
</entry>

A un XML de ce type :

<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
       xmlns="http://www.w3.org/2005/Atom">
  <title />
  <author>
    <name />
  </author>
  <updated>2012-09-10T13:08:42.4981763Z</updated>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>42</d:PartitionKey>
      <d:RowKey>106564e8-5093-4c5b-b059-e02ac75a59d4</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2012-09-10T15:08:42.4942455+02:00</d:Timestamp>
      <d:CreationDate m:type="Edm.DateTime">2012-09-10T15:09:28.6219389+02:00</d:CreationDate>
      <d:ModificationDate m:type="Edm.DateTime">2012-09-10T15:09:28.6219389+02:00</d:ModificationDate>
    </m:properties>
  </content>
</entry>

 

Bien entendu, si vous vouliez réellement mettre en place cette solution, il faut avant tout vérifier que les propriétés que vous ajoutez n’existent pas déjà.

On notera par ailleurs, qu’il n’y a pas de gestion de statut dans le XML, donc pour savoir si c’est une entité que l’on créé où une entité que l’on modifie, il faut se baser sur la présence ou non de la valeur du champ id.

On peut donc voir le résultat ci-dessous au sein de mon table storage, je ne modifie que la dernière ligne dans mon application de démonstration :

image_thumb1

 

Maintenant, passons à un mode lecture avancé. Prenons le cas suivant, on vous donne un Table Storage à lire et explorer sans utiliser d’outils tierces, ni même Visual Studio et son explorateur de Table Storage ! Bref la galère à première vue …. Bon en même temps, c’est pas le scénario qui arrive tous les jours.

 

On va donc commencer par créer une entité plus complexe que la précédente qui va contenir un Tuple permettant de stocker nos différentes propriétés :

public class ExtractEntity
{
    private List<Tuple<string, object, object>> _properties = new List<Tuple<string, object, object>>();
    public List<Tuple<string, object, object>> Properties
    {
        get
        {
            return _properties;
        }
        set
        {
            _properties = value;
        }
    }
}

On notera au passage, que mon entité n’hérite pas de TableServiceEntity, et donc il est possible de créer son propre système de wrapping pour certaines entités.

Au niveau du code, il est possible de faire comme ci-dessous, c’est à dire s’abonner à l’évènement ReadingEntity et de modifier le XML d’entrée

public MyServiceContext(CloudStorageAccount storageAccount)
    : base(storageAccount.TableEndpoint.ToString(), storageAccount.Credentials)
{
    this.IgnoreMissingProperties = true;
    this.WritingEntity += GenericServiceContext_WritingEntity;
    this.ReadingEntity += GenericServiceContext_ReadingEntity;

    var tableClient = storageAccount.CreateCloudTableClient();

    tableClient.CreateTableIfNotExist("TestTable");
}

private void GenericServiceContext_ReadingEntity(object sender, System.Data.Services.Client.ReadingWritingEntityEventArgs e)
{
    ExtractEntity entity = e.Entity as ExtractEntity;
    if (entity == null)
    {
        return;
    }

    var q = from p in e.Data.Element(_atomNs + "content")
                            .Element(_metadataNs + "properties")
                            .Elements()
            select new
            {
                Name = p.Name.LocalName,
                IsNull = string.Equals("true", p.Attribute(_dataNs + "null") == null
                        ? null
                        : p.Attribute(_metadataNs + "null").Value, StringComparison.OrdinalIgnoreCase),
                TypeName = p.Attribute(_dataNs + "type") == null ? null : p.Attribute(_metadataNs + "type").Value,
                p.Value
            };

    foreach (var dp in q)
    {
        entity.Properties.Add(new Tuple<string, object, object>(dp.Name, dp.TypeName ?? "Edm.String", dp.Value));
    }
}

 

Le XML que l’on récupère en entrée, ressemble à celui ci-dessous, il est donc possible de retrouver l’ensemble des informations nécessaires pour connaitre l’entité et sa table d’origine

<entry m:etag="W/&quot;datetime'2012-09-10T13%3A34%3A20.327Z'&quot;"
       xmlns="http://www.w3.org/2005/Atom"
       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>http://127.0.0.1:10002/devstoreaccount1/TestTable(PartitionKey='42',RowKey='365aa1c5-ac6b-42ea-b674-749dfdc7b514')</id>
  <title type="text"></title>
  <updated>2012-09-10T14:52:49Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="TestTable" href="TestTable(PartitionKey='42',RowKey='365aa1c5-ac6b-42ea-b674-749dfdc7b514')" />
  <category term="devstoreaccount1.TestTable" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">42</d:PartitionKey>
      <d:RowKey xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">365aa1c5-ac6b-42ea-b674-749dfdc7b514</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-10T13:34:20.327Z</d:Timestamp>
      <d:CreationDate m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-07T11:54:29Z</d:CreationDate>
      <d:ModificationDate m:type="Edm.DateTime" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">2012-09-07T11:54:29Z</d:ModificationDate>
    </m:properties>
  </content>
</entry>

 

On va donc se retrouver avec une entité comprenant toutes les propriétés de notre table, comme on peut le voir ci-dessous :

image_thumb4

 

Voilà en espérant que ça puisse vous donner quelques idées pour vos développements futurs ou passés !

Remonter

Tour d’horizon de Mobile Services

SEPT7

Bonjour à tous,

 

Juste un petit article pour vous annoncer que j’ai publié sur le blog de So@t un article qui récapitule tout ce que l’on peut faire avec Windows Azure Mobile Services !

Allez y ça se passe à cette adresse : http://blog.soat.fr/2012/09/tout-savoir-sur-windows-azure-mobile-services/ 

Et si vous souhaitez aller plus loin, voici deux autres articles sur le même sujet : Trucs et astuces avec Windows Azure Mobile Services et Utiliser Windows Azure Mobile Services dans vos applications !

 

Voilà bonne lecture à tous

Remonter

Trucs et astuces avec Windows Azure Mobile Services

SEPT5

Vu que c’est la dernière nouveauté de Windows Azure, vous comprendrez qu’en ce moment je joue avec toute la journée, donc voilà des petits trucs et astuces que je peux vous donner pour vous aider lorsque vous développez avec ce SDK, donc pour une application de type Metro !

 

Déjà pour instancier votre client mobile, et créer une table, il faut réaliser un code de ce type

MobileServiceClient _mobileServiceClient = new MobileServiceClient(_mobileServiceUrl, _mobileServiceKey);
IMobileServiceTable<AnEntity> _anEntityMobileServiceTable = _mobileServiceClient.GetTable<AnEntity>();

 

Si pendant l’exécution de ce code, notamment à la deuxième ligne vous avez une erreur de ce type :

image

Vu que l’erreur vous indique que le paramètre “key” est null, alors qu’il ne le doit pas, vous avez deux solutions, soit hypothétiser une solution et la tester, soit regarder où se trouve ce paramètre key, qui se situe en fait dans le SDK de Windows Azure Mobile Service ….

Alors la solution pour résoudre cette erreur est assez simple, dans la déclaration de votre entité vous avez simplement oublié de donner le nom d’une de vos propriétés (oui c’est simple quand on a la solution)

[DataContract(Name = "anentity")]
public class AnEntity : BaseEntity
{
    [DataMember(Name = "id")]
    public int Id;

    [DataMember(Name = "astring")]
    public string AString;
   
    [DataMember(Name = "anint")]
    public int AnInt;
   
    [DataMember]
    public bool ABool;
}

Là dans ce cas, j’avais uniquement oublié le “Name” de ma propriété “ABool” !

 

Deuxième et dernière astuce, au niveau du portail Windows Azure, il est possible de modifier les différentes  méthodes de CRUD, il s’agit de Node.js, de plus il n’y a qu’un seul environnement disponible, donc attention aux modifications en production ! Je vous conseille donc d’avoir une application Mobile Services de tests pour éviter les catastrophes du type envoyé un toast à tous vos utilisateurs en disant “Bazinga” ou alors de modifier les droits d’accès à une table !

De plus, écrivez vos scripts dans Visual Studio, Web Matrix, ou tout autre logiciel / IDE capable de fournir un minimum d’intellisense sinon ça devient rapidement laborieux.

Voilà un peu de Node.js pour envoyer un toast lors de l’insertion en base d’une entité :

function insert(item, user, request) {
    request.execute({
        success: function () {
            request.respond();
            console.log(item.astring);
            console.log(item.channel);
            push.wns.sendToastText04(item.channel, { text1: item.astring }, {
                success: function (pushResponse) {
                    console.log("Sent push: ", pushResponse);
                }
            });
        }
    });
}

 

N’oubliez pas les logs, ils sont consultables facilement depuis le portail HTML 5 !

Voilà en espérant que ça vous sera utile !

Remonter

Utiliser Windows Azure Mobile Services dans vos applications

SEPT3

Si Windows Azure Mobile Service ne vous dit rien, je vous conseille avant tout d’aller voir l’annonce de Scott Guthrie à ce sujet !

Et maintenant que vous avez compris à quoi cela sert, vous vous dîtes, et flûte le service a l’air pourtant intéressant, cependant actuellement je ne peux l’utiliser qu’avec une application Windows 8 ! Et bien, sachez que non, il est possible de l’utiliser pour des applications Windows Phone 7, voir WPF, ou ce que vous souhaitez ! Bon, bien entendu, il ne sera pas possible d’utiliser les possibilités de push et d’authentification fournis par ce service, par contre un système CRUD bête et méchant, c’est possible !

Alors pour le prouver, j’ai réalisé (très rapidement) une application WPF permettant d’ajouter des items dans une table, et de les lire.

Le schéma de ces données est le suivant :

image

 

Alors pour pouvoir attaquer notre service hébergé dans Azure, de quoi avons-nous besoin, uniquement des urls pour lire, ajouter, modifier, supprimer ainsi que de votre clé privée pour accéder à votre service.

 

public readonly string GetTestDatas = "https://votrenamespace.azure-mobile.net/tables/testdata/";
public readonly string AddTestData = "https://votrenamespace.azure-mobile.net/tables/testdata/";
public readonly string UpdateTestData = "https://votrenamespace.azure-mobile.net/tables/testdata/";
public readonly string DeleteTestData = "https://votrenamespace.azure-mobile.net/tables/testdata/";
public readonly string ApiKey = "VotreCléPrivée";

 

Alors comment cela se passe ? Et bien c’est fort simple, votre application doit faire des appels en Json, soit en GET ou en POST selon les besoins, en prenant bien soin d’ajouter dans les headers de vos requêtes.

Par exemple, pour lister les différents éléments de ma table, il me suffit d’exécuter ce code :

public List<TestData> Get()
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(GetTestDatas);
    request.Headers.Add("X-ZUMO-APPLICATION", ApiKey);
    request.Method = "GET";
    request.ContentType = "application/json";

    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    Stream responseStream = response.GetResponseStream();

    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<TestData>));
    return (List<TestData>)ser.ReadObject(responseStream);
}

Bien entendu, il faut avoir au préalable créé une classe TestData dans mon cas, qui contient les différents valeurs nécessaires à la sérialisation, et la désérialisation.

[DataContract]
public class TestData
{
    [DataMember(Name="id")]
    public int Id { get; set; }
    [DataMember(Name = "value")]
    public string Value { get; set; }
}

 

De même pour l’insertion de données, ou pour la modification de données, il suffit de passer l’id de l’élément que vous voulez modifier pour qu’il le mette à jour ou insère une ligne, quand à la suppression, il faut juste passer la clé primaire de l’objet à supprimer pour réaliser cette action.

public TestData Put(TestData item)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(AddTestData);
    request.Headers.Add("X-ZUMO-APPLICATION", ApiKey);
    request.Method = "POST";
    request.ContentType = "application/json";

    using (var streamWriter = new StreamWriter(request.GetRequestStream()))
    {
        string json = "{\"value\": \"" + item.Value + "\"}";

        streamWriter.Write(json);
    }


    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    Stream responseStream = response.GetResponseStream();

    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(TestData));
    return (TestData)ser.ReadObject(responseStream);
}

 

On a donc vu qu’il est possible de détourner cette fonctionnalité pour l’utiliser au sein de nos projets, et dans ce cas de s’abstraire de toute la partie CRUD d’une application.

Tout le code est là, je ne vous le fournis donc pas !

Remonter

Nouvel endpoint disponible dans le mode PAAS de Windows Azure

JUIL21

Depuis le 7 juin dernier, vous avez du remarqué qu’il y a eu pas de changement du côté d’Azure, notamment avec l’arrivée du mode IAAS, cependant le mode PAAS qui existait avant ces nouveautés a été enrichies. Une des nouvelles fonctionnalités que j’aime bien, est la possibilité de définir des InstanceInput dans les endpoints de notre rôle Azure.

 

Pour configurer un endpoint de ce type, il vous suffit d’aller dans la configuration de votre rôle Azure, puis dans la partie Endpoint, comme on peut le voir ci-dessous :

image

Il est aussi possible de saisir ces informations directement dans le fichier de définition du service, comme ceci :

<Endpoints>
  <InputEndpoint name="Endpoint1" protocol="http" port="80" />
  <InstanceInputEndpoint name="Endpoint2" protocol="tcp" localPort="3389">
    <AllocatePublicPortFrom>
      <FixedPortRange max="10109" min="10105" />
    </AllocatePublicPortFrom>
  </InstanceInputEndpoint>
</Endpoints>

 

Le but de cet endpoint est d’attribué au load balancer Azure une plage de port qui redirigera vers un port privée, ainsi si dans mon cas j’ai 3 instances, lorsque que j’irais sur http://monservice.cloudapp.net:10105/ j’atteindrais ma machine virtuelle sur le port 3389, cependant les autres ports amèneront vers d’autres machines virtuelles.

 

Ici dans mon cas, je me sers de cette fonctionnalité pour accéder en remote desktop à mes machines, ce qui me permet d’accéder en remote à toutes mes machines sans pour autant avoir tous les fichiers rdp à télécharger sur le portail.

image

 

Ce cas là n’est en soit pas très utile, mais cette fonctionnalité vous offre la possibilité d’implémenter des fonctionnalités propres à une seule machine et non pas à tout votre cluster de machines.

Remonter